LVGL — це відкрита, безкоштовна бібліотека для створення графічних інтерфейсів користувача на вбудовані пристрої. Вона має велику кількість функцій та інтеграцій, завдяки чому ідеально підходить для створення як простих, так і складних інтерфейсів. Пропонує широкий вибір компонентів. Завдяки готовим інтеграціям із зовнішніми бібліотеками можливо використовувати в проєкті зображення у форматах PNG, JPEG або WEBP; анімації у форматі Lottie; шрифти у форматах TrueType, OpenType або WOFF; навіть можливо завантажувати 3D-моделі у форматі glTF. Бібліотека підтримує мікроконтролери виробників, таких як: STMicroelectronics (серія STM32), Espressif або NXP.
LVGL може використати можливості сучасних мікропроцесорів для застосувань HMI. Має потужний рушій рендерингу, який підтримує малювання дуг, напівпрозорих елементів та градієнтів. Додатково має вбудовані драйвери для спеціалізованих графічних чипів, що є в деяких мікропроцесорах.

Бібліотеку можна обмежити, щоб її можна було застосувати на менш потужних пристроях. У конфігураційному файлі можна вимкнути всі невикористовувані елементи, підтримку різних кольорових форматів, зовнішні бібліотеки та інші функції, щоб зменшити розмір бінарного файлу. Також можна застосувати менш потужний рушій рендерингу, щоб зменшити навантаження на процесор. Бібліотека не потребує багато пам’яті, хоча це залежить від кількості створених елементів.
Початок роботи
Насамперед необхідно встановити бібліотеку та підключити її до проєкту. Процедура відрізняється залежно від типу проєкту. Наприклад, у проєктах, що використовують ESP-IDF, достатньо додати залежність lvgl/lvgl. Можливо також безпосередньо додати код бібліотеки в проєкт.
Наступним кроком є конфігурація. Вона відбувається через редагування файлу lv_conf.h або за допомогою інструмента Kconfig. У конфігурації можна налаштувати кількість доступної пам’яті і спосіб її алокації, увімкнути або вимкнути різні функціональності, включно із зовнішніми бібліотеками або доступними елементами інтерфейсу.
Перед використанням потрібно ініціалізувати бібліотеку, викликавши функцію lv_init(). Далі потрібно створити екземпляр дисплея lv_display_t за допомогою функції lv_display_create(). Перший створений після ініціалізації дисплей стає дисплеєм за замовчуванням. Деякі функції замість екземпляра lv_display_t приймають значення NULL і в такому випадку використовують дисплей за замовчуванням. Разом із дисплеєм створюється екран — об’єкт UI, який є коренем дерева об’єктів на цьому дисплеї. Екземпляр активного екрана можна отримати за допомогою функції lv_display_get_screen_active().
Інтерфейс будується з елементів, які у LVGL називаються об’єктами (objects). Елементи розташовані в ієрархії, що має вигляд дерева. Функції створення об’єктів приймають як параметр елемент батька. Батьком може бути будь-який елемент, зокрема порожній, що дозволяє групувати елементи. Цей механізм схожий на використання тега div у HTML. Докладний опис та список елементів можна знайти в документації бібліотеки.
Елементи на екрані можна позиціонувати абсолютним способом, встановлюючи його координати безпосередньо функціями lv_obj_set_pos()/lv_obj_set_x()/lv_obj_set_y(), або в динамічний спосіб, відносно батька lv_obj_align() чи іншого об’єкта lv_obj_align_to(). Варто зазначити, що позиція буде автоматично підрахована заново лише у випадку зв’язку з батьком (коли той змінить своє положення).
Зовнішній вигляд елементів можна змінювати за допомогою стилів. Властивості стилів можна встановити безпосередньо для конкретного об’єкта, або створити екземпляр lv_style_t, налаштувати потрібні властивості і застосувати до багатьох компонентів через функцію lv_obj_add_style(). Механізм подібний до класів у CSS, особливо тим, що стилі у LVGL також працюють каскадно. Повний перелік властивостей стилів доступний у документації бібліотеки.
Для додавання підтримки пристроїв введення (сенсорний екран, миша, клавіатура) треба створити екземпляр lv_indev_t і підключити до нього (lv_indev_set_read_cb()) функцію callback, яка читатиме дані з пристрою. Для сенсорних панелей, що підтримують кілька одночасних дотиків (мультитач), потрібно для кожної точки додати окремий екземпляр lv_indev_t. Пристрої введення працюють у режимі активного очікування (polling) і будуть автоматично опитуватися з інтервалом.
Лічильник вгору/вниз
Нижче показано, як реалізувати простий додаток. Додаток зберігає стан лічильника і відображає його на мітці. Користувач може зменшити лічильник кнопкою «-» або збільшити кнопкою «+».
Для зручності використано можливість застосування бібліотеки SDL як платформи запуску. Завдяки цьому тести можна проводити безпосередньо на комп’ютері, без потреби мати мікропроцесор із дисплеєм. Бібліотеку SDL2 треба встановити, а далі увімкнути LV_USE_SDL у значення 1 у конфігураційному файлі, щоб активувати інтеграцію.
На початку програми потрібно викликати функцію ініціалізації, створити дисплей, а також пристрій введення. У випадку SDL2 обидві ці речі вже реалізовані у LVGL і достатньо викликати готові функції. Наприкінці функції main розташовано обробку циклу подій (event-loop). Вона відповідає за оновлення UI у відповідь на різноманітні події, наприклад, виявлення та обробку натискання на сенсорній панелі. 
Перший доданий елемент — це мітка для відображення результату. Вона буде додана безпосередньо як дочірній елемент до активного екрана за замовчуванням. Мітка розташована по центру екрану за допомогою функції lv_obj_align. Функція додатково приймає зсув у пікселях від точки відліку, в цьому випадку нульовий по обох осях. Використано функцію lv_label_set_text_static замість lv_label_set_text, оскільки переданий текстовий літерал доступний протягом усього виконання програми, тож немає потреби копіювати текст у внутрішню пам’ять мітки.

Далі будуть додані дві кнопки, позначені «+» і «-». Для додавання кнопок використано можливість додати зсув у lv_obj_align, щоб трохи відсунути їх від краю. Самі кнопки не зберігають текст чи зображення, що відображаються на них, це окремі елементи, які мають кнопку як батька. У цьому випадку достатньо додати мітку, оскільки LVGL постачає набір символів у вигляді шрифту. Повний список символів доступний у документації бібліотеки.

Щоб додаток став інтерактивним, до кнопок потрібно додати callback-функцію, що викликатиметься при натисканні. Значення лічильника зберігається у глобальній змінній g_counter. Для доступу до мітки використано функції доступу до дерева елементів lv_obj_get_parent і lv_obj_get_child. Аргумент 0 означає доступ до найстаршої (першої за порядком додавання) дитини. Функція також приймає від’ємні значення, що інвертують порядок доступу, тобто -1 означає доступ до останньої за порядком додавання дитини.

Для зв’язування callback-функцій із подіями служить функція lv_obj_add_event_cb. Третій параметр цієї функції — фільтр подій, можна додати одну функцію для обробки різних подій. Четвертий параметр — це відомий у мові C параметр типу void*, який служить для передачі користувацьких параметрів у callback-функції.

Зовнішній вигляд елементів можна змінювати, задаючи властивості безпосередньо для конкретного елемента або через стилі. Стилі корисні, коли кілька елементів повинні мати однаковий вигляд, оскільки дозволяють уникнути дублювання. Вони працюють каскадно, тобто властивості одного стилю можуть бути перезаписані пізніше доданим стилем. У прикладі додано дві кнопки з однаковим виглядом. Добре використати стилі для них.

Елементи мають різні стани в залежності від дій з ними, наприклад: коли користувач навів курсор на елемент, коли він натиснутий або коли нічого не відбувається. Різним станам можна призначити різні стилі, які працюватимуть каскадно. При додаванні стилю до об’єкта потрібно вказати селектор, у якому стані стиль активується. Механізм схожий на псевдокласи у CSS. Для окремих змін або елементів стиль можна встановити безпосередньо.

Логіку додатка можна реалізувати, застосовуючи патерн спостерігач. LVGL надає спостережуваний об’єкт lv_subject_t, до якого можна підключати спостерігачів. Деякі елементи мають вбудовані спеціальні функції, що полегшують роботу зі спостережуваними об’єктами. Наприклад, текст мітки можна прив’язати до такого об’єкта, і він автоматично оновлюватиметься щоразу, коли змінюватиметься спостережуване значення. Інший приклад — кнопка, яка при натисканні може змінювати поточне значення. Також можна підключити власного спостерігача у вигляді callback-функції.
Реалізація виглядала би так: глобальна змінна лічильника буде замінена на екземпляр lv_subject_t. Текст мітки буде прив’язаний до спостережуваного об’єкта через функцію lv_label_bind_text. Кнопки при кліку автоматично додаватимуть до поточного значення +1 або -1. Для цього використовується функція lv_obj_add_subject_increment_event, яка додає до об’єкта інкремент значення спостережуваного об’єкта на задану константу як обробник вказаної у аргументі події.
Нижче наведено код, який потрібно додати для використання lv_observer_t. Callback-функції s_on_down_clicked та s_on_up_clicked не знадобляться, так само як і початкова ініціалізація тексту мітки.

Приклад програми: текстовий редактор
Як приклад більш складного додатка, що використовує: векторні шрифти, інтеграцію із файловою системою та розширені елементи UI, буде представлений текстовий редактор. UI з одного боку натхненний традиційними десктопними додатками через застосування панелі інструментів, а з іншого боку адаптований для сенсорних екранів завдяки віртуальній клавіатурі. Для побудови використані готові елементи бібліотеки без значних змін, а також власні елементи на їх основі.
Для розташування елементів використано flex-розкладку. Клавіатура, текстове поле та панель інструментів розміщені в колонку. Сам панель інструментів — це flex-контейнер рядкового типу. Підтримуються автоматично обчислені відступи між елементами, що використано для розміщення кнопки закриття. Flex у LVGL схожий на CSS та є зручним рішенням для розташування компонентів на екрані.

Прикладом складного використання компонентів є вікно вибору файлу для запису (та читання). Для його створення використано елемент lv_msgbox, який у базовій версії служить для показу коротких повідомлень користувачу. Основна частина елемента у найпростіших випадках — мітка з текстом повідомлення. Проте замість мітки можна додати більше елементів і розглядати вікно повідомлення як загальне вікно. Список файлів створений на основі таблиці з власною функцією малювання. Через це воно потребує значно менше пам’яті, ніж список з міток.
Завдяки бібліотеці stb_truetype, LVGL підтримує векторні шрифти у форматі TrueType. Щоб скористатися цим, у конфігурації потрібно увімкнути рушій Tiny TTF. Шрифт елемента (наприклад, текстового поля) можна змінити під час роботи додатка. Змінений елемент автоматично позначається для повторного малого. Рушій Tiny TTF підтримує зміну розміру шрифту в будь-який момент після завантаження шрифту. Варто пам’ятати про ручне позначення зміни стилю, щоб елементи, які його використовують, автоматично оновилися. Підтримка кодування UTF-8 дає змогу повною мірою використовувати можливості багатомовних шрифтів. Широкий вибір відкритих шрифтів у форматі TrueType доступний на Google Fonts.
Додаток використовує модуль lv_fs для роботи з файлами, який надає спільний інтерфейс для багатьох файлових систем. Є кілька реалізацій, зокрема: на базі інтерфейсу стандартної бібліотеки мови C, API POSIX або Win32. Можливо одночасне використання кількох реалізацій. Шляхи починаються з великої літери, що ідентифікує драйвер файлової системи. Наприклад, літері P можна призначити POSIX, а W — Win32. Тоді шлях P:/home/user/file.txt оброблятиметься POSIX, а W:C:\Users\user\file.txt — Win32.
Підсумок
LVGL має широке застосування: від простих інтерфейсів на обладнанні з дуже обмеженими ресурсами до дуже складних інтерфейсів, що нагадують мобільні додатки. Проєктування спрощує широкий вибір готових компонентів, які легко можна модифікувати відповідно до потреб. Для зручності використовується система стилів і розкладки flex та grid, подібні до тих, що у CSS. Підтримка файлової системи, алокації та логів незалежно від платформи дає можливість писати повністю портативні додатки або легко переносити їх на інші платформи. Можливість запуску на комп’ютерах під Linux, Windows або Mac пришвидшує розробку UI. Це хороший вибір для створення професійних графічних інтерфейсів у вбудованих системах.
