Статья: Изучение библиотеки LVGL

Источник статьи

Или на канале ДЗЕН

Начало работы с библиотекой LVGL. Первые шаги в создании графических интерфейсов

Введение

В настоящее время графические интерфейсы пользователя (GUI) используются практически во всех видах устройств, от бытовых приборов до сложных систем автоматизации и управления. Создание эффективных и удобных в использовании интерфейсов становится все более важным. Одним из инструментов, которые могут помочь в этом, является библиотека LVGL (Light and Versatile Graphics Library). LVGL – это бесплатная библиотека GUI на языке программирования Си, которая позволяет разработчикам создавать интерфейсы для различных устройств, включая микроконтроллеры и системы IoT. В этой статье мы рассмотрим основы работы с библиотекой LVGL, чтобы помочь начинающим разработчикам создавать эффективные и удобные графические интерфейсы.

Преимущества LVGL

Библиотека LVGL имеет несколько преимуществ, которые делают ее одним из лучших инструментов для создания графических интерфейсов:

  • Платформонезависимость: LVGL может быть использован на различных устройствах, включая микроконтроллеры и системы IoT, что делает его очень гибким и удобным для разработчиков.
  • Простота использования: LVGL имеет простой и интуитивно понятный API, что делает его очень доступным для начинающих разработчиков.
  • Низкое потребление ресурсов: LVGL использует очень мало ресурсов, что делает его идеальным для использования на микроконтроллерах и других устройствах с ограниченными ресурсами.
  • Широкие возможности настройки: LVGL предоставляет различные настройки и опции для создания интерфейсов, которые могут быть настроены под конкретные потребности проекта.
  • Открытый исходный код: LVGL является проектом с открытым исходным кодом, что позволяет разработчикам вносить изменения в код библиотеки и адаптировать ее под свои нужды.
  • Богатый функционал: LVGL предоставляет широкий набор функций и возможностей для создания интерфейсов, включая поддержку различных типов элементов управления, анимаций, шрифтов и других функций. 
  • Оптимизация производительности: LVGL был специально разработан для оптимальной производительности и быстрого рендеринга графических элементов, что позволяет создавать интерфейсы с плавной анимацией и отзывчивым пользовательским интерфейсом.
  • Поддержка сообщества: LVGL имеет активное сообщество разработчиков, которые работают над поддержкой библиотеки, исправлением ошибок и развитием новых функций. Это обеспечивает надежность и стабильность работы библиотеки и облегчает поиск решений в случае проблем при использовании LVGL.
  • Лицензия: LVGL использует лицензию MIT, что позволяет использовать его в коммерческих и некоммерческих проектах без ограничений. Это делает его доступным для всех разработчиков и позволяет использовать его в любых проектах, включая те, которые не могут использовать библиотеки с другими типами лицензий.

Подключение дисплея и сенсорного экрана

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

Про TFT_eSPI я уже ранее писал в статье 

TFT_eSPI. Мощная и быстрая библиотека для работы с TFT дисплеями.

А библиотеки-драйверы для дисплеев Adafruit были рассмотрены в статье 

Подключение TFT дисплея к ESP8266.

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

Что касается сенсорного экрана, то практически все доступные на Aliexpress дисплеи с возможностью сенсорного ввода, используют один из этих драйверов:

  • FT6X36
  • GT911
  • XPT2046

FT6X36 и GT911 – это емкостные сенсорные экраны с поддержкой мультитач, работающие через I2C интерфейс.
XPT2046 – резистивный сенсорный экран, распознающий одновременно только одно нажатие, и работающий по SPI интерфейсу.

Подключаются они к соответствующим пинам микроконтроллера и для работы каждый использует свою библиотеку:

  •  FT6X36: https://github.com/strange-v/FT6X36.git
  •  GT911: https://github.com/TAMCTec/gt911-arduino.git
  •  XPT2046: https://github.com/PaulStoffregen/XPT2046_Touchscreen.git

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

/*******************************************************************************

* Touch libraries:

* FT6X36: https://github.com/strange-v/FT6X36.git

* GT911: https://github.com/TAMCTec/gt911-arduino.git

* XPT2046: https://github.com/PaulStoffregen/XPT2046_Touchscreen.git ******************************************************************************/

/* uncomment for FT6X36 */

// #define TOUCH_FT6X36

// #define TOUCH_FT6X36_SCL 19

// #define TOUCH_FT6X36_SDA 18

// #define TOUCH_FT6X36_INT 39

// #define TOUCH_SWAP_XY

// #define TOUCH_MAP_X1 480 // #define TOUCH_MAP_X2 0

// #define TOUCH_MAP_Y1 0

// #define TOUCH_MAP_Y2 320

/* uncomment for GT911 */

#define TOUCH_GT911

#define TOUCH_GT911_SCL 32

#define TOUCH_GT911_SDA 33

#define TOUCH_GT911_INT 21

#define TOUCH_GT911_RST 25

#define TOUCH_GT911_ROTATION ROTATION_LEFT

#define TOUCH_MAP_X1 480

#define TOUCH_MAP_X2 0

#define TOUCH_MAP_Y1 320

#define TOUCH_MAP_Y2 0

/* uncomment for XPT2046 */

// #define TOUCH_XPT2046

// #define TOUCH_XPT2046_SCK 12

// #define TOUCH_XPT2046_MISO 13

// #define TOUCH_XPT2046_MOSI 11

// #define TOUCH_XPT2046_CS 38

// #define TOUCH_XPT2046_INT 18

// #define TOUCH_XPT2046_ROTATION 0

// #define TOUCH_MAP_X1 4000

// #define TOUCH_MAP_X2 100

// #define TOUCH_MAP_Y1 100

// #define TOUCH_MAP_Y2 4000

int touch_last_x = 0, touch_last_y = 0;

#if defined(TOUCH_FT6X36)

#include <Wire.h>

#include <FT6X36.h>

FT6X36 ts(&Wire, TOUCH_FT6X36_INT);

bool touch_touched_flag = true, touch_released_flag = true;

#elif defined(TOUCH_GT911)

#include <Wire.h>

#include

<TAMC_GT911.h> TAMC_GT911 ts = TAMC_GT911(TOUCH_GT911_SDA, TOUCH_GT911_SCL, TOUCH_GT911_INT, TOUCH_GT911_RST, max(TOUCH_MAP_X1, TOUCH_MAP_X2), max(TOUCH_MAP_Y1, TOUCH_MAP_Y2));

#elif defined(TOUCH_XPT2046)

#include <XPT2046_Touchscreen.h>

#include <SPI.h>

XPT2046_Touchscreen ts(TOUCH_XPT2046_CS, TOUCH_XPT2046_INT);

#endif

#if defined(TOUCH_FT6X36)

void touch(TPoint p, TEvent e)

{

if (e != TEvent::Tap && e != TEvent::DragStart && e != TEvent::DragMove && e != TEvent::DragEnd)

{ return;

}

// translation logic depends on screen rotation

#if defined(TOUCH_SWAP_XY)

touch_last_x = map(p.y, TOUCH_MAP_X1, TOUCH_MAP_X2, 0, TOUCH_MAP_X1);

touch_last_y = map(p.x, TOUCH_MAP_Y1, TOUCH_MAP_Y2, 0, TOUCH_MAP_Y1);

#else

touch_last_x = map(p.x, TOUCH_MAP_X1, TOUCH_MAP_X2, 0, TOUCH_MAP_X1);

touch_last_y = map(p.y, TOUCH_MAP_Y1, TOUCH_MAP_Y2, 0, TOUCH_MAP_Y1);

#endif

switch (e)

{

case TEvent::Tap:

Serial.println("Tap");

touch_touched_flag = true; touch_released_flag = true;

break;

case TEvent::DragStart:

Serial.println("DragStart");

touch_touched_flag = true;

break;

case TEvent::DragMove:

Serial.println("DragMove");

touch_touched_flag = true;

break;

case TEvent::DragEnd:

Serial.println("DragEnd");

touch_released_flag = true;

break; default: Serial.println("UNKNOWN"); break;

}

}

#endif void touch_init()

{

#if defined(TOUCH_FT6X36)

Wire.begin(TOUCH_FT6X36_SDA, TOUCH_FT6X36_SCL);

ts.begin();

ts.registerTouchHandler(touch);

#elif defined(TOUCH_GT911)

Wire.begin(TOUCH_GT911_SDA, TOUCH_GT911_SCL);

ts.begin();

ts.setRotation(TOUCH_GT911_ROTATION);

#elif

defined

(TOUCH_XPT2046) SPI.begin(TOUCH_XPT2046_SCK, TOUCH_XPT2046_MISO, TOUCH_XPT2046_MOSI, TOUCH_XPT2046_CS); ts.begin(); ts.setRotation(TOUCH_XPT2046_ROTATION);

#endif } bool touch_has_signal() {

#if defined(TOUCH_FT6X36) ts.loop(); return touch_touched_flag || touch_released_flag;

#elif defined(TOUCH_GT911) return true;

#elif defined(TOUCH_XPT2046) return ts.tirqTouched();

#else return false; #endif } bool touch_touched() { #if defined(TOUCH_FT6X36) if (touch_touched_flag) { touch_touched_flag = false; return true; }

else { return false; }

#elif defined(TOUCH_GT911) ts.read(); if (ts.isTouched) { #if defined(TOUCH_SWAP_XY) touch_last_x = map(ts.points[0].y, TOUCH_MAP_X1, TOUCH_MAP_X2, 0, TOUCH_MAP_X1-1);

touch_last_y = map(ts.points[0].x, TOUCH_MAP_Y1, TOUCH_MAP_Y2, 0, TOUCH_MAP_Y1-1); #else touch_last_x = map(ts.points[0].x, TOUCH_MAP_X1, TOUCH_MAP_X2, 0, TOUCH_MAP_X1-1);

touch_last_y = map(ts.points[0].y, TOUCH_MAP_Y1, TOUCH_MAP_Y2, 0, TOUCH_MAP_Y1-1); #endif return true; }

else { return false; }

#elif defined(TOUCH_XPT2046)

if (ts.touched())

{ TS_Point p = ts.getPoint(); #if defined(TOUCH_SWAP_XY) touch_last_x = map(p.y, TOUCH_MAP_X1, TOUCH_MAP_X2, 0, gfx->width() - 1); touch_last_y = map(p.x, TOUCH_MAP_Y1, TOUCH_MAP_Y2, 0, gfx->height() - 1);

#else touch_last_x = map(p.x, TOUCH_MAP_X1, TOUCH_MAP_X2, 0, gfx->width() - 1); touch_last_y = map(p.y, TOUCH_MAP_Y1, TOUCH_MAP_Y2, 0, gfx->height() - 1);

#endif return true; } else { return false; }

#else return false;

#endif } bool touch_released() { #if defined(TOUCH_FT6X36)

if (touch_released_flag) { touch_released_flag = false;

return true; }

else { return false; }

#elif defined(TOUCH_GT911)

return true;

#elif defined(TOUCH_XPT2046)

return true;

#else return false;

#endif

}

На этом настройка технической части завершена.

Первый проект на LVGL

После настройки дисплея и сенсорного экрана следующим шагом будет установка и адаптация библиотеки LVGL под ваши нужды. В данном руководстве будет использоваться версия библиотеки LVGL 8.3.6, поэтому для работы на более старых или новых версиях скорее всего в код программы придется внести некоторые изменения. Скачать её можно через Менеджер библиотек Arduino IDE:

Редактирование конфигурационного файла

После установки библиотеки вам нужно будет создать для нее конфигурационный файл.

Для этого перейдите в папку с библиотекой с установленной библиотекой. В Windows она обычно находится по этому пути:

C:\Users\ИмяПользователя\Documents\Arduino\libraries\lvgl

Далее найдите файл lv_conf_template.h и переименуйте его в  lv_conf.h и откройте в текстовом редакторе.

Прежде всего найдите строку:#if 0СКОПИРОВАТЬ КОД

Измените её на#if 1СКОПИРОВАТЬ КОД

Затем найдите строку:#define LV_TICK_CUSTOM 0СКОПИРОВАТЬ КОД

Измените её на:#define LV_TICK_CUSTOM 1СКОПИРОВАТЬ КОД

Хотя в данном файле достаточно много настроек, изменения только этих 2 параметров достаточно для использования LVGL в ваших проектах для Arduino IDE. Сохраните файл.

Далее важный момент. Авторы библиотеки настойчиво рекомендуют, после создания файла lv_conf.h, переместить его в папку c библиотеками Arduino IDE. То есть путь к файлу должен быть таким:

C:\Users\ИмяПользователя\Documents\Arduino\libraries\lv_conf.h

Возможно и стоит это сделать, но у меня все прекрасно работает даже если этот файл никуда не перемещать, а оставить в папке с библиотекой.

Код программы

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

Это будет простой проект с одной надписью и кнопкой.

При нажатии на кнопку, на ней появляется счетчик с числом касаний.

Создайте в Arduino IDE новый проект и скопируйте в него следующий код:

#include <lvgl.h>

#include <TFT_eSPI.h>

#include "touch.h" static const uint16_t screenWidth = 480; //ширина экрана

static const uint16_t screenHeight = 320; //высота экрана

//Объявление служебных переменных для LVGL

static lv_disp_draw_buf_t draw_buf;

static lv_color_t buf[screenWidth * screenHeight / 6];

lv_obj_t * btn1; lv_obj_t * screenMain; lv_obj_t * label;

TFT_eSPI tft = TFT_eSPI(); //инициализация библиотеки TFT_eSPI

//Здесь напишем функцию для вывода содержимого буфера на экран

void my_disp_flush( lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p )

{

uint32_t w = ( area->x2 - area->x1 + 1 );

uint32_t h = ( area->y2 - area->y1 + 1 );

tft.startWrite();

tft.setAddrWindow( area->x1, area->y1, w, h );

tft.pushColors( ( uint16_t * )&color_p->full, w * h, true );

tft.endWrite(); lv_disp_flush_ready( disp );

}

// Вычисление координат касания

void my_touchpad_read(lv_indev_drv_t *indev_driver, lv_indev_data_t *data)

{

if (touch_has_signal()) //если есть касание

{

if (touch_touched())

{ data->state = LV_INDEV_STATE_PR; //сообщаем библиотеке, что есть касание и удерживается

//Отправка координат

data->point.x = touch_last_x; //координата касания по X

data->point.y = touch_last_y; //координата касания по Y

}

else if (touch_released())

{

data->state = LV_INDEV_STATE_REL; //сообщаем библиотеке, что касания больше нет

}

}

else

{

data->state = LV_INDEV_STATE_REL;

}

}

//Функция обработки нажатия экранной кнопки

static void event_handler_btn(lv_event_t * event){ static uint32_t cnt = 1; lv_obj_t * btn = lv_event_get_target(event);

lv_obj_t * label = lv_obj_get_child(btn, 0);

lv_label_set_text_fmt(label, "%"LV_PRIu32, cnt); cnt++;

}

void setup()

{

Serial.begin( 115200 ); //открытие серийного порта

touch_init(); //иницилизация тача

lv_init();//инициализация LVGL

//инициализация дисплея в библиотеке TFT_ESPi и изменение его ориентации на альбомную

tft.begin();

tft.setRotation(1);

//Далее идут функции настройки LVGL

lv_disp_draw_buf_init( &draw_buf, buf, NULL, screenWidth * screenHeight / 6 ); //создаем буфер для вывода информации на экран

//далее идет настройка параметров экрана

static lv_disp_drv_t disp_drv; //объявляем переменную для хранения драйвера дисплея

lv_disp_drv_init( &disp_drv ); //базовая инициализация драйвера

disp_drv.hor_res = screenWidth; //ширина экрана

disp_drv.ver_res = screenHeight; //высота экрана

disp_drv.flush_cb = my_disp_flush; //функция которая выводит содержимое буфера в заданное место экрана. Указываем имя функции которую мы написали выше

disp_drv.draw_buf = &draw_buf; //объявляем библиотеке, что содержимое буфера экрана находится в переменной draw_buf

lv_disp_drv_register( &disp_drv ); //регистрируем драйвер дисплея и сохранем его настройки

// Инициализируем драйвер тачскрина

static lv_indev_drv_t indev_drv; //объявляем переменные для хранения драйвера тачскрина

lv_indev_drv_init( &indev_drv ); // базовая инициализация драйвера

indev_drv.type = LV_INDEV_TYPE_POINTER; //указываем тип драйвера. В данном случае это тачскрин

indev_drv.read_cb = my_touchpad_read; //указываем имя функции обработчика нажатий на тачскрин, которую мы создали

lv_indev_drv_register( &indev_drv ); //регистрация драйвера тачскрина и сохранение его настроек

//Создаем экранные объекты

lv_obj_t * screenMain = lv_obj_create(NULL); //создаем экранный объект, который будет содержать все другие объекты

//Создадим объект надпись и опишем его свойства

label = lv_label_create(screenMain); //создаем объект Надпись как дочерний объект

screenMain lv_label_set_long_mode(label, LV_LABEL_LONG_WRAP); //текст можно переносить по строкам если не вмещается

lv_label_set_text(label, "Press the button!"); //сам текст для надписи

lv_obj_set_size(label, 240, 40); //размеры надписи

lv_obj_set_pos(label, 0, 15); //положение на экране

//создадим объект Кнопка

btn1 = lv_btn_create(screenMain); //создаем объект Кнопка как дочерний объект

screenMain lv_obj_add_event_cb(btn1, event_handler_btn, LV_EVENT_CLICKED, NULL); //функция, которая вызывается при нажатии на кнопку

lv_obj_set_width(btn1, 120); //ширина lv_obj_set_height(btn1, 30); //высота lv_obj_set_pos(btn1,10, 40); //положение

//далее создадим надпись на кнопке

lv_obj_t * label1 = lv_label_create(btn1); //создаем объект Надпись как дочерний объект созданной ранее кнопки

lv_label_set_text(label1, "Press me!"); //Надпись на кнопке

// далее выводим все на экран

lv_scr_load(screenMain);

}

void loop()

{ //функция обновления экрана и параметров LVGL

lv_timer_handler(); delay( 10 );

}

Затем нажмите в Arduino IDE комбинацию клавиш CTRL+SHIFT+N и в появившемся поле введите touch.h:

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

После прошивки вашего устройства на экране вы увидите следующее:

После нескольких нажатий на кнопку значение счетчика изменится:

Объяснение кода программы

Я постарался максимально подробно закомментировать данный скетч, но на некоторые моменты все же стоит обратить ваше внимание.

Прежде всего это критически важная для работы библиотеки функция my_disp_flush.

Она выводит содержимое буфера в заданные координаты экрана:

void my_disp_flush( lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p )

{

uint32_t w = ( area->x2 - area->x1 + 1 );

uint32_t h = ( area->y2 - area->y1 + 1 ); tft.startWrite();

tft.setAddrWindow( area->x1, area->y1, w, h );

tft.pushColors( ( uint16_t * )&color_p->full, w * h, true );

tft.endWrite();

lv_disp_flush_ready( disp );

}

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

Это позволяет достичь высокой скорости отрисовки и плавности анимации интерфейса.

В данном примере функция отрисовки из буфера адаптирована для библиотеки tft_eSPI.

Для Adafruit она выглядит несколько иначе:

void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p)

{

uint32_t w = (area->x2 - area->x1 + 1);

uint32_t h = (area->y2 - area->y1 + 1); uint32_t wh = w*h; tft.startWrite(); tft.setAddrWindow(area->x1, area->y1, w, h);

while (wh--) tft.pushColor(color_p++->full); tft.endWrite();

lv_disp_flush_ready(disp);

}

Переменные “w”, “h” и “wh” упрощают использование функций s, потому что они должны передаваться переменными с такими значениями.

Следующая функция “tft.startWrite()” инициирует процесс записи данных на ЖК-дисплей.

Затем “tft.setaddr Window(area-> x1, area-> y1, w, h)” записывает данные на ЖК-дисплей, который начинается с “x1”, “y1” и имеет размеры “w“ и ”h”.

После этого цикл записывает все данные с помощью функции “tft.pushColor(color_p++-> full)”.

Наконец, “tft.EndWrite()” завершает запись данных.

В конце “lv_disp_flush_ready (disp)” информирует LVGL о том, что мы записали необходимые данные.

Теперь перейдем к функции определения координат касания тачскрина:

// Вычисление координат касания

void my_touchpad_read(lv_indev_drv_t *indev_driver, lv_indev_data_t *data)

{

if (touch_has_signal()) //если есть касание

{ if (touch_touched())

{

data->state = LV_INDEV_STATE_PR; //сообщаем библиотеке, что есть касание и удерживается

//Отправка координат

data->point.x = touch_last_x; //координата касания по X

data->point.y = touch_last_y; //координата касания по Y

}

else if (touch_released())

{

data->state = LV_INDEV_STATE_REL; //сообщаем библиотеке, что касания больше нет

}

}

else { data->state = LV_INDEV_STATE_REL;

}

}

Здесь мы с помощью функции “touch_touched()” определяем было ли касание.

Если было, то устанавливаем состояние “Нажато” и сохраняются последние координаты касания.

При отпускании устанавливается состояние “Отпущено” и происходит вызод из функции.

Далее в Setup() настраиваем непосредственно параметры

LVGLlv_disp_draw_buf_init( &draw_buf, buf, NULL, screenWidth * screenHeight / 6 ); //создаем буфер для вывода информации на экран

//далее идет настройка параметров экрана

static lv_disp_drv_t disp_drv; //объявляем переменную для хранения драйвера дисплея

lv_disp_drv_init( &disp_drv ); //базовая инициализация драйвера

disp_drv.hor_res = screenWidth; //ширина экрана

disp_drv.ver_res = screenHeight; //высота экрана

disp_drv.flush_cb = my_disp_flush; //функция которая выводит содержимое буфера в заданное место экрана. Указываем имя функции которую мы написали выше

disp_drv.draw_buf = &draw_buf; //объявляем библиотеке, что содержимое буфера экрана находится в переменной

draw_buf lv_disp_drv_register( &disp_drv ); //регистрируем драйвер дисплея и сохранем его настройки

// Инициализируем драйвер тачскрина

static lv_indev_drv_t indev_drv; //объявляем переменные для хранения драйвера тачскрина

lv_indev_drv_init( &indev_drv ); // базовая инициализация драйвера

indev_drv.type = LV_INDEV_TYPE_POINTER; //указываем тип драйвера. В данном случае это тачскрин

indev_drv.read_cb = my_touchpad_read; //указываем имя функции обработчика нажатий на тачскрин, которую мы создали

lv_indev_drv_register( &indev_drv ); //регистрация драйвера тачскрина и сохранение его настроек

Здесь вы можете найти ссылки на функции “my_disp_flush” и “my_input_read”, которые мы ранее написали.

Когда все будет инициализировано, нам нужно создать объект “Экран”.

lv_obj_t * screenMain = lv_obj_create(NULL); //создаем экранный объект, который будет содержать все другие объекты

Это будет родительский объект для всех остальных элементов интерфейса.

Если вы скроете “screenMain”, то все дочерние объекты также будут скрыты.

Это удобное поведение, когда вам нужно переключаться между экранами.

далее создаем элементы интерфейса. первым будет объект “Надпись”:

//Создадим объект надпись и опишем его свойства

label = lv_label_create(screenMain); //создаем объект Надпись как дочерний объект

screenMain lv_label_set_long_mode(label, LV_LABEL_LONG_WRAP); //текст можно переносить по строкам если не вмещается

lv_label_set_text(label, "Press the button!"); //сам текст для надписи

lv_obj_set_size(label, 240, 40); //размеры надписи

lv_obj_set_pos(label, 0, 15); //положение на экране

Она возникла как дочерний объект «screenMain». Кроме того, заданы некоторые параметры-обозначения, такие как перенос строк, если текст не вмещается в границу исходного объекта, текстовые обозначения, размер и положение.

Теперь создадим кнопку:

btn1 = lv_btn_create(screenMain); //создаем объект Кнопка как дочерний объект screenMain

lv_obj_add_event_cb(btn1, event_handler_btn, LV_EVENT_CLICKED, NULL); //функция, которая вызывается при нажатии на кнопку

lv_obj_set_width(btn1, 120); //ширина

lv_obj_set_height(btn1, 30); //высота

lv_obj_set_pos(btn1,10, 40); //положение

Она тоже является дочерним элементом «screenMain». 

Далее идут несколько строк кода, которые задают изменение положения и положения кнопки. Также есть строка, которая задает функцию обработки событий, но об этом поговорим чуть позже.

Добавим надпись на кнопку:

//далее создадим надпись на кнопке

lv_obj_t * label1 = lv_label_create(btn1); //создаем объект Надпись как дочерний объект созданной ранее кнопки

lv_label_set_text(label1, "Press me!"); //Надпись на кнопке

Надпись является дочерним элементом «btn1», а не только экраном. Это означает, что при перемещении кнопки надпись также перемещается. Поэтому вам не нужно сохранять положение надписей.

Далее идет функция отображения экрана:

lv_scr_load(screenMain);

Она просто выводит на дисплей критерий созданного экрана нашего объекта.

Для динамического отображения яркости экрана и обработки касаний в цикле() дополните эту функцию:

lv_timer_handler(); delay( 10 );

И, наконец, добавим функцию-обработчик нажатия на кнопку:

//Функция обработки нажатия экранной кнопки

static void event_handler_btn(lv_event_t * event){ static uint32_t cnt = 1; lv_obj_t * btn = lv_event_get_target(event);

lv_obj_t * label = lv_obj_get_child(btn, 0);

lv_label_set_text_fmt(label, "%"LV_PRIu32, cnt); cnt++;

}

Она при возникновении события «Нажатие кнопки» она создает переменный счетчик, затем создает объект «Надпись», родителем которого является объект, вызвавший эту функцию, после чего устанавливает ему свойство «Текст» равного счетчика следования.

На этом разбор кода программы можно считать завершенным.

Заключение

В заключении можно отметить, что библиотека LVGL предоставляет широкие возможности для создания качественных и функциональных графических интерфейсов на устройствах Arduino и ESP32. 

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

Изучение библиотеки LVGL. Создание красивых интерфейсов с помощью стилей

Введение

Эта статья является частью цикла материалов о библиотеке графического интерфейса пользователя LVGL. Ранее мы изучили базовые свойства объектов в LVGL, а в данной статье мы сосредоточимся на изучении стилей. Стили являются ключевым аспектом создания удобного и привлекательного пользовательского интерфейса, и мы рассмотрим различные доступные варианты стилей в LVGL, а также способы настройки и создания собственных стилей.

Теоретическая часть

В этой теоретической части статьи мы разберемся что представляют из себя стили в библиотеке LVGL и немного поговорим об особенностях их применения. 

Краткая информация о стилях LVGL

Стили в библиотека LVGL во многом напоминают CSS. Это означает, что вам нужно создать объект стиля, задать его параметры, такие как текст, граница, линия, размеры тени, цвета и т.д. После создания объекта стиля вы должны применить его к одному или нескольким объектам (меткам, кнопкам, переключателям и т.д.). Общая концепция заключается в следующем:

  • Стиль – это lv_style_t переменная, которая может содержать такие свойства, как ширина границы, цвет текста и так далее. Это похоже на class в CSS.
  • Объектам могут быть назначены стили для изменения их внешнего вида. При назначении может быть указана целевая часть (псевдоэлемент в CSS) и целевое состояние (псевдокласс). Например, можно добавить style_blue к ручке ползунка, когда она находится в нажатом состоянии.
  • Один и тот же стиль может использоваться любым количеством объектов.
  • Стили могут быть каскадными, что означает, что объекту может быть присвоено несколько стилей, и каждый стиль может иметь разные свойства. Следовательно, не все свойства должны быть указаны в стиле. LVGL будет искать свойство до тех пор, пока его не определит стиль, или использовать значение по умолчанию, если оно не указано ни в одном из стилей. Например, style_btn может привести к серой кнопке по умолчанию и style_btn_red может добавить только background-color=red, чтобы перезаписать цвет фона.
  • Самый последний добавленный стиль имеет более высокий приоритет. Это означает, что если свойство указано в двух стилях, то будет использоваться самый новый стиль в объекте.
  • Некоторые свойства (например, цвет текста) могут быть унаследованы от родительского элемента (ов), если он не указан в объекте.
  • Объекты также могут иметь локальные стили с более высоким приоритетом, чем “обычные” стили.
  • В отличие от CSS (где псевдоклассы описывают разные состояния, например :focus), в LVGL свойство присваивается заданному состоянию.
  • Переходы могут применяться, когда объект меняет состояние.

Состояния

Любой объект в LVGL в каждый определенный момент времени может находится в одном или сразу нескольких состояниях:

  • LV_STATE_DEFAULT (0x0000) Нормальное состояние
  • LV_STATE_CHECKED (0x0001) Переключенное состояние. Актуально для переключателей (вкл/выкл)
  • LV_STATE_FOCUSED (0x0002) В фокусе. Выделен с помощью клавиатуры, энкодера или нажатия с помощью сенсорной панели / мыши
  • LV_STATE_FOCUS_KEY (0x0004) В фокусе. Выделен с помощью клавиатуры или энкодера, но без помощм сенсорной панели / мыши
  • LV_STATE_EDITED (0x0008) Значение изменяется с помощью энкодера
  • LV_STATE_PRESSED (0x0020) Нажат
  • LV_STATE_SCROLLED (0x0040) Прокручивается
  • LV_STATE_DISABLED (0x0080) Отключенное состояние
  • LV_STATE_USER_1 (0x1000) Пользовательское состояние
  • LV_STATE_USER_2 (0x2000) Пользовательское состояние
  • LV_STATE_USER_3 (0x4000) Пользовательское состояние
  • LV_STATE_USER_4 (0x8000) Пользовательское состояние

Объект может находиться в комбинации состояний. Например одновременная фокусировка и нажатие. Записать это можно так LV_STATE_FOCUSED | LV_STATE_PRESSED.
Каждое состояние имеет собственный приоритет. В списке выше он указан в скобках. При изменении состояния объекта будут применены свойства состояния с большим приоритетом. Например укажем цвет фона для 3 состояний объекта:

  • LV_STATE_DEFAULT: белый
  • LV_STATE_PRESSED: серый
  • LV_STATE_FOCUSED: красный

В обычном состоянии LV_STATE_DEFAULT с приоритетом 0x0000 цвет фона объекта будет белым. При нажатии его состояние изменится на LV_STATE_PRESSED. Его приоритет 0х0020 выше чем приоритет состояния по умолчанию 0х0000. Поэтому цвет фона изменится на серый. При отпускании нажатия объект все еще остается в фокусе и его состояние переключится в LV_STATE_FOCUSED, имеющее приоритет 0x0002. Он выше значения по умолчанию. Поэтому цвет фона объекта изменится на красный.

Пойдем немного дальше. Зададим цвет для комбинации состояний:

LV_STATE_PRESSED | LV_STATE_FOCUSED: зеленый

Теперь при нажатии и удержании на объекте он будет находится сразу в 2 состояниях

LV_STATE_PRESSED и LV_STATE_FOCUSED.

Приоритет будет равен сумме приоритетов этих двух состояний. То есть 0х0020 + 0х0002 = 0х0022. Он выше чем приоритет нажатого состояния 0х0020, поэтому цвет фона изменится на зеленый.

Каскадные стили

Не обязательно устанавливать все свойства в одном стиле. К объекту можно добавить больше стилей и заставить последний добавленный стиль изменять или расширять внешний вид. Например, создайте общий серый стиль кнопок и создайте новый для красных кнопок, где задан только новый цвет фона.

Это очень похоже на CSS, когда используемые классы перечислены следующим образом

 <div class=".btn .btn-red">.

Стили, добавленные позже, имеют приоритет над ранее установленными. Итак, в примере с серой / красной кнопкой, приведенном выше, в первую очередь следует добавить обычный стиль кнопки, а во вторую – красный. Тем не менее, приоритет состояний все еще принимается во внимание. Итак, давайте рассмотрим следующий случай:

  • основной стиль кнопки определяет темно-серый цвет для состояния по умолчанию и светло-серый цвет для нажатого состояния
  • стиль красной кнопки определяет цвет фона как красный только в состоянии по умолчанию

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

Наследование

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

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

Родители будут использовать свое собственное состояние для определения значения.

Например если есть объект Экран с установленным значением Цвет текста зеленый, то создав на нем дочерний объект Кнопка с ее дочерним объектом Надпись, цвет текста для надписи будет унаследован от родительского объекта Экран и будет зеленым.

Принудительное наследование значений / значение по умолчанию

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

/* обычный стиль */ lv_style_set_prop_meta(&style, LV_STYLE_TEXT_COLOR, LV_STYLE_PROP_META_INHERIT);

/* локальный стиль */ lv_obj_set_local_style_prop_meta(child,LV_STYLE_PROP_META_INHERIT, LV_PART_MAIN);

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

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

Для этого вы можете использовать тот же API, но с LV_STYLE_PROP_META_INITIAL вместо этого. 

Части объекта

В LVGL существуют следующие предопределенные частей объекта:

  • LV_PART_MAIN Основная часть объекта. Или фон представляющий собой прямоугольник
  • LV_PART_SCROLLBAR Полоса (полосы) прокрутки
  • LV_PART_INDICATOR Индикатор, например, для ползунка, панели, переключателя или флажка чекбокса
  • LV_PART_KNOB Перемещаемая часть ползунка, или так называемая, ручка за которую перетаскивают, чтобы изменить значение
  • LV_PART_SELECTED Выбранный в данный момент параметр или раздел в списках
  • LV_PART_ITEMS Используется, если виджет содержит несколько похожих элементов (например, ячейки таблицы)
  • LV_PART_TICKS Отметки на шкалах, например, для диаграммы или счетчика
  • LV_PART_CURSOR Курсор текстовой области или диаграммы
  • LV_PART_CUSTOM_FIRST Пользовательские части могут быть добавлены здесь

Например, объект Слайдер состоит из трех частей:

  • LV_PART_MAIN – Фон
  • LV_PART_INDICATOR – Индикатор
  • LV_PART_KNOB – Ручка

Программная часть

Немного разобравшись с теорией, самое время узнать как использовать стили LVGL в ваших проектах.

Инициализация стилей и установка / получение свойств

Стили хранятся в lv_style_t переменных.

Переменные стиля должны быть static, глобальными или динамически распределяемыми.

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

Перед использованием стиля его следует инициализировать с помощью 

lv_style_init(&my_style).

После инициализации стиля, можно добавлять или изменять его свойства

Функция установки свойства в общем виде выглядит так:

lv_style_set_(&style, );

Создадим, например, пару стилей для кнопок и добавим им несколько свойств:

static lv_style_t style_btn; //объявляем переменную стиля style_btn
lv_style_init(&style_btn); //Инициализируем стиль
lv_style_set_bg_color(&style_btn, lv_color_hex(0x115588)); //устанавливаем цвет фона введя его значение в HEX 0x115588
lv_style_set_bg_opa(&style_btn, LV_OPA_50); //устанавливаем значение прозрачности фона равным 50%
lv_style_set_border_width(&style_btn, 2); //устанавливаем ширину рамки кнопки равной 2 пикселя
lv_style_set_border_color(&style_btn, lv_color_black()); //устанавливаем цвет рамки кнопки черным с помощью предустановленного значения цвета библиотеки
static lv_style_t style_btn_red; //объявляем новую переменную стиля style_btn_red
lv_style_init(&style_btn_red); //инициализируем её
lv_style_set_bg_color(&style_btn_red, lv_palette_main(LV_PALETTE_RED)); //устанавливаем цвет фона красный с помощью выбора предустановленного цвета из палитры LVGL
lv_style_set_bg_opa(&style_btn_red, LV_OPA_COVER); //устанавливаем полностью непрозрачный цвет фона

Чтобы удалить свойство, используйте:

lv_style_remove_prop(&style, LV_STYLE_BG_COLOR);

Чтобы получить значение свойства из стиля:

lv_style_value_t v;
lv_res_t res = lv_style_get_prop(&style, LV_STYLE_BG_COLOR, &v);
if(res == LV_RES_OK) { /Found/
do_something(v.color);
}

lv_style_value_t содержит 3 поля:

  • num для свойств целых чисел, логических значений и непрозрачности
  • color для свойств цвета
  • ptr для свойств указателя

Чтобы сбросить стиль (удалить все его данные), используйте:

lv_style_reset(&style);

Для экономии памяти стили могут быть объявлены как константы:

const lv_style_const_prop_t style1_props[] = { LV_STYLE_CONST_WIDTH(50), LV_STYLE_CONST_HEIGHT(50), LV_STYLE_CONST_PROPS_END }; LV_STYLE_CONST_INIT(style1, style1_props);

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

Добавление и удаления стиля объекта

Чтобы использовать стиль его нужно добавить к объекту. Для этого используется функция:

lv_obj_add_style(obj, &style, <selector>).

selector – это упорядоченное значение частей и состояния, к которым должен быть добавлен стиль. Например:

  • LV_PART_MAIN | LV_STATE_DEFAULT – Основная часть объекта в состоянии по умолчанию
  • LV_STATE_PRESSED: Основная часть объекта в нажатом состоянии. LV_PART_MAIN может быть опущен
  • LV_PART_SCROLLBAR: Полоса прокрутки в состоянии по умолчанию. LV_STATE_DEFAULT может быть опущен.
  • LV_PART_SCROLLBAR | LV_STATE_SCROLLED: Полоса прокрутки при прокрутке объекта
  • 0 То же, что LV_PART_MAIN | LV_STATE_DEFAULT.
  • LV_PART_INDICATOR | LV_STATE_PRESSED | LV_STATE_CHECKED Часть индикатора, когда объект одновременно нажимается и включен (например стоит галка в чекбоксе).

Например:

lv_obj_add_style(btn, &style_btn, 0); // Стиль кнопки по умолчанию lv_obj_add_style(btn, &btn_red, LV_STATE_PRESSED); // стиль кнопки при нажатии на нее

Удаление стиля объекта

Чтобы удалить все стили из объекта, используйте lv_obj_remove_style_all(obj).

Для удаления определенных стилей используйте lv_obj_remove_style(obj, style, selector). Эта функция удалит, style только если selector совпадет с selector используемым в lv_obj_add_style

Вместо значения style можно использовать NULL и проверить только selector, удалив при этом все совпадающие стили. 

В качестве selector можно использовать значения LV_STATE_ANY и LV_PART_ANY для удаления стиля из любого состояния или части.

Уведомление об изменении стиля

Если стиль, который уже присвоен объекту, изменяется (то есть добавляется или изменяется свойство), объекты, использующие этот стиль, должны быть уведомлены. Для этого есть 3 варианта:

  • Если вы знаете, что измененные свойства могут быть применены простым перерисовыванием (например, изменением цвета или непрозрачности), просто вызовите lv_obj_invalidate(obj) или lv_obj_invalidate(lv_scr_act()).
  • Если были изменены или добавлены более сложные свойства стиля, и вы знаете, на какие объекты влияет этот вызов стиля lv_obj_refresh_style(obj, part, property). Чтобы обновить все части и свойства, используйте lv_obj_refresh_style(obj, LV_PART_ANY, LV_STYLE_PROP_ANY)
  • Чтобы заставить LVGL проверять все объекты на предмет использования ими стиля и обновлять их при необходимости, вызовите lv_obj_report_style_change(&style). Если style это NULL. При этом все объекты будут уведомлены об изменении стиля.

Получить значение свойства для объекта

Для получения окончательного значения свойства – с учетом каскадирования, наследования, локальных стилей и переходов – можно использовать функции получения свойств, подобные этой:

lv_obj_get_style_<property_name>(obj, <part>)

Эта функции используют текущее состояние объекта, и если лучшего кандидата не существует, они возвращают значение по умолчанию. Например:

lv_color_t color = lv_obj_get_style_bg_color(btn, LV_PART_MAIN);

Локальные стили

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

Эта концепция похожа на встроенные стили в CSS (например, <div style="color:red">) с некоторыми изменениями.

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

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

Это может быть полезно для настройки внешнего вида какого-либо единичного объекта.

Например установить определенный шрифт для отображения часов..

В отличие от CSS, локальные стили LVGL могут быть назначены состояниям (псевдоклассам) и частям (псевдоэлементам).

Чтобы задать локальное свойство, используются такие функции, как 

lv_obj_set_style_<property_name>(obj, <value>, <selector>); 

Например:

lv_obj_set_style_bg_color(slider, lv_color_red(), LV_PART_INDICATOR | LV_STATE_FOCUSED);

Переходы

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

Параметры переходов хранятся в стилях. Можно установить

  • Время перехода
  • Задержка перед началом перехода
  • Путь анимации (также известный как функция синхронизации или смягчения)
  • Свойства для анимации

Свойства перехода могут быть определены для каждого состояния. Например, установка времени перехода в состояние DEFAULT в 500 мс означает, что когда объект переходит в состояние DEFAULT, анимация времени перехода будет занимать 500 мс. Установка времени перехода в  состоянии PRESSED  100 мс означает, что переход в нажатое состояние будет занимать 100 мс. В итоге при нажатии на объект он быстро покажет анимацию перехода в нажатое состояние, потом при отпускании кнопки он с плавной анимацией вернется в свое обычное состояние. 

Для описания перехода lv_transition_dsc_t переменную необходимо инициализировать и добавить к стилю:

/* Сохраняется только его указатель, поэтому он должен быть статическим, глобальным или динамически распределяемым */ static const lv_style_prop_t trans_props[] = {LV_STYLE_BG_OPA, LV_STYLE_BG_COLOR,0}; //0 - конечный маркер static lv_style_transition_dsc_t trans1; lv_style_transition_dsc_init(&trans1, trans_props, lv_anim_path_ease_out, duration_ms, delay_ms); lv_style_set_transition(&style1, &trans1);

Прозрачность, режимы наложения и преобразования

Если дляя свойств 

opablend_modetransform_angle или transform_zoom 

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

Эти свойства оказывают такое влияние только на MAIN часть виджета.

Созданный снимок называется “промежуточный слой” или просто “layer”.

Если для параметра только opa  и/или blend_mode установлено значение, отличное от значения по умолчанию, LVGL может создавать слой из меньших фрагментов.

Размер этих фрагментов может быть настроен с помощью следующих свойств в lv_conf.h:

  • LV_LAYER_SIMPLE_BUF_SIZE: [байты] оптимальный размер целевого буфера. LVGL попытается выделить этот объем памяти.
  • LV_LAYER_SIMPLE_FALLBACK_BUF_SIZE: [байты], используемые, если LV_LAYER_SIMPLE_BUF_SIZE не удалось выделить.

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

Требуемый объем память зависит от параметров угла, масштабирования и поворота, а также размера области для перерисовки, но она никогда не должна превышать размер виджета (включая дополнительный размер прорисовки, используемый для тени, контура и т.д.).

Если виджет может полностью покрывать область для перерисовки, LVGL создает слой RGB (который быстрее визуализируется и использует меньше памяти).

В противном случае необходимо использовать рендеринг ARGB.

Виджет может не охватывать свою область, если он имеет радиус, bg_opa != 255, имеет тень, контур и т.д.

Область клика виджета также соответствующим образом преобразовывается.

Темы

Темы представляют собой набор стилей. При наличии активной темы LVGL применяет ее к каждому созданному виджету. То есть задает интерфейсу вид по умолчанию, который затем может быть изменен путем добавления дополнительных стилей.

У каждого дисплея может быть своя тема.

Например, у вас может быть цветная тема на TFT-дисплее и монохромная тема на дополнительном монохромном дисплее.

Чтобы задать тему для отображения, требуется выполнить два шага:

  1. Инициализировать тему
  2. Назначить экрану инициализированную тему.

Функции инициализации темы могут иметь разные прототипы.

В этом примере показано, как установить тему “по умолчанию”:

lv_theme_t * th = lv_theme_default_init(display, /* Использовать DPI, размер и т.д. этого дисплея */ LV_COLOR_PALETTE_BLUE, LV_COLOR_PALETTE_CYAN, / * Основная и дополнительная палитры * / false, / * Светлый (false) или темный (true) режим */ & lv_font_montserrat_10, &lv_font_montserrat_14, &lv_font_montserrat_18); /*Мелкий, обычный, крупный шрифты */ lv_disp_set_theme(display, th); /*Назначить тему для дисплея */

Встроенные темы активированы в lv_conf.h.

Если тема по умолчанию включена LV_USE_THEME_DEFAULT 1, LVGL автоматически инициализирует и устанавливает ее при создании дисплея.

Расширение тем

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

Таким образом можно объединить любое количество тем. Например. тема по умолчанию -> пользовательская тема -> темная тема.

lv_theme_set_parent(new_theme, base_theme) расширяет base_theme с помощью new_theme .

Практическая часть

Настало время перейти к практической части.

Возьмем пример из статьи Начало работы с библиотекой LVGL. Первые шаги в создании графических интерфейсов и внесем в него некоторые изменения.

Создадим новые стили для кнопки и надписи.

Для этого добавим следующий код:

static lv_style_t style1; //объявляем переменную стиля lv_style_init(&style1); //инициализируем стиль lv_style_set_text_color(&style1, lv_palette_main(LV_PALETTE_GREEN)); //цвет текста зеленый lv_style_set_border_color(&style1, lv_palette_main(LV_PALETTE_RED)); //цвет границы красный lv_style_set_border_width(&style1, 2); //ширина границы 2 пикселя lv_style_set_bg_color(&style1, lv_palette_main(LV_PALETTE_YELLOW)); //цвет фона желтый lv_style_set_bg_opa(&style1, 255); //непрозрачность максимальная lv_style_set_radius(&style1, 1); //радиус закругления углов 1 static lv_style_t bigStyle; //объявляем новую переменную стиля lv_style_init(&bigStyle); //инициализируем ее lv_style_set_text_font(&bigStyle, &lv_font_montserrat_48); //устанавливаем новый шрифт

Мы создали два новых стиля style1 и bigStyle и задали для них некоторые параметры.

В стиле style1 мы добавляем цвет границы (красный) и устанавливаем ее ширину в 2 пикселя, устанавливаем желтый цвет фона и максимальную непрозрачность.

Наконец, мы меняем радиус границы на 1.

В стиле bigStyle мы просто изменим шрифт по умолчанию на другой чуть большего размера.

Теперь добавим созданные стили к имеющимся объектам btn1 и labellv_obj_add_style(label, &bigStyle,0); lv_obj_add_style(btn1, &style1,0);

В данном случае мы просто воспользовались упрощенной записью функции для добавления стиля к основной части объекта для состояния по умолчанию.

Напомню что до применения стилей объекты на экране выглядели так:

После применения стилей вид элементов стал таким:

Таким образом вы можете изменить внешний вид любого элемента интерфейса.

И, наконец, установим темную тему экрана. Для этого объявим переменную дисплея

disp типа lv_disp_t и зарегистрируем драйвер дисплея для работы с ним:

lv_disp_t * disp = lv_disp_drv_register( &disp_drv ); //регистрируем драйвер дисплея и сохраним его настройки

Теперь инициализируем стандартную тему и назначим её дисплею:

lv_theme_t * th = lv_theme_default_init( disp,lv_palette_main(LV_PALETTE_BLUE), lv_palette_main(LV_PALETTE_CYAN),true,&lv_font_montserrat_14); //инициализация стандартной темы в темном режиме lv_disp_set_theme(disp, th); //Назначить тему для дисплея

После загрузки программы в микроконтроллер экран примет такой вид:

Полный текст программы представлен ниже

#include <lvgl.h>

#include <TFT_eSPI.h>

#include “touch.h”

static const uint16_t screenWidth = 480; //ширина экрана
static const uint16_t screenHeight = 320; //высота экрана

//Объявление служебных переменных для LVGL
static lv_disp_draw_buf_t draw_buf;
static lv_color_t buf[screenWidth * screenHeight / 6];
lv_obj_t * btn1;
lv_obj_t * screenMain;
lv_obj_t * label;

TFT_eSPI tft = TFT_eSPI(); //инициализация библиотеки TFT_eSPI

//Здесь напишем функцию для вывода содержимого буфера на экран
void my_disp_flush( lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p )
{
uint32_t w = ( area->x2 – area->x1 + 1 );
uint32_t h = ( area->y2 – area->y1 + 1 );

tft.startWrite();
tft.setAddrWindow( area->x1, area->y1, w, h );
tft.pushColors( ( uint16_t * )&color_p->full, w * h, true );
tft.endWrite();

lv_disp_flush_ready( disp );
}
// Вычисление координат касания
void my_touchpad_read(lv_indev_drv_t *indev_driver, lv_indev_data_t *data)
{
if (touch_has_signal()) //если есть касание
{
if (touch_touched())
{
data->state = LV_INDEV_STATE_PR; //сообщаем библиотеке, что есть касание и удерживается

//Отправка координат
data->point.x = touch_last_x; //координата касания по X
data->point.y = touch_last_y; //координата касания по Y
}
else if (touch_released())
{
data->state = LV_INDEV_STATE_REL; //сообщаем библиотеке, что касания больше нет
}
}
else
{
data->state = LV_INDEV_STATE_REL;
}
}
//Функция обработки нажатия экранной кнопки
static void event_handler_btn(lv_event_t * event){
static uint32_t cnt = 1;
lv_obj_t * btn = lv_event_get_target(event);
lv_obj_t * label = lv_obj_get_child(btn, 0);
lv_label_set_text_fmt(label, “%”LV_PRIu32, cnt);
cnt++;
}

void setup()
{
Serial.begin( 115200 ); //открытие серийного порта
touch_init(); //иницилизация тача
lv_init();//инициализация LVGL

//инициализация дисплея в библиотеке TFT_ESPi и изменение его ориентации на альбомную
tft.begin();
tft.setRotation(1);
//Далее идут функции настройки LVGL
lv_disp_draw_buf_init( &draw_buf, buf, NULL, screenWidth * screenHeight / 6 ); //создаем буфер для вывода информации на экран

//далее идет настройка параметров экрана
static lv_disp_drv_t disp_drv; //объявляем переменную для хранения драйвера дисплея
lv_disp_drv_init( &disp_drv ); //базовая инициализация драйвера
disp_drv.hor_res = screenWidth; //ширина экрана
disp_drv.ver_res = screenHeight; //высота экрана
disp_drv.flush_cb = my_disp_flush; //функция которая выводит содержимое буфера в заданное место экрана. Указываем имя функции которую мы написали выше
disp_drv.draw_buf = &draw_buf; //объявляем библиотеке, что содержимое буфера экрана находится в переменной draw_buf
lv_disp_t * disp = lv_disp_drv_register( &disp_drv ); //регистрируем драйвер дисплея и сохраняем его настройки
// Инициализируем драйвер тачскрина
static lv_indev_drv_t indev_drv; //объявляем переменные для хранения драйвера тачскрина
lv_indev_drv_init( &indev_drv ); // базовая инициализация драйвера
indev_drv.type = LV_INDEV_TYPE_POINTER; //указываем тип драйвера. В данном случае это тачскрин
indev_drv.read_cb = my_touchpad_read; //указываем имя функции обработчика нажатий на тачскрин, которую мы создали
lv_indev_drv_register( &indev_drv ); //регистрация драйвера тачскрина и сохранение его настроек

lv_theme_t * th = lv_theme_default_init( disp,lv_palette_main(LV_PALETTE_BLUE), lv_palette_main(LV_PALETTE_CYAN),true,&lv_font_montserrat_14); //инициализация стандартной темы в темном режиме

lv_disp_set_theme(disp, th); //Назначить тему для дисплея

static lv_style_t style1; //объявляем переменную стиля
lv_style_init(&style1); //инициализируем стиль
lv_style_set_text_color(&style1, lv_palette_main(LV_PALETTE_GREEN)); //цвет текста зеленый
lv_style_set_border_color(&style1, lv_palette_main(LV_PALETTE_RED)); //цвет границы красный
lv_style_set_border_width(&style1, 2); //ширина границы 2 пикселя
lv_style_set_bg_color(&style1, lv_palette_main(LV_PALETTE_YELLOW)); //цвет фона желтый
lv_style_set_bg_opa(&style1, 255); //непрозрачность максимальная
lv_style_set_radius(&style1, 1); //радиус закругления углов 1

static lv_style_t bigStyle; //объявляем новую переменную стиля
lv_style_init(&bigStyle); //инициализируем ее
lv_style_set_text_font(&bigStyle, &lv_font_montserrat_48); //устанавливаем новый шрифт

//Создаем экранные объекты
lv_obj_t * screenMain = lv_obj_create(NULL); //создаем экранный объект, который будет содержать все другие объекты
//Создадим объект надпись и опишем его свойства
label = lv_label_create(screenMain); //создаем объект Надпись как дочерний объект screenMain
lv_label_set_long_mode(label, LV_LABEL_LONG_WRAP); //текст можно переносить по строкам если не вмещается
lv_label_set_text(label, “Press the button!”); //сам текст для надписи
lv_obj_set_size(label, 460, 80); //размеры надписи
lv_obj_set_pos(label, 0, 15); //положение на экране
lv_obj_add_style(label, &bigStyle,0);
//создадим объект Кнопка
btn1 = lv_btn_create(screenMain); //создаем объект Кнопка как дочерний объект screenMain
lv_obj_add_event_cb(btn1, event_handler_btn, LV_EVENT_CLICKED, NULL); //функция, которая вызывается при нажатии на кнопку
lv_obj_set_width(btn1, 120); //ширина
lv_obj_set_height(btn1, 30); //высота
lv_obj_set_pos(btn1,10, 80); //положение
//далее создадим надпись на кнопке
lv_obj_t * label1 = lv_label_create(btn1); //создаем объект Надпись как дочерний объект созданной ранее кнопки
lv_label_set_text(label1, “Press me!”); //Надпись на кнопке
// далее выводим все на экран
lv_obj_add_style(btn1, &style1,0);
lv_scr_load(screenMain);

}

void loop()
{
//функция обновления экрана и параметров LVGL
lv_timer_handler();
delay( 10 );
}

Заключение

В этой статье мы рассмотрели важность использования стилей для создания привлекательных и функциональных пользовательских интерфейсов в библиотеке LVGL. Мы изучили различные типы стилей, доступных в LVGL, и научились создавать свои собственные стили. Теперь вы знаете, как правильно применять стили к различным элементам пользовательского интерфейса, чтобы создать привлекательный и функциональный интерфейс для вашего приложения. Мы надеемся, что эта статья поможет вам улучшить свои навыки работы со стилями в LVGL и создать более качественный пользовательский интерфейс для вашего проекта.

Изучение библиотеки LVGL. Свойства стилей

Введение

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

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

Мы также изучили, как создавать стили и применять их к объектам для достижения различных эффектов и визуальных изменений.

В этой статье мы сосредоточимся на более подробном изучении свойств стилей в библиотеке LVGL.

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

Изучение свойств стилей позволит нам создавать красивые и интуитивно понятные пользовательские интерфейсы с помощью библиотеки LVGL. Мы научимся использовать эти свойства для создания эффектов, выделения элементов, подсветки и других интересных визуальных эффектов.

Размер и положение

В данном разделе собраны свойства, связанные с размером, положением, выравниванием и компоновкой объектов.

width

Задает ширину объекта. Могут использоваться пиксельные, процентные и LV_SIZE_CONTENT значения. Процентные значения определяются в зависимости от ширины родительского объекта.

min_width

Задает минимальную ширину. Могут использоваться значения в пикселях и процентах. Процентные значения определяются в зависимости от ширины родительского объекта. По умолчанию параметр равен 0.

max_width

Задает максимальную ширину. Могут использоваться значения в пикселях и процентах. Процентные значения определяются в зависимости от ширины родительского объекта. По умолчанию параметр равен LV_COORD_MAX, определяемому на стадии инициализации библиотеки.

height

Задает высоту объекта. Могут использоваться пиксельные, процентные и LV_SIZE_CONTENT значения. Процентные значения определяются в зависимости от высоты родительского объекта.

min_height

Задает минимальную высоту. Могут использоваться значения в пикселях и процентах. Процентные значения определяются в зависимости от высоты родительского объекта. По умолчанию параметр равен 0.

max_height

Задает максимальную высоту. Могут использоваться значения в пикселях и процентах. Процентные значения определяются в зависимости от высоты родительского объекта. По умолчанию параметр равен LV_COORD_MAX, определяемому на стадии инициализации библиотеки.

x

Задаёт координату X объекта с учетом свойства align. Можно использовать значения в пикселях и процентах. Процентные значения определяются в зависимости от ширины родительского объекта. Значение по умолчанию 0.

y

Задаёт координату Y объекта с учетом свойства align. Можно использовать значения в пикселях и процентах. Процентные значения определяются в зависимости от высоты родительского объекта. Значение по умолчанию 0.

align

Установка выравнивания объекта, которое указывает, из какой точки родительского элемента следует интерпретировать координаты X и Y. 

Возможными значениями являются: 

  • LV_ALIGN_DEFAULT
  • LV_ALIGN_TOP_LEFT/MID/RIGHT
  • LV_ALIGN_BOTTOM_LEFT/MID/RIGHT
  • LV_ALIGN_LEFT/RIGHT_MID
  • LV_ALIGN_CENTER

При установке значений LV_ALIGN_DEFAULT, LV_ALIGN_TOP(MID, BOTTOM)_LEFT(MID) отсчет координат объекта идет слева направо. При установке LV_ALIGN_TOP_RIGHT и похожих –  справа налево.

Значение параметра по умолчанию LV_ALIGN_DEFAULT.

transform_width

Расширяет объект с обеих сторон сторон. Можно использовать значения в пикселях и процентах (с помощью функции lv_pct(x)). Процентные значения относятся к ширине объекта. Значение по умолчанию 0.

transform_height

Увеличивает высоту объекта с обеих сторон. Можно использовать значения в пикселях и процентах (с помощью функции lv_pct(x)). Процентные значения относятся к высоте объекта. Значение по умолчанию 0.

translate_x

Переместить объект на указанное значение в направлении X. Применяется после разметки, выравнивания и другого позиционирования. Могут использоваться значения в пикселях и процентах (с lv_pct(x)). Процентные значения относятся к ширине объекта. Значение по умолчанию 0.

translate_y

Переместить объект на указанное значение в направлении Y. Применяется после разметки, выравнивания и другого позиционирования. Могут использоваться значения в пикселях и процентах (с lv_pct(x)). Процентные значения относятся к высоте объекта. Значение по умолчанию 0.

transform_zoom

Увеличение объекта. Значение 256 (или LV_IMG_ZOOM_NONE) означает обычный размер, 128 половинный размер, 512 двойной размер и так далее. Значение по умолчанию 0.

transform_angle

Поворот объекта. Значение интерпретируется в единицах 0,1 градуса. Например, 450 означает 45 градусов. Значение по умолчанию 0.

transform_pivot_x

Задать координату X точки поворота для преобразований. Относительно верхнего левого угла объекта.

transform_pivot_y

Задать координату Y точки поворота для преобразований. Относительно верхнего левого угла объекта.

Пример использования

Разберем  использование данных свойств с помощью небольшого примера. В нем мы создадим на экране объект Метка с текстом. Далее с помощью создания нового стиля изменим его размеры, зададим выравнивание по центру экрана и повернем на 60 градусов относительно заданной точки.

void transform_style_demo()
{
 static lv_style_t style;
 lv_style_init(&style);//Инициализация стиля
 lv_style_set_width(&style,120); // Устанавливаем ширину объекта
 lv_style_set_height(&style,100); // Устанавливаем высоту объекта
 lv_style_set_align(&style, LV_ALIGN_CENTER);//Выравниваем объект по центру
 lv_style_set_transform_zoom(&style, 512); //Увеличиваем объект в 2 раза
 lv_style_set_transform_pivot_x(&style,20); //Смещаем центр преобразования объекта по Х
 lv_style_set_transform_pivot_y(&style,20); //Смещаем центр преобразования объекта по У
 lv_style_set_transform_angle(&style,600); //поворачиваем объект на 60 градусов относительно указанного центра преобразования
 lv_obj_t * lbl=lv_label_create(lv_scr_act()); //Создаем объект Label
 lv_label_set_text(lbl,"test label"); //Вводим текст для метки
 lv_obj_remove_style_all(lbl); //Удаляем все ранее присовенные стили объекта
 lv_obj_add_style(lbl, &style, 0); //Присваиваем ему новый стиль
}

Результат выполнения программы представлен на скриншоте ниже:

Отступы

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

pad_top

Устанавливает отступ сверху. Это уменьшает область содержимого в этом направлении. Значение по умолчанию 0.

pad_bottom

Устанавливает отступ снизу. Это уменьшает область содержимого в этом направлении. Значение по умолчанию 0.

pad_left

Устанавливает отступ слева. Это уменьшает область содержимого в этом направлении. Значение по умолчанию 0.

pad_right

Устанавливает отступ справа. Это уменьшает область содержимого в этом направлении. Значение по умолчанию 0.

pad_row

Задает отступ между строками. Используется макетами.

pad_column

Задает отступ между столбцами. Используется макетами.

Пример использования

В данном примере создадим объект Таблица и зададим  отступы от краев для его дочерних элементов ячеек.

void transform_style_table()

{

static lv_style_t style; lv_style_init(&style);//Инициализация стиля

lv_style_set_pad_top(&style,20); // Отступ сверху

lv_style_set_pad_bottom(&style,20); // Отступ снизу

lv_style_set_pad_left(&style, 15);//Отступ слева

lv_style_set_pad_right(&style, 5); //Отступ справа lv_obj_t *

tab=lv_table_create(lv_scr_act()); //Создаем объект

Table lv_table_set_cell_value(tab,0,0,"Table value 1"); //Вводим значение ячейки таблицы

lv_table_set_cell_value(tab,0,1,"Val 2"); //Вводим значение ячейки таблицы

lv_table_set_cell_value(tab,1,0,"Table Val 3"); //Вводим значение ячейки таблицы

lv_table_set_cell_value(tab,1,1,"Val 4"); //Вводим значение ячейки таблицы

lv_obj_add_style(tab, &style, 0); //Присваиваем таблице новый стиль

}

Результат выполнения программы на скриншоте ниже:

Фон

Набор свойств описывающих фон объекта.

bg_color

Установка цвета фона объекта с помощью hex кода цвета в RGB диапазоне. Значение по умолчанию 0xffffff (белый).
 

bg_opa

Установите непрозрачность фона. Значение 0, LV_OPA_0 или LV_OPA_TRANSP означает “полностью прозрачный”, 255, LV_OPA_100 или LV_OPA_COVER означает “полностью непрозрачный”, другие значения или LV_OPA_10, LV_OPA_20 и т.д. означают полупрозрачность. Значение по умолчанию LV_OPA_TRANSP.

bg_grad_color

Установка градиентного цвета фона. Используется, только если grad_dir не равен LV_GRAD_DIR_NONE. Значение по умолчанию 0x000000.

bg_grad_dir

Задает направление градиента фона. Возможными значениями являются LV_GRAD_DIR_NONE/HOR/VER.

bg_main_stop

Задаёт точку, с которой цвет фона начинает переходить в градиент. 0 означает верхнюю / левую сторону, 255 – нижнюю / правую сторону, 128 – центр и так далее. Значение по умолчанию 0.

bg_grad_stop

Задаёт точку, с которой градиент полностью заменяет собой цвет фона. 0 означает верхнюю / левую сторону, 255 – нижнюю / правую сторону, 128 – центр и так далее. Значение по умолчанию 255.

bg_grad

Задать определение градиента. Указанный экземпляр должен существовать, пока объект активен. Значение NULL для отключения. Он обертывает BG_GRAD_COLORBG_GRAD_DIRBG_MAIN_STOP и BG_GRAD_STOP в один дескриптор и позволяет создавать градиенты с большим количеством цветов.

bg_dither_mode

Установить режим сглаживания градиента фона. Возможными значениями являются LV_DITHER_NONE/ORDERED/ERR_DIFF.

bg_img_src

Задать фоновое изображение. Может быть указателем на lv_img_dsc_t, путем к файлу или LV_SYMBOL_...

bg_img_opa

Установить непрозрачность фонового изображения. Значение 0, LV_OPA_0 или LV_OPA_TRANSP означает “полностью прозрачный”, 255, LV_OPA_100 или LV_OPA_COVER означает “полностью закрывающий”, другие значения или LV_OPA_10, LV_OPA_20 и т.д. означают полупрозрачность. Значение по умолчанию LV_OPA_COVER.

bg_img_recolor

Задать цвет для смешивания с фоновым изображением.

bg_img_recolor_opa

Установить интенсивность перекраски фонового изображения. Значение 0, LV_OPA_0 или LV_OPA_TRANSP означает отсутствие смешивания, 255, LV_OPA_100 или LV_OPA_COVER означает полное перекрашивание, другие значения или LV_OPA_10, LV_OPA_20 и т.д. интерпретируются пропорционально.

bg_img_tiled

Если включено, фоновое изображение будет выложено плиткой. Возможными значениями являются true или false.

Пример использования

Немного модифицируем предыдущий пример.

Зададим градиентный фон для таблицы и создадим новый объект Button с фоновым изображением.

void transform_style_table()

{

static lv_style_t style; lv_style_init(&style);//Инициализация стиля

lv_style_set_pad_top(&style,20); // Отступ сверху

lv_style_set_pad_bottom(&style,20); // Отступ снизу

lv_style_set_pad_left(&style, 15);//Отступ слева

lv_style_set_pad_right(&style, 5); //Отступ справа

lv_style_set_bg_grad_color(&style,lv_color_hex(0xaaaa10));//Задаем цвет градиента

lv_style_set_bg_grad_dir(&style,LV_GRAD_DIR_HOR);//Направление градиента

lv_style_set_bg_grad_stop(&style,200);//начало

градиента lv_style_set_bg_main_stop(&style,50);//

конец градиента static lv_style_t style2;//Объявляем новый стиль для кнопки

lv_style_init(&style2);//Инициализация стиля

lv_style_set_bg_img_src(&style2,LV_SYMBOL_BELL);//фоновое изображение для кнопки

lv_style_set_align(&style2, LV_ALIGN_CENTER);//Выравниваем объект по центру

lv_style_set_transform_zoom(&style2, 1024); //Увеличиваем объект в 2 раза

lv_obj_t * tab=lv_table_create(lv_scr_act()); //Создаем объект

table lv_table_set_cell_value(tab,0,0,"Table value 1"); //Вводим значение ячейки таблицы

lv_table_set_cell_value(tab,0,1,"Val 2"); //Вводим значение ячейки таблицы

lv_table_set_cell_value(tab,1,0,"Table Val 3"); //Вводим значение ячейки таблицы

lv_table_set_cell_value(tab,1,1,"Val 4"); //Вводим значение ячейки таблицы

lv_obj_add_style(tab, &style, 0); //Присваиваем ему новый стиль

lv_obj_t * btn=lv_btn_create(lv_scr_act()); //Создаем объект

button lv_obj_add_style(btn, &style2, 0); //Присваиваем ему новый стиль

}

Результат выполнения программы на скриншоте ниже:

Граница

Набор свойств отвечающих за отрисовку границ объекта.

border_color

Задать цвет границы. Значение по умолчанию 0х000000.

border_opa

Установить непрозрачность границы. Значение 0, LV_OPA_0 или LV_OPA_TRANSP означает “полностью прозрачный”, 255, LV_OPA_100 или LV_OPA_COVER означает “полностью закрывающий”, другие значения или LV_OPA_10, LV_OPA_20 и т.д. означают полупрозрачность. Значение по умолчанию LV_OPA_COVER.

border_width

Задать ширину границы в пикселях.

border_side

Указать с какой стороны ( из сторон) объекта должна быть нарисована граница. Возможными значениями являются LV_BORDER_SIDE_NONE/TOP/BOTTOM/LEFT/RIGHT/INTERNAL. Для указания нескольких сторон можно использовать символ |, например LV_BORDER_SIDE_TOP | LV_BORDER_SIDE_LEFT.

border_post

Задает, следует ли рисовать границу до или после рисования дочерних элементов. true: после дочерних элементов, false: перед дочерними элементами

Пример использования

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

void transform_style_table()

{

static lv_style_t style; lv_style_init(&style);//Инициализация стиля

lv_style_set_pad_top(&style,20); // Отступ сверху

lv_style_set_pad_bottom(&style,20); // Отступ снизу

lv_style_set_pad_left(&style, 15);//Отступ слева

lv_style_set_pad_right(&style, 5); //Отступ справа

lv_style_set_bg_grad_color(&style,lv_color_hex(0xaaaa10));

lv_style_set_bg_grad_dir(&style,LV_GRAD_DIR_HOR);

lv_style_set_bg_grad_stop(&style,200);

lv_style_set_bg_main_stop(&style,50);

lv_style_set_border_width(&style,15); //Ширина границы объекта

lv_style_set_border_side(&style,LV_BORDER_SIDE_BOTTOM | LV_BORDER_SIDE_RIGHT); //Отрисовка правой и нижней границ объекта

lv_obj_t * tab=lv_table_create(lv_scr_act()); //Создаем объект table

lv_table_set_cell_value(tab,0,0,"Table value 1"); //Вводим значение ячейки таблицы

lv_table_set_cell_value(tab,0,1,"Val 2"); //Вводим значение ячейки таблицы

lv_table_set_cell_value(tab,1,0,"Table Val 3"); //Вводим значение ячейки таблицы

lv_table_set_cell_value(tab,1,1,"Val 4"); //Вводим значение ячейки таблицы

lv_obj_add_style(tab, &style, 0); //Присваиваем ему новый стиль

}

Результат выполнения на скриншоте ниже:

Контур

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

outline_width

Задать ширину контура в пикселях.

outline_color

Задать цвет контура.

outline_opa

Установить непрозрачность контура. Значение 0, LV_OPA_0 или LV_OPA_TRANSP означает “полностью прозрачный”, 255, LV_OPA_100 или LV_OPA_COVER означает “полностью закрывающий”, другие значения или LV_OPA_10, LV_OPA_20 и т.д. означают полупрозрачность.

outline_pad

Задать заполнение контура, то есть промежуток между объектом и контуром.

Пример использования

Зададим для предыдущего примера красный цвето контура.

void transform_style_table()

{

static lv_style_t style; lv_style_init(&style);//Инициализация стиля

lv_style_set_pad_top(&style,20); // Отступ сверху

lv_style_set_pad_bottom(&style,20); // Отступ снизу

lv_style_set_pad_left(&style, 15);//Отступ слева

lv_style_set_pad_right(&style, 5); //Отступ справа

lv_style_set_bg_grad_color(&style,lv_color_hex(0xaaaa10));

lv_style_set_bg_grad_dir(&style,LV_GRAD_DIR_HOR);

lv_style_set_bg_grad_stop(&style,200);

lv_style_set_bg_main_stop(&style,50);

lv_style_set_border_width(&style,15); //Ширина границы объекта

lv_style_set_border_side(&style,LV_BORDER_SIDE_BOTTOM | LV_BORDER_SIDE_RIGHT); //Отрисовка правой и нижней границ объекта

lv_style_set_outline_width(&style,10); //Ширина контура объекта

lv_style_set_outline_color(&style,lv_color_hex(0xff0000)); //цвет контура

lv_style_set_outline_pad(&style,5); //Отступ от границы

lv_obj_t * tab=lv_table_create(lv_scr_act()); //Создаем объект table

lv_table_set_cell_value(tab,0,0,"Table value 1"); //Вводим значение ячейки таблицы

lv_table_set_cell_value(tab,0,1,"Val 2"); //Вводим значение ячейки таблицы

lv_table_set_cell_value(tab,1,0,"Table Val 3"); //Вводим значение ячейки таблицы

lv_table_set_cell_value(tab,1,1,"Val 4"); //Вводим значение ячейки таблицы

lv_obj_add_style(tab, &style, 0); //Присваиваем ему новый стиль

}

Результат выполнения на скриншоте ниже:

Тень

Набор свойств описывающих тень, нарисованную под объектом.

shadow_width

Задать ширину тени в пикселях. Значение должно быть >= 0.

shadow_ofs_x

Задать смещение тени в пикселях в направлении X.

shadow_ofs_y

Задать смещение тени в пикселях в направлении Y.

shadow_spread

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

shadow_color

Задать цвет тени.

shadow_opa

Установить непрозрачность тени. Значение 0, LV_OPA_0 или LV_OPA_TRANSP означает “полностью прозрачный”, 255, LV_OPA_100 или LV_OPA_COVER означает “полностью закрывающий”, другие значения или LV_OPA_10, LV_OPA_20 и т.д. означают полупрозрачность.

Пример использования

Зададим для таблицы из примера отрисовку тени.

void transform_style_table()

{

static lv_style_t style; lv_style_init(&style);//Инициализация стиля

lv_style_set_pad_top(&style,20); // Отступ сверху

lv_style_set_pad_bottom(&style,20); // Отступ снизу

lv_style_set_pad_left(&style, 15);//Отступ слева

lv_style_set_pad_right(&style, 5); //Отступ справа

lv_style_set_bg_grad_color(&style,lv_color_hex(0xaaaa10));

lv_style_set_bg_grad_dir(&style,LV_GRAD_DIR_HOR);

lv_style_set_bg_grad_stop(&style,200);

lv_style_set_bg_main_stop(&style,50);

lv_style_set_shadow_width(&style,30); //Ширина тени

lv_style_set_shadow_ofs_x(&style,50); //смещение тени по x

lv_style_set_shadow_ofs_y(&style,50); //сещение тени по y

lv_obj_t * tab=lv_table_create(lv_scr_act()); //Создаем объект table

lv_table_set_cell_value(tab,0,0,"Table value 1"); //Вводим значение ячейки таблицы

lv_table_set_cell_value(tab,0,1,"Val 2"); //Вводим значение ячейки таблицы

lv_table_set_cell_value(tab,1,0,"Table Val 3"); //Вводим значение ячейки таблицы

lv_table_set_cell_value(tab,1,1,"Val 4"); //Вводим значение ячейки таблицы

lv_obj_add_style(tab, &style, 0); //Присваиваем ему новый стиль

}

Результат выполнения на скриншоте ниже:

Текст

Набор свойств для описания стиля текста.

text_color

Задает цвет текста.
 

text_opa

Установить непрозрачность текста. Значение 0, LV_OPA_0 или LV_OPA_TRANSP означает “полностью прозрачный”, 255, LV_OPA_100 или LV_OPA_COVER означает “полностью закрывающий”, другие значения или LV_OPA_10, LV_OPA_20 и т.д. означают полупрозрачность.

text_font

Задать шрифт текста (указатель lv_font_t *).

text_letter_space

Задать интервал между буквами в пикселях

text_line_space

Задайте расстояние между строками в пикселях.

text_decor

Задать оформление текста. Возможными значениями являются LV_TEXT_DECOR_NONE/UNDERLINE/STRIKETHROUGH. Можно указать несколько параметров через символ |.

text_align

Задать способ выравнивания строк текста. Обратите внимание, что при этом выравнивается не сам объект, а только строки внутри объекта. Возможные значения LV_TEXT_ALIGN_LEFT/CENTER/RIGHT/AUTOLV_TEXT_ALIGN_AUTO определяет базовое направление текста и использует выравнивание по левому или правому краю соответственно.

Пример использования

Создадим объект Label, изменим цвет текста, расстояние между символами и оформление

void transform_style_demo()

{

static lv_style_t style; lv_style_init(&style);//Инициализация стиля

lv_style_set_width(&style,120); // Устанавливаем ширину объекта

lv_style_set_height(&style,100); // Устанавливаем высоту объекта

lv_style_set_align(&style, LV_ALIGN_CENTER);//Выравниваем объект по центру

lv_style_set_text_color(&style,lv_color_hex(0x00FF00));//Цвет текста

lv_style_set_text_letter_space(&style,15);//Расстояние между символами

lv_style_set_text_decor(&style,LV_TEXT_DECOR_UNDERLINE | LV_TEXT_DECOR_STRIKETHROUGH); //Оформление текста

lv_obj_t * lbl=lv_label_create(lv_scr_act()); //Создаем объект Label

lv_label_set_text(lbl,"test label"); //Вводим текст для метки

lv_obj_remove_style_all(lbl); //Удаляем все ранее присовенные стили объекта

lv_obj_add_style(lbl, &style, 0); //Присваиваем ему новый стиль

}

Результат выполнения на скриншоте ниже:

Линии

Набор свойств для описания объектов, похожих на линии.

line_width

Задать ширину линий в пикселях.

line_dash_width

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

line_dash_gap

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

line_rounded

Сделать конечные точки линий закругленными. true: закругленный, false: перпендикулярный конец линии.

line_color

Задать цвет для линий.

line_opa

Установить непрозрачность линий. Значение 0, LV_OPA_0 или LV_OPA_TRANSP означает “полностью прозрачный”, 255, LV_OPA_100 или LV_OPA_COVER означает “полностью закрывающий”, другие значения или LV_OPA_10, LV_OPA_20 и т.д. означают полупрозрачность.

Пример использования

Нарисуем несколько простых линий и немного поменяем их свойства.

void transform_style_line()

{

static lv_point_t line_points[] = { {5, 5}, {70, 70}, {120, 10}, {180, 60}, {240, 10} }; //массив координат для линий

static lv_style_t style; lv_style_init(&style);//Инициализация стиля

lv_style_set_line_width(&style, 10);//длина линий

lv_style_set_line_color(&style,lv_palette_main(LV_PALETTE_BLUE));//цвет линий

lv_style_set_line_rounded(&style, true);//Закругление на концах линий

lv_obj_t * line=lv_line_create(lv_scr_act()); //Создаем объект

line lv_line_set_points(line, line_points, 5); //Рисуем линии по координатам из массива

lv_obj_add_style(line, &style, 0); //Присваиваем им новый стиль

}

Результат выполнения на скриншоте ниже

Разное

Различные свойства без определенной категории

radius

Установить радиус закругления для каждого угла объекта. Значение устанвливается в пикселях (>= 0) или LV_RADIUS_CIRCLE для максимального значения. 

clip_corner

Обрезать содержимое объекта если оно не помещается в закругленном углу. Может быть true или false.

opa

Установить значение непрозрачности объекта. Значение 0, LV_OPA_0 или LV_OPA_TRANSP означает “полностью прозрачный”, 255, LV_OPA_100 или LV_OPA_COVER означает “полностью закрывающий”, другие значения или LV_OPA_10, LV_OPA_20 и т.д. означают полупрозрачность.

color_filter_dsc

Смешать цвет со всеми цветами объекта.

color_filter_opa

Интенсивность смешивания цветового фильтра.

anim

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

anim_time

Время анимации в миллисекундах.

anim_speed

Скорость анимации в пикселях / сек. Ее значение зависит от конкретного виджета. Например, скорость прокрутки надписи.

transition

В параметрах указывается lv_style_transition_dsc_t для описания перехода.

blend_mode

Описывает, как смешивать цвета с фоном. Возможные значения следующие LV_BLEND_MODE_NORMAL/ADDITIVE/SUBTRACTIVE/MULTIPLY

layout

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

base_dir

Задает базовое направление объекта. Возможными значениями являются LV_BIDI_DIR_LTR/RTL/AUTO

Заключение

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

Использование стилей в LVGL очень полезно для создания красивых и интуитивно понятных пользовательских интерфейсов и для достижения различных эффектов и выделения элементов.

Изучение свойств стилей в библиотеке LVGL поможет вам создавать уникальные и привлекательные пользовательские интерфейсы для ваших проектов. Вы сможете настроить внешний вид и поведение объектов в соответствии с вашими потребностями и предпочтениями.

Изучение библиотеки LVGL. Базовые свойства объектов

Введение

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

Теперь мы глубже погрузимся в изучение библиотеки и рассмотрим такой её важный элемент  как объекты. Объекты – это основные элементы пользовательского интерфейса, такие как кнопки, полосы прокрутки, метки и т.д. В данной статье мы сфокусируемся на базовых свойствах всех типов объектов. Узнаем, как их создавать , изменять размеры, параметры и свойства. Понимание этих основных свойств объектов позволит нам эффективно использовать LVGL для создания красивых и функциональных пользовательских интерфейсов на микроконтроллерах.

Общие сведения по объектам

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

Объекты создаются в графическом контексте, который управляется библиотекой LVGL.lv_obj_set_align(obj, LV_ALIGN_CENTER);

lv_obj_set_pos(obj, 10, 20); // Или в одной функции

lv_obj_align(obj, LV_ALIGN_CENTER, 10, 20);

Каждый объект имеет уникальный идентификатор, который используется для обращения к нему из программного кода. Объекты могут быть созданы как на верхнем уровне экрана, так и вложены друг в друга для создания более сложных интерфейсов.

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

Типы объектов в LVGL

LVGL предоставляет множество типов объектов или, так называемых виджетов, для создания пользовательских интерфейсов. Некоторые из наиболее распространенных типов объектов в LVGL:

  • Кнопка (lv_btn) – элемент интерфейса, который может быть нажат пользователем.
  • Метка (lv_label) – объект, который отображает текст на экране.
  • Изображение (lv_img) – объект, который отображает изображение на экране.
  • Список (lv_list) – объект, который позволяет выбирать один или несколько элементов из списка.
  • Ползунок (lv_slider) – объект, который позволяет пользователю выбирать значение из диапазона.
  • Поле ввода (lv_textarea) – объект, который позволяет пользователю вводить текст.
  • Таблица (lv_table) – объект, который представляет собой таблицу с ячейками.

Каждый из этих объектов имеет ряд свойств:

  • Базовые свойства. Размер, положение, видимость и т.п. Они есть у всех объектов. 
  • Специальные  свойства. Шрифт, формат текста, действие при нажатии, рисунок и т.п. Они зависят от конкретного объекта. У других виджетов их может и не быть.

В рамках данной статьи мы рассмотрим только базовые свойства присущие всем объектам LVGL.

Объявление объекта

Ссылки на все объекты выполняются с использованием lv_obj_t указателя в качестве дескриптора. Этот указатель впоследствии может быть использован для установки или получения атрибутов объекта.В общем виде это может выглядеть так:

lv_obj_t * btn1;

В дальнейшем обращаться к данному объекту мы сможем по имени указателя btn1.

Создание и удаление объекта

У каждого виджета есть своя собственная функция create с прототипом, подобным этому:

lv_obj_t * lv_<виджет>_create(lv_obj_t * родитель, <другие параметры );,,,

Обычно функции создания имеют только  параметр родитель, указывающий им, на каком объекте создавать новый виджет.

Возвращаемое значение представляет собой указатель на созданный объект с lv_obj_t * тип.

Например, для создания кнопки используется следующий код:

lv_obj_t * btn1 = lv_btn_create(screenMain);

В данном случаем, мы создаем объект типа lv_btn, родителем котором является объект screenMain и присваиваем ссылку на него указателю btn1.

В дальнейшем все действия с объектом можно выполнять с использованием его указателя. Например для изменения ширины кнопки можно использовать функцию:

lv_obj_set_width(btn1, 120); //ширина

Здесь мы для объекта на который ссылается указатель btn1, устанавливаем ширину 120 пикселей.

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

То есть, если вы, например, в функции void setup() создадите объект btn1, то в дальнейшем при попытке обратиться к нему из другой функции вы получите ошибку.

Чтобы этого избежать, можно объявить глобальную ссылку на объект.

Как, например, в этом примере:

#include "touch.h"

lv_obj_t * btn1; lv_obj_t * screenMain; ... ... ...

void setup()

{

... ... screenMain = lv_obj_create(NULL);

btn1 = lv_btn_create(screenMain); ... ...

}

В результате, мы создадим глобальные ссылки screenMain и btn1 и, после создания объектов, можем обращаться к ним из любого места программы.

Но данный подход имеет существенный минус.

Объекты постоянно будут висеть в оперативной памяти устройства и отбирать драгоценные килобайты.

Для немедленного удаления объекта используется функция 

lv_obj_del(lv_obj_t * obj).

Она удаляет объект и все его дочерние элементы. Например, для удаления btn1 из предыдущего примера достаточно написать:

lv_obj_del(btn1);

Если по какой-либо причине вы не можете удалить объект немедленно, вы можете использовать 

lv_obj_del_async(obj) 

который выполнит удаление при следующем вызове lv_timer_handler()

Это полезно, например, если вы хотите удалить родительский объект в LV_EVENT_DELETE обработчике дочернего объекта.

Вы можете удалить все дочерние элементы объекта (но не сам объект) с помощью lv_obj_clean(obj).

Вы можете использовать lv_obj_del_delayed(obj, 1000) для удаления объекта через некоторое время. Задержка выражается в миллисекундах.

Родительско-дочерняя структура

При создании любого объекта, кроме экрана, обязательно указывается его родитель. Родительский объект может рассматриваться как контейнер для его дочерних элементов. У каждого объекта есть ровно один родительский объект (кроме экранов), но у родителя может быть любое количество дочерних элементов. Нет ограничений на тип родительского объекта, но есть объекты, которые обычно являются родительскими (например, кнопка) или дочерними (например, метка).

Если позиция родительского элемента изменяется, дочерние элементы будут перемещаться вместе с ним. Следовательно, все координаты указываются относительно родительского объекта.

Это иллюстрирует следующий код:

lv_obj_t * parent = lv_obj_create(lv_scr_act()); /*Создадим родительский объект на текущем экране*/

lv_obj_set_size(parent, 100, 80); /*Установим размер родительского объекта*/

lv_obj_t * obj1 = lv_obj_create(parent); /*Создадим объект на предварительно созданном родительском объекте*/

lv_obj_set_pos(obj1, 10, 10); /*Установить позицию дочернего объекта относительно родительского*/

Схематичный результат выполнения программы вы можете увидеть на картинке ниже:

lv_obj_set_pos(parent, 50, 50); /*Перемещаем родителя. Дочерний элемент переместится вместе с ним*/

Дочерний элемент также изменит свое положение вместе с родительским:

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

lv_obj_set_x(obj1, -30); /* Немного отодвинем дочерний элемент влево*/

На экране вы увидите следующее:

Часть дочернего элемента за пределами родительского невидима. 

Это поведение может быть изменено с помощью 

lv_obj_add_flag(obj, LV_OBJ_FLAG_OVERFLOW_VISIBLE);.

Тогда картинка на экране будет выглядеть несколько иначе:

Дисплей и экраны

На самом высоком уровне иерархии объектов LVGL находится дисплей, который представляет драйвер для устройства отображения (физического дисплея или симулятора).

С дисплеем может быть связан один или несколько экранов. Каждый экран содержит иерархию объектов для графических виджетов, представляющих макет, который охватывает весь дисплей.

Когда вы создадите подобный экран 

lv_obj_t * screen = lv_obj_create(NULL), вы можете сделать его активным с помощью lv_scr_load(screen)

Функция lv_scr_act() дает вам указатель на активный экран.

Если у вас несколько дисплеев, важно знать, что функции экрана работают на самом последнем созданном дисплее или на том, который явно выбран с помощью lv_disp_set_default.

Чтобы узнать экран к которому относится объект, используйте функцию lv_obj_get_screen(obj) .

Размер и положение объекта

Размер объекта могут быть изменены по отдельным осям с помощью
lv_obj_set_width(obj, new_width) и lv_obj_set_height(obj, new_height)

Размер по обеим осям может быть изменён одновременно с помощью
lv_obj_set_size(obj, new_width, new_height).

Например:

lv_obj_set_width(btn1, 100); //устанавливаем ширину 100 пикселей для объекта btn1

lv_obj_set_height(btn1, 60); //Устанавливаем высоту 60 пикселей для объекта btn1

lv_obj_set_size(btn1, 100, 60); //Устанавливаем для объекта btn1 ширину 100 и высоту 60 пикселей

Вы можете задать положение относительно родительского объекта с помощью
lv_obj_set_x(obj, new_x) и lv_obj_set_y(obj, new_y)

Или для обеих осей одновременно с помощью


lv_obj_set_pos(obj, new_x, new_y).

Например:

lv_obj_set_x(btn1, 50); //устанавливаем для объекта btn1 положение по оси Х равным 50 пикселям

lv_obj_set_y(btn1, 60); //устанавливаем для объекта btn1 положение по оси У равным 60 пикселям

lv_obj_set_pos(btn1, 50, 60); //устанавливаем координаты для объекта btn1 по оси X=50, а по оси Y=60

Выравнивание

Вы можете выровнять объект по его родительскому элементу с помощью lv_obj_set_align(obj, LV_ALIGN_...).

После этого все настройки x и y будут соответствовать установленному режиму выравнивания.

Например, это приведет к смещению объекта на 10 и 20 пикселей от центра его родительского объекта:

lv_obj_set_align(obj, LV_ALIGN_CENTER);

lv_obj_set_pos(obj, 10, 20); // Или в одной функции lv_obj_align(obj, LV_ALIGN_CENTER, 10, 20);

Для выравнивания одного объекта относительно другого используется функция: 

lv_obj_align_to(obj_to_align, obj_reference, LV_ALIGN_..., x, y)

Например, чтобы выровнять текст под изображением:

lv_obj_align_to(text, image, LV_ALIGN_OUT_BOTTOM_MID, 0, 10)

Существуют следующие типы выравнивания:

Дополнительные атрибуты объектов

Есть некоторые атрибуты, которые могут быть включены / отключены с помощью 

lv_obj_add/clear_flag(obj, LV_OBJ_FLAG_...):

  • LV_OBJ_FLAG_HIDDEN Делает объект скрытым. (Как будто его там вообще не было)
  • LV_OBJ_FLAG_CLICKABLE Делает объект интерактивным, то есть реагирующим на касание или нажатие кнопки мыши
  • LV_OBJ_FLAG_CLICK_FOCUSABLE При нажатии на объект добавляется сфокусированное состояние
  • LV_OBJ_FLAG_CHECKABLE При нажатии на объект переключается его состояние. По логике похоже на чекбокс.
  • LV_OBJ_FLAG_SCROLLABLE включить/выключить прокрутку объекта если он не вмещается на экран
  • LV_OBJ_FLAG_SCROLL_ELASTIC Плавная прокрутка, но с меньшей скоростью
  • LV_OBJ_FLAG_SCROLL_MOMENTUM Прокрутка обладает инерцией
  • LV_OBJ_FLAG_SCROLL_ONE Разрешить прокрутку только одного дочернего объекта с возможностью привязки
  • LV_OBJ_FLAG_SCROLL_CHAIN_HOR Разрешить распространение горизонтальной прокрутки на родительский объект
  • LV_OBJ_FLAG_SCROLL_CHAIN_VER Разрешить распространение вертикальной прокрутки на родительский объект
  • LV_OBJ_FLAG_SCROLL_CHAIN  объединяет в себе действие этих двух флагов (LV_OBJ_FLAG_SCROLL_CHAIN_HOR | LV_OBJ_FLAG_SCROLL_CHAIN_VER)
  • LV_OBJ_FLAG_SCROLL_ON_FOCUS Автоматически прокручивать объект, чтобы сделать его видимым при фокусировке
  • LV_OBJ_FLAG_SCROLL_WITH_ARROW Разрешить прокрутку сфокусированного объекта с помощью клавиш со стрелками
  • LV_OBJ_FLAG_SNAPPABLE Если привязка прокрутки включена на родительском объекте, он может привязываться к этому объекту
  • LV_OBJ_FLAG_PRESS_LOCK Сохранять нажатое состояние объекта, даже если курсор или  точка касания вышли за пределы объекта
  • LV_OBJ_FLAG_EVENT_BUBBLE Распространять события и на родительский объект
  • LV_OBJ_FLAG_GESTURE_BUBBLE Распространить жесты на родительский объект
  • LV_OBJ_FLAG_ADV_HITTEST Более точное определение координат при касании объекта. Например для объектов сложной формы
  • LV_OBJ_FLAG_IGNORE_LAYOUT Игнорировать слои  расположения объектов. То есть не позволять перекрывать его другими объектами
  • LV_OBJ_FLAG_FLOATING Не прокручивать объект, когда прокручивается родительский объект и разместить его поверх всех объектов. 
  • LV_OBJ_FLAG_OVERFLOW_VISIBLE Не обрезать дочерний объект если он выходит за границы родительского
  • LV_OBJ_FLAG_LAYOUT_1 Пользовательский флаг, свободно используемый макетами
  • LV_OBJ_FLAG_LAYOUT_2 Пользовательский флаг, свободно используемый макетами
  • LV_OBJ_FLAG_WIDGET_1 Пользовательский флаг, свободно используемый виджетом
  • LV_OBJ_FLAG_WIDGET_2 Пользовательский флаг, свободно используемый виджетом
  • LV_OBJ_FLAG_USER_1 Пользовательский флаг, свободно используемый пользователем
  • LV_OBJ_FLAG_USER_2 Пользовательский флаг, свободно используемый пользователем
  • LV_OBJ_FLAG_USER_3 Пользовательский флаг, свободно используемый пользователем
  • LV_OBJ_FLAG_USER_4 Пользовательский флаг, свободно используемый пользователем

Небольшой пример:

/*Спрячем объект*/

lv_obj_add_flag(obj, LV_OBJ_FLAG_HIDDEN);

/*Сделаем объект неинтерактивным*/

lv_obj_clear_flag(obj, LV_OBJ_FLAG_CLICKABLE);

Группировка объектов

Объекты добавляются в группу с помощью 

lv_group_add_obj(group, obj),

и вы можете использовать 

lv_obj_get_group(obj),

чтобы увидеть, к какой группе принадлежит объект.

lv_obj_is_focused(obj) возвращает, сфокусирован ли объект в данный момент на своей группе или нет. Если объект не добавлен в группу, функция вернет false.

Расширение области нажатия

По умолчанию на объекты можно нажимать только в пределах их ограниченной области. Однако её можно расширить с помощью 

lv_obj_set_ext_click_area(obj, size).

Заключение

В данной статье мы углубились в изучение объектов библиотеки LVGL и изучили базовые свойства, присущие всем объектам.

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

Также мы рассмотрели их структуру  и поняли, как изменения параметров влияют на внешний вид и функциональность.

Это позволит нам создавать более сложные и интерактивные пользовательские интерфейсы с помощью библиотеки LVGL на микроконтроллерах.