|
|
# Canvas (холст)
|
|
|
В библиотеке есть возможность "рисовать" в окне браузера командами с устройства при помощи HTML Canvas, а также получать координаты кликов по холсту, что даёт безграничные возможности по визуализации данных и взаимодействию с ними, фактически - **беспроводной цветной сенсорный дисплей**. Сетевой трафик минимизирован до коротких команд, так что отправка даже больших пакетов графики не потребляет много помяти. Также частично реализован [Processing API](https://processing.org/reference/) как более удобный для рисования, чем нативный [HTML Canvas API](https://www.w3schools.com/tags/ref_canvas.asp). Примеры использования:
|
|
|
- Вывод кастомных "графиков"
|
|
|
- Вывод схемы/карты помещения с точками интереса
|
|
|
- Графическое представление показаний датчика (наклон, уровень жидкости...)
|
|
|
- Отображение позиции "головки" станка или других механизмов и их частей
|
|
|
- Удалённый "тачпад" для управления устройством
|
|
|
|
|
|
## Особенности API
|
|
|
- Независимо от указанного в программе размера холст будет пропорционально отмасштабирован и вписан в свой контейнер (по ширине UI или внутри виджета), также масштаб будет увеличен на устройствах с увеличенной плотностью пикселей. Таким образом графика на холсте будет выглядеть одинаково и чётко при любом размере экрана или виджета, а *виртуальный* размер холста задаётся для удобства программиста, чтобы ориентироваться в размере своего холста. В то же время реальный размер холста в пикселях будет отличаться от "виртуального" размера, заданного в программе (подробнее ниже)!
|
|
|
- Начало координат - левый верхний угол, ось Y направлена вниз
|
|
|
- Отрицательные числа при задании координат графики **через функции Canvas** (не через кастомный js код) вычитаются из ширины холста, таким образом `point(-1, -1)` установит точку в правый нижний угол
|
|
|
- Список функций см. на главной странице документации
|
|
|
|
|
|
## Создание холста
|
|
|
Холст создаётся в билдере как обычный виджет:
|
|
|
```cpp
|
|
|
void build(gh::Builder& b) {
|
|
|
b.Canvas(); // пустой холст, 400x300px
|
|
|
b.Canvas(600, 600); // пустой холст 600x600px
|
|
|
}
|
|
|
```
|
|
|
|
|
|
|
|
|
### Цвет
|
|
|
Функции рисования принимают цвет в формате 24-бит `uint32_t`, например `void stroke(uint32_t hex)`, вторым аргументом может идти прозрачность 0..255 `void stroke(uint32_t hex, uint8_t a)`. Также в качестве цвета можно передавать встроенный класс `gh::Color`, как внешнюю переменную или сразу конструктором, например:
|
|
|
```cpp
|
|
|
gh::Color color;
|
|
|
cv.stroke(color);
|
|
|
|
|
|
cv.stroke(gh::Color(255, 0, 0)); // RGB, красный
|
|
|
cv.stroke(gh::Color(50, 200, 255, true)); // HSV, пастельный зелёный
|
|
|
cv.stroke(gh::Color(200)); // Gray, светло серый
|
|
|
|
|
|
cv.stroke(0xff0000, 200); // прозрачность 200 из 255
|
|
|
```
|
|
|
|
|
|
## Рисование
|
|
|
Для рисования внутри билдера нужно соблюдать следующий строгий порядок вызова функций:
|
|
|
1. Создать объект холста `gh::Canvas`
|
|
|
2. Передать его в `BeginCanvas()`
|
|
|
3. Рисовать
|
|
|
4. Вызвать `EndCanvas()`
|
|
|
|
|
|
> Между созданием `gh::Canvas` и завершением рисования `EndCanvas()` категорически не должно быть других виджетов!
|
|
|
|
|
|
> Функции вывода графики, вызванные внутри билдера, добавляют данные сразу к буферу билдера и не создают новых строк, что уменьшает расход и фрагментацию памяти
|
|
|
|
|
|
```cpp
|
|
|
void build() {
|
|
|
gh::Canvas cv; // создать холст
|
|
|
hub.BeginCanvas(300, 300, &cv); // начать рисование
|
|
|
// линии крест-накрест
|
|
|
cv.line(0, 0, -1, -1);
|
|
|
cv.line(0, -1, -1, 0);
|
|
|
hub.EndCanvas(); // закончить
|
|
|
}
|
|
|
```
|
|
|
|
|
|
### Обновление
|
|
|
На созданном в билдере холсте можно рисовать из основного цикла программы, по таймеру или событям. Для рисования нужно задать холсту имя и соблюдать следующий порядок вызова функций:
|
|
|
1. Создать объект обновления холста `gh::CanvasUpdate` с указанием имени и объекта GyverHub
|
|
|
2. Рисовать
|
|
|
3. Вызвать `send()`
|
|
|
|
|
|
```cpp
|
|
|
void build(gh::Builder& b) {
|
|
|
b.Canvas_(F("cv"), 300, 300); // холст с именем cv, 300x300
|
|
|
}
|
|
|
|
|
|
void loop() {
|
|
|
static gh::Timer tmr(300);
|
|
|
if (tmr) {
|
|
|
gh::CanvasUpdate cv("cv", &hub);
|
|
|
// вывести круг случайного цвета в случайном месте
|
|
|
cv.fill(gh::Color(random(255), random(255), random(255)), random(100, 255));
|
|
|
cv.circle(random(0, 600), random(0, 600), random(10, 50));
|
|
|
cv.send();
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
### Обновление в билдере
|
|
|
Функции рисования на `gh::Canvas` внутри билдера работают только в том случае, если билдер вызван для сборки ПУ, в этот же момент собираются остальные виджеты. Если билдер вызван для обработки действия - *функции рисования в теле билдера игнорируются*, но можно отправлять обновления через `gh::CanvasUpdate`. Пример: по клику по кнопке добавлять кружочки на холст с изначальным рисунком:
|
|
|
|
|
|
```cpp
|
|
|
void build(gh::Builder& b) {
|
|
|
if (b.Button().click()) { // клик по кнопке (обработка действия)
|
|
|
gh::CanvasUpdate cv("cv", &hub);
|
|
|
// случайный кружок
|
|
|
cv.circle(random(0, 30) * 10, random(0, 30) * 10, random(5, 30));
|
|
|
cv.send();
|
|
|
}
|
|
|
|
|
|
gh::Canvas cv; // создать холст
|
|
|
b.BeginCanvas_(F("cv"), 300, 300, &cv); // и начать рисование
|
|
|
|
|
|
// это рисование будет выполнено только при сборке панели управления
|
|
|
cv.line(0, 0, -1, -1); // линии крест-накрест
|
|
|
cv.line(0, -1, -1, 0);
|
|
|
b.EndCanvas(); // закончить
|
|
|
}
|
|
|
```
|
|
|
|
|
|
### Обработка кликов
|
|
|
GyverHub позволяет получить координаты кликов мышкой (и касания пальцем) по холсту в координатах заданного в программе размера холста. Для этого нужно создать переменную обработчика позиции типа `gh::Pos` и подключить её к виджету холста. Действие можно обработать через `click()` с холста, либо `changed()` с позиции:
|
|
|
|
|
|
```cpp
|
|
|
void build(gh::Builder& b) {
|
|
|
gh::Pos pos;
|
|
|
gh::Canvas cv;
|
|
|
if (b.BeginCanvas(400, 600, &cv, &pos).click()) {
|
|
|
Serial.print("click: ");
|
|
|
Serial.print(pos.x);
|
|
|
Serial.print(',');
|
|
|
Serial.println(pos.y);
|
|
|
}
|
|
|
b.EndCanvas();
|
|
|
|
|
|
// ИЛИ
|
|
|
if (pos.changed()) {
|
|
|
Serial.print("pos: ");
|
|
|
Serial.print(pos.x);
|
|
|
Serial.print(',');
|
|
|
Serial.println(pos.y);
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
Если действия по клику с холста должны приводить к выполнению какого-то объёмного кода, то лучше вынести обработку из билдера в основной цикл программы, а переменную позиции создать глобально. В таком случае узнать о факте клика можно из флага `changed()`:
|
|
|
|
|
|
```cpp
|
|
|
gh::Pos pos;
|
|
|
|
|
|
void build(gh::Builder& b) {
|
|
|
b.Canvas_(F("cv"), 400, 600, nullptr, &pos);
|
|
|
}
|
|
|
|
|
|
void loop() {
|
|
|
hub.tick();
|
|
|
|
|
|
if (pos.changed()) {
|
|
|
Serial.println(pos.x);
|
|
|
Serial.println(pos.y);
|
|
|
|
|
|
// выведем кружок в точку клика
|
|
|
gh::CanvasUpdate cv("cv", &hub);
|
|
|
// случайный кружок
|
|
|
cv.circle(pos.x, pos.y, 10);
|
|
|
cv.send();
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
Можно реагировать на клики прямо в билдере:
|
|
|
```cpp
|
|
|
void build(gh::Builder& b) {
|
|
|
gh::Canvas cv;
|
|
|
gh::Pos pos; // создадим локально, глобально в этом случае не нужно
|
|
|
|
|
|
b.BeginCanvas_(F("cv"), 400, 400, &cv, &pos);
|
|
|
cv.stroke(0xff0000);
|
|
|
cv.strokeWeight(5);
|
|
|
cv.line(0, 0, -1, -1);
|
|
|
cv.line(0, -1, -1, 0);
|
|
|
b.EndCanvas();
|
|
|
|
|
|
if (pos.changed()) {
|
|
|
// сюда попадаем при клике по холсту
|
|
|
gh::CanvasUpdate cv("cv", &hub);
|
|
|
cv.circle(pos.x, pos.y, 10); // кружок в точку клика
|
|
|
cv.send();
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
### Клики по геометрии
|
|
|
`gh::Pos` также позволяет обрабатывать клики внутри геометрии. Пример холста, на который выведен прямоугольник и окружность, при клике они меняют цвет заливки:
|
|
|
|
|
|
```cpp
|
|
|
void build() {
|
|
|
gh::Pos pos;
|
|
|
gh::Canvas cv;
|
|
|
|
|
|
b.BeginCanvas_(F("cv"), 300, 200, &cv, &pos);
|
|
|
cv.stroke(0xff0000);
|
|
|
cv.fill(0);
|
|
|
cv.strokeWeight(5);
|
|
|
cv.rect(50, 50, 100, 100);
|
|
|
cv.circle(200, 100, 30);
|
|
|
b.EndCanvas();
|
|
|
|
|
|
if (pos.changed()) {
|
|
|
gh::CanvasUpdate cv("cv", &hub);
|
|
|
if (pos.inRect(50, 50, 100, 100)) {
|
|
|
static bool f = 0;
|
|
|
f = !f;
|
|
|
cv.fill(f ? 0xff0000 : 0);
|
|
|
cv.rect(50, 50, 100, 100);
|
|
|
}
|
|
|
if (pos.inCircle(200, 100, 30)) {
|
|
|
static bool f = 0;
|
|
|
f = !f;
|
|
|
cv.fill(f ? 0xff0000 : 0);
|
|
|
cv.circle(200, 100, 30);
|
|
|
}
|
|
|
cv.send();
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
### Текстовые команды
|
|
|
Свои [команды рисования](https://www.w3schools.com/tags/ref_canvas.asp) можно вводить в текстовом виде в функцию `.custom()`. Особенности:
|
|
|
- В коде холста текущий *Canvas* всегда называется `cv`, а его *Context* - `cx`
|
|
|
- Не работает задание отрицательных координат, т.к. функции вызываются вне парсера Canvas API
|
|
|
- Для ввода строковых значений допускаются как одинарные кавычки (`'`/`\'`), так и двойные (`"`/`\"`)
|
|
|
- Область видимости созданных переменных - внутри всего блока между созданием `Canvas` и его отправкой/завершением
|
|
|
- В области видимости холста есть функция `scale()`, при помощи которой можно привести виртуальные координаты к реальным. Нужно просто умножить значение на эту функцию
|
|
|
|
|
|
Пример с горизонтальным градиентом от красного к белому на весь холст:
|
|
|
```cpp
|
|
|
void build(gh::Builder& b) {
|
|
|
gh::Canvas cv;
|
|
|
b.BeginCanvas(400, 300, &cv);
|
|
|
// градиент шириной 400 виртуальных пикселей - масштабируем в реальные
|
|
|
cv.custom(F(
|
|
|
"let grd=cx.createLinearGradient(0,0,400*scale(),0);"
|
|
|
"grd.addColorStop(0,'red');"
|
|
|
"grd.addColorStop(1,'white');"
|
|
|
"cx.fillStyle = grd;"));
|
|
|
cv.fillRect(0, 0, -1, -1);
|
|
|
b.EndCanvas();
|
|
|
}
|
|
|
```
|
|
|
|
|
|
### Вывод изображений
|
|
|
Нужно указать путь к файлу с изображением:
|
|
|
```cpp
|
|
|
void build(gh::Builder& b) {
|
|
|
gh::Canvas cv;
|
|
|
b.BeginCanvas_("cv2", 400, 300, &cv);
|
|
|
cv.drawImage("/image.jpg", 0, 0, 400);
|
|
|
b.EndCanvas();
|
|
|
}
|
|
|
```
|
|
|
|
|
|
Также можно вывести напрямую кадр с камеры, перехватив обработчик `Fetch`:
|
|
|
TODO |