You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

248 lines
14 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 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