From d078157238eb99ad359f2449218c22398fc912ef Mon Sep 17 00:00:00 2001 From: falkTX Date: Sun, 5 May 2024 13:41:30 +0200 Subject: [PATCH] Add initial LVGL widget, can draw but no user events yet Signed-off-by: falkTX --- opengl/LVGL.cpp | 303 ++++++++++++++++++++++++++++++++++++++++++++++++ opengl/LVGL.hpp | 139 ++++++++++++++++++++++ 2 files changed, 442 insertions(+) create mode 100644 opengl/LVGL.cpp create mode 100644 opengl/LVGL.hpp diff --git a/opengl/LVGL.cpp b/opengl/LVGL.cpp new file mode 100644 index 0000000..de27948 --- /dev/null +++ b/opengl/LVGL.cpp @@ -0,0 +1,303 @@ +/* + * LVGL for DPF + * Copyright (C) 2024 Filipe Coelho + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "LVGL.hpp" +#include "OpenGL.hpp" + +#include "../distrho/extra/Sleep.hpp" +#include "../distrho/extra/Time.hpp" + +#include "demos/lv_demos.h" + +START_NAMESPACE_DGL + +// -------------------------------------------------------------------------------------------------------------------- + +static thread_local lv_global_t* lv_global = nullptr; + +static uint32_t gettime_ms() noexcept +{ + return DISTRHO_NAMESPACE::d_gettime_ms(); +} + +struct LVGLWidget::PrivateData { + LVGLWidget* const self; + lv_global_t* const global; + + GLuint textureId = 0; + Size textureSize; + void* textureData = nullptr; + + lv_display_t* display = nullptr; + + explicit PrivateData(LVGLWidget* const s) + : self(s), + global(static_cast(std::calloc(1, sizeof(lv_global_t)))) + { + lv_global = global; + init(); + } + + ~PrivateData() + { + lv_global = global; + cleanup(); + lv_global = nullptr; + std::free(global); + } + +private: + void init() + { + lv_init(); + lv_delay_set_cb(d_msleep); + lv_tick_set_cb(gettime_ms); + + const uint width = self->getWidth() ?: 2000; + const uint height = self->getHeight() ?: 1200; + + display = lv_display_create(width, height); + DISTRHO_SAFE_ASSERT_RETURN(display != nullptr,); + + lv_display_set_dpi(display, LV_DPI_DEF * self->getScaleFactor()); + + glGenTextures(1, &textureId); + DISTRHO_SAFE_ASSERT_RETURN(textureId != 0,); + + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, textureId); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + + static constexpr const float transparent[] = { 0.0f, 0.0f, 0.0f, 0.0f }; + glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, transparent); + + glPixelStorei(GL_PACK_ALIGNMENT, 1); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + glBindTexture(GL_TEXTURE_2D, 0); + glDisable(GL_TEXTURE_2D); + + lv_display_set_driver_data(display, this); + lv_display_set_flush_cb(display, flush_cb); + lv_display_add_event_cb(display, resolution_changed_cb, LV_EVENT_RESOLUTION_CHANGED, NULL); + + recreateTextureData(width, height); + } + + void cleanup() + { + global->deinit_in_progress = true; + + if (display != nullptr) + { + lv_display_delete(display); + display = nullptr; + } + + if (textureId != 0) + { + glDeleteTextures(1, &textureId); + textureId = 0; + } + + std::free(textureData); + textureData = nullptr; + + lv_deinit(); + } + + void recreateTextureData(const uint width, const uint height) + { + const auto format = lv_display_get_color_format(display); + const uint32_t stride = lv_draw_buf_width_to_stride(width, format); + const uint32_t data_size = height * stride; + + textureData = std::realloc(textureData, data_size); + std::memset(textureData, 0, data_size); + + textureSize = Size(width, height); + lv_display_set_buffers(display, textureData, nullptr, data_size, LV_DISPLAY_RENDER_MODE_DIRECT); + } + + static void resolution_changed_cb(lv_event_t* const ev) + { + lv_display_t* const evdisplay = static_cast(lv_event_get_current_target(ev)); + PrivateData* const evthis = static_cast(lv_display_get_driver_data(evdisplay)); + const uint width = lv_display_get_horizontal_resolution(evdisplay); + const uint height = lv_display_get_vertical_resolution(evdisplay); + + evthis->recreateTextureData(width, height); + } + + static void flush_cb(lv_display_t* const evdisplay, const lv_area_t*, uint8_t*) + { + lv_display_flush_ready(evdisplay); + } + + DISTRHO_DECLARE_NON_COPYABLE(PrivateData) +}; + +// -------------------------------------------------------------------------------------------------------------------- + +LVGLWidget::LVGLWidget(TopLevelWidget* const tlw) + : TopLevelWidget(tlw->getWindow()), + lvglData(new PrivateData(this)) +{ + addIdleCallback(this, 1000 / 60); // 60 fps + + // TESTING + lv_demo_music(); +} + +LVGLWidget::LVGLWidget(Window& windowToMapTo) + : TopLevelWidget(windowToMapTo), + lvglData(new PrivateData(this)) +{ + addIdleCallback(this, 1000 / 60); // 60 fps + + // TESTING + lv_demo_music(); +} + +LVGLWidget::~LVGLWidget() +{ + removeIdleCallback(this); + delete lvglData; +} + +void LVGLWidget::idleCallback() +{ + lv_global = lvglData->global; + lv_timer_handler(); + + // FIXME + TopLevelWidget::repaint(); +} + +void LVGLWidget::onDisplay() +{ + DISTRHO_SAFE_ASSERT_RETURN(getSize() == lvglData->textureSize,); + + #if LV_COLOR_DEPTH == 32 + constexpr const GLenum format = GL_BGRA; + #elif LV_COLOR_DEPTH == 24 + constexpr const GLenum format = GL_BGR; + #else + #error Unsupported color format + #endif + + const int width = static_cast(getWidth()); + const int height = static_cast(getHeight()); + + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, lvglData->textureId); + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, format, GL_UNSIGNED_BYTE, lvglData->textureData); + + glBegin(GL_QUADS); + { + glTexCoord2f(0.0f, 0.0f); + glVertex2d(0, 0); + + glTexCoord2f(1.0f, 0.0f); + glVertex2d(width, 0); + + glTexCoord2f(1.0f, 1.0f); + glVertex2d(width, height); + + glTexCoord2f(0.0f, 1.0f); + glVertex2d(0, height); + } + glEnd(); + + glBindTexture(GL_TEXTURE_2D, 0); + glDisable(GL_TEXTURE_2D); +} + +bool LVGLWidget::onKeyboard(const Widget::KeyboardEvent& event) +{ + if (TopLevelWidget::onKeyboard(event)) + return true; + + // TODO + return false; +} + +bool LVGLWidget::onCharacterInput(const Widget::CharacterInputEvent& event) +{ + if (TopLevelWidget::onCharacterInput(event)) + return true; + + // TODO + return false; +} + +bool LVGLWidget::onMouse(const Widget::MouseEvent& event) +{ + if (TopLevelWidget::onMouse(event)) + return true; + + // TODO + return false; +} + +bool LVGLWidget::onMotion(const Widget::MotionEvent& event) +{ + if (TopLevelWidget::onMotion(event)) + return true; + + // TODO + return false; +} + +bool LVGLWidget::onScroll(const Widget::ScrollEvent& event) +{ + if (TopLevelWidget::onScroll(event)) + return true; + + // TODO + return false; +} + +void LVGLWidget::onResize(const Widget::ResizeEvent& event) +{ + TopLevelWidget::onResize(event); + + if (lvglData->display == nullptr) + return; + + lv_global = lvglData->global; + lv_display_set_resolution(lvglData->display, event.size.getWidth(), event.size.getHeight()); + lv_refr_now(lvglData->display); +} + +// -------------------------------------------------------------------------------------------------------------------- + +END_NAMESPACE_DGL + +// -------------------------------------------------------------------------------------------------------------------- + +lv_global_t* lv_global_default() +{ + DISTRHO_SAFE_ASSERT(DGL_NAMESPACE::lv_global != nullptr); + return DGL_NAMESPACE::lv_global; +} + +// -------------------------------------------------------------------------------------------------------------------- diff --git a/opengl/LVGL.hpp b/opengl/LVGL.hpp new file mode 100644 index 0000000..801bddc --- /dev/null +++ b/opengl/LVGL.hpp @@ -0,0 +1,139 @@ +/* + * LVGL for DPF + * Copyright (C) 2024 Filipe Coelho + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#pragma once + +#if __cplusplus < 201103L +# error DPF LVGL widgets require C++11 +#endif + +#include "TopLevelWidget.hpp" +#include "lvgl.h" + +START_NAMESPACE_DGL + +// -------------------------------------------------------------------------------------------------------------------- + +#if !defined(LV_ENABLE_GLOBAL_CUSTOM) || LV_ENABLE_GLOBAL_CUSTOM != 1 +# error LV_ENABLE_GLOBAL_CUSTOM must be set to 1 for DPF builds +#endif + +#if LV_COLOR_DEPTH != 24 && LV_COLOR_DEPTH != 32 +# error LV_COLOR_DEPTH must be 24 or 32 for DPF builds +#endif + +#if LV_DPI_DEF != 160 +# error LV_DPI_DEF must be 160 for DPF builds +#endif + +#if LV_USE_STDLIB_MALLOC != LV_STDLIB_CLIB +# error LV_USE_STDLIB_MALLOC must be LV_STDLIB_CLIB for DPF builds +#endif + +#if LV_USE_STDLIB_STRING != LV_STDLIB_CLIB +# error LV_USE_STDLIB_STRING must be LV_STDLIB_CLIB for DPF builds +#endif + +#if LV_USE_STDLIB_SPRINTF != LV_STDLIB_CLIB +# error LV_USE_STDLIB_SPRINTF must be LV_STDLIB_CLIB for DPF builds +#endif + +#if defined(LV_USE_OS) && LV_USE_OS != LV_OS_NONE +# error LV_USE_OS must be LV_OS_NONE for DPF builds +#endif + +#if defined(LV_USE_EVDEV) && LV_USE_EVDEV +# error LV_USE_EVDEV must not be enabled for DPF builds +#endif + +#if defined(LV_USE_LIBINPUT) && LV_USE_LIBINPUT +# error LV_USE_LIBINPUT must not be enabled for DPF builds +#endif + +#if defined(LV_USE_LINUX_DRM) && LV_USE_LINUX_DRM +# error LV_USE_LINUX_DRM must not be enabled for DPF builds +#endif + +#if defined(LV_USE_LINUX_FBDEV) && LV_USE_LINUX_FBDEV +# error LV_USE_LINUX_FBDEV must not be enabled for DPF builds +#endif + +#if defined(LV_USE_NUTTX) && LV_USE_NUTTX +# error LV_USE_NUTTX must not be enabled for DPF builds +#endif + +#if defined(LV_USE_SDL) && LV_USE_SDL +# error LV_USE_SDL must not be enabled for DPF builds +#endif + +#if defined(LV_USE_WINDOWS) && LV_USE_WINDOWS +# error LV_USE_WINDOWS must not be enabled for DPF builds +#endif + +#if defined(LV_USE_X11) && LV_USE_X11 +# error LV_USE_X11 must not be enabled for DPF builds +#endif + +// -------------------------------------------------------------------------------------------------------------------- + +/** + LVGL Widget class. + + This class exposes the Dear ImGui drawing API inside a DGL Widget. + The drawing function onDisplay() is implemented internally + but a new onImGuiDisplay() needs to be overridden instead. + + This class will take care of setting up ImGui for drawing, + and also also user input, resizes and everything in between. + */ +class LVGLWidget : public TopLevelWidget, + public IdleCallback +{ +public: + /** + Constructor for a LVGLTopLevelWidget. + */ + explicit LVGLWidget(TopLevelWidget* tlw); + + /** + Constructor for a LVGLTopLevelWidget. + */ + explicit LVGLWidget(Window& windowToMapTo); + + /** + Destructor. + */ + ~LVGLWidget() override; + +protected: + void idleCallback() override; + void onDisplay() override; + bool onKeyboard(const Widget::KeyboardEvent& event) override; + bool onCharacterInput(const Widget::CharacterInputEvent&) override; + bool onMouse(const Widget::MouseEvent& event) override; + bool onMotion(const Widget::MotionEvent& event) override; + bool onScroll(const Widget::ScrollEvent& event) override; + void onResize(const Widget::ResizeEvent& event) override; + + struct PrivateData; + PrivateData* const lvglData; + + DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(LVGLWidget) +}; + +// -------------------------------------------------------------------------------------------------------------------- + +END_NAMESPACE_DGL