## Canvas (холст)
В библиотеке есть возможность "рисовать" в окне браузера командами с устройства при помощи HTML Canvas, а также получать координаты кликов по холсту, что даёт безграничные возможности по визуализации данных и взаимодействию с ними, фактически - **беспроводной цветной сенсорный дисплей**. Сетевой трафик минимизирован до коротких команд, так что отправка даже больших пакетов графики не потребляет много помяти. Также частично реализован [Processing API](https://processing.org/reference/) как более удобный для рисования, чем нативный [HTML Canvas API](https://www.w3schools.com/tags/ref_canvas.asp). Примеры использования:
- Вывод кастомных "графиков"
- Вывод схемы/карты помещения с точками интереса
- Графическое представление показаний датчика (наклон, уровень жидкости...)
- Отображение позиции "головки" станка или других механизмов и их частей
- Удалённый "тачпад" для управления устройством

### Особенности API
- Независимо от указанного в программе размера холст будет пропорционально отмасштабирован и вписан в свой контейнер (по ширине UI или внутри виджета), также масштаб будет увеличен на устройствах с увеличенной плотностью пикселей. Таким образом графика на холсте будет выглядеть одинаково и чётко при любом размере экрана или виджета, а *виртуальный* размер холста задаётся для удобства программиста, чтобы ориентироваться в размере своего холста. В то же время реальный размер холста в пикселях будет отличаться от "виртуального" размера, заданного в программе (подробнее ниже)!
- Холст не перестраивается при изменении ширины окна браузера и сохраняет выведенную графику. Обновляется и перестраивается под новую ширину он только при обновлении панели управления
- Начало координат - левый верхний угол, ось Y направлена вниз
- Отрицательные числа при задании координат графики **через функции GHcanvas** (не через кастомный js код) вычитаются из ширины холста, таким образом `point(-1, -1)` установит точку в правый нижний угол
- Список функций см. на главной странице [документации](https://github.com/GyverLibs/GyverHub/wiki)

### Создание холста
Холст создаётся в билдере как обычный компонент. Холст может быть виджетом, ширина виджета также к нему применяется:
```cpp
void build() {
  hub.Canvas();           // пустой холст, 400x300px
  hub.Canvas(600, 600);   // пустой холст 600x600px
}
```

### Рисование в билдере
Для рисования внутри билдера нужно соблюдать следующий строгий порядок вызова функций:
1. Создать объект холста `GHcanvas`
2. Передать его в `BeginCanvas()`
3. Рисовать
4. Вызвать `EndCanvas()`

> Между созданием `GHcanvas` и завершением рисования `EndCanvas()` категорически не должно быть других функций компонентов!

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

```cpp
void build() {
  GHcanvas cv;                     // создать холст
  hub.BeginCanvas(300, 300, &cv);  // начать рисование
  // линии крест-накрест
  cv.line(0, 0, -1, -1);
  cv.line(0, -1, -1, 0);
  hub.EndCanvas();                 // закончить
}
```

### Цвет
Функции рисования принимают цвет в формате 24-бит `uint32_t`, например `void stroke(uint32_t hex)`, вторым аргументом может идти прозрачность 0..255 `void stroke(uint32_t hex, uint8_t a)`. Также в качестве цвета можно передавать встроенный тип `GHcolor`, как внешнюю переменную или сразу конструктором, например:
```cpp
GHcolor color;
cv.stroke(color);

cv.stroke(GHcolor(255, 0, 0));          // RGB, красный
cv.stroke(GHcolor(50, 200, 255, true)); // HSV, пастельный зелёный
cv.stroke(GHcolor(200));                // Gray, светло серый

cv.stroke(GH_RED);      // встроенные константы цвета
cv.stroke(GH_RED, 200); // прозрачность 200 из 255
```

### Рисование "на лету"
На созданном в билдере холсте можно рисовать из основного цикла программы, по таймеру или событям. Для рисования нужно задать холсту имя и соблюдать следующий порядок вызова функций:
1. Создать объект холста `GHcanvas`
2. Передать холст в `sendCanvasBegin()` с указанием имени существующего холста
3. Рисовать
4. Передать холст в `sendCanvasEnd()`

```cpp
void build() {
  hub.Canvas_(F("cv"), 300, 300);  // холст с именем cv, 300x300
}

void loop() {
  static GHtimer tmr(300);
  if (tmr.ready()) {
    GHcanvas cv;                        // создать холст
    hub.sendCanvasBegin(F("cv"), cv);   // начать отправку
    // вывести круг случайного цвета в случайном месте
    cv.fill(GHcolor(random(255), random(255), random(255)), random(100, 255));
    cv.circle(random(0, 600), random(0, 600), random(10, 50));
    hub.sendCanvasEnd(cv);              // закончить отправку
  }
}
```

#### Рисование по действию
Функции рисования на холсте внутри билдера работают только в том случае, если билдер вызван для сборки панели управления. Если билдер вызван для обработки действия - *функции рисования в теле билдера игнорируются*. Но внутри условия компонента они будут работать, таким образом можно отправлять обновления холста по действиям с компонентов. Пример: по клику по кнопке добавлять кружочки на холст с изначальным рисунком:

```cpp
void build() {
  if (hub.Button()) {                   // клик по кнопке (обработка действия)
    GHcanvas cv;                        // создать холст
    hub.sendCanvasBegin(F("cv"), cv);   // начать отправку
    // случайный кружок
    cv.circle(random(0, 30) * 10, random(0, 30) * 10, random(5, 30));
    hub.sendCanvasEnd(cv);              // закончить отправку
  }

  GHcanvas cv;                              // создать холст
  hub.BeginCanvas_(F("cv"), 300, 300, &cv); // начать рисование

  // это рисование будет выполнено только при сборке панели управления
  cv.line(0, 0, -1, -1);                    // линии крест-накрест
  cv.line(0, -1, -1, 0);
  hub.EndCanvas();                          // закончить
}
```

### Обработка кликов
GyverHub позволяет получить координаты кликов мышкой (и касания пальцем) по холсту в координатах заданного в программе размера холста. Для этого нужно создать переменную обработчика позиции типа `GHpos` и подключить её к компоненту холста. Компонент холста вернёт `true`, если в приложении был клик по холсту. С переменной можно получить координаты из `x` и `y`:

```cpp
GHpos pos;
GHcanvas cv;
if (hub.BeginCanvas(400, 600, &cv, &pos)) {
  Serial.println(pos.x);
  Serial.println(pos.y);
}
// рисование если нужно
// ...
```

Если действия по клику с холста должны приводить к выполнению какого-то объёмного кода, то лучше вынести обработку из билдера в основной цикл программы, а переменную позиции создать глобально. В таком случае узнать о факте клика можно из флага `changed()`:

```cpp
GHpos pos;

void build() {
  // ...
  GHcanvas cv;
  hub.BeginCanvas_(F("cv"), 400, 600, &cv, &pos);
  // ...
}

void loop() {
  // ...
  if (pos.changed()) {
    Serial.println(pos.x);
    Serial.println(pos.y);

    // выведем кружок в точку клика
    GHcanvas cv;                        // создать холст
    hub.sendCanvasBegin(F("cv"), cv);   // начать отправку
    // случайный кружок
    cv.circle(pos.x, pos.y, 10);
    hub.sendCanvasEnd(cv);              // закончить отправку
  }
  // ...
}
```

В то же время можно реагировать на клики прямо в билдере:
```cpp
void build() {
  GHcanvas cv;
  GHpos pos;  // создадим локально, глобально в этом случае не нужно

  if (hub.BeginCanvas_(F("cv"), 400, 600, &cv, &pos)) {
    // сюда попадаем при клике по холсту
    // для рисования нужно создать новый холст!
    GHcanvas cv0;
    hub.sendCanvasBegin(F("cv"), cv0);
    cv0.circle(pos.x, pos.y, 10);   // кружок в точку клика
    hub.sendCanvasEnd(cv0);
  }

  // рисуем ...
  // рисование работает только при загрузке панели управления
  hub.EndCanvas();
}
```

### Клики по геометрии
`GHpos` также позволяет обрабатывать клики внутри геометрии. Пример холста, на который выведен прямоугольник и окружность, при клике они меняют цвет заливки:

```cpp
void build() {
  GHpos pos;
  GHcanvas cv;
  if (hub.BeginCanvas_(F("cv"), 300, 200, &cv, &pos)) {
      GHcanvas cv;
      hub.sendCanvasBegin(F("cv"), cv);
      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);
      }
      hub.sendCanvasEnd(cv);
  }

  cv.stroke(0xff0000);
  cv.fill(0);
  cv.strokeWeight(5);
  cv.rect(50, 50, 100, 100);
  cv.circle(200, 100, 30);
  hub.EndCanvas();
}
```

### Текстовые команды
Свои [команды рисования](https://www.w3schools.com/tags/ref_canvas.asp) можно вводить в текстовом виде в функцию `.custom()`. Особенности:
- В коде холста текущий *Canvas* всегда называется `cv`, а его *Context* - `cx`
- Не работает задание отрицательных координат, т.к. функции вызываются вне парсера GHcanvas API
- Для ввода строковых значений допускаются как одинарные кавычки (`'`/`\'`), так и двойные (`"`/`\"`)
- Область видимости созданных переменных - внутри всего блока между созданием `GHcanvas` и его отправкой/завершением
- В области видимости холста есть функция `scale()`, при помощи которой можно привести виртуальные координаты к реальным. Нужно просто умножить значение на эту функцию

Пример с горизонтальным градиентом от красного к белому на весь холст:
```cpp
GHcanvas cv;
hub.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);
```

Пример вывода изображения по url, выведется в реальном размере самого изображения
```cpp
cv1.custom(F("let img = new Image;"
"img.src='https://www.w3schools.com/tags/img_the_scream.jpg';"
"img.onload = function(){cx.drawImage(img,0,0);};"));
```

В следующем примере масштабируем изображение под ширину 200 пикселей
```cpp
cv1.custom(F("let img = new Image;"
"img.src='https://www.w3schools.com/tags/img_the_scream.jpg';"
"img.onload = function(){cx.drawImage(img,0,0,200*scale(),(200*img.height/img.width)*scale());};"));
```