From c8fdc0c1e30fd615cb4dedccdc30236137709da7 Mon Sep 17 00:00:00 2001 From: deanlee Date: Sun, 6 Oct 2024 23:36:20 +0800 Subject: [PATCH] raylib spinner --- selfdrive/ui/SConscript | 18 +++-- selfdrive/ui/qt/spinner.cc | 120 --------------------------------- selfdrive/ui/qt/spinner.h | 37 ---------- selfdrive/ui/raylib/spinner.cc | 69 +++++++++++++++++++ selfdrive/ui/raylib/util.cc | 56 +++++++++++++++ selfdrive/ui/raylib/util.h | 21 ++++++ 6 files changed, 160 insertions(+), 161 deletions(-) delete mode 100644 selfdrive/ui/qt/spinner.cc delete mode 100644 selfdrive/ui/qt/spinner.h create mode 100644 selfdrive/ui/raylib/spinner.cc create mode 100644 selfdrive/ui/raylib/util.cc create mode 100644 selfdrive/ui/raylib/util.h diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index 643951fff311cb..4d6f6cb1abdb59 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -1,6 +1,6 @@ import os import json -Import('qt_env', 'arch', 'common', 'messaging', 'visionipc', 'transformations') +Import('env', 'qt_env', 'arch', 'common', 'messaging', 'visionipc', 'transformations') base_libs = [common, messaging, visionipc, transformations, 'm', 'OpenCL', 'ssl', 'crypto', 'pthread'] + qt_env["LIBS"] @@ -70,10 +70,8 @@ if GetOption('extras'): if GetOption('extras') and arch != "Darwin": qt_env.SharedLibrary("qt/python_helpers", ["qt/qt_window.cc"], LIBS=qt_libs) - # spinner and text window + # text window qt_env.Program("_text", ["qt/text.cc"], LIBS=qt_libs) - qt_env.Program("_spinner", ["qt/spinner.cc"], LIBS=qt_libs) - # setup and factory resetter qt_env.Program("qt/setup/reset", ["qt/setup/reset.cc"], LIBS=qt_libs) @@ -110,3 +108,15 @@ if GetOption('extras') and arch != "Darwin": # build watch3 if arch in ['x86_64', 'aarch64', 'Darwin'] or GetOption('extras'): qt_env.Program("watch3", ["watch3.cc"], LIBS=qt_libs + ['common', 'msgq', 'visionipc']) + +# raylib env +raylib_env = env.Clone() +raylib_lib = env.Library("raylib_lib", ['raylib/util.cc'], LIBS='raylib') +raylib_libs = ['raylib', raylib_lib] +raylib_env['LIBPATH'] += [f'#third_party/raylib/{arch}/'] +raylib_frameworks = [] +if arch == "Darwin": + raylib_frameworks += ['OpenCL', 'CoreVideo', 'Cocoa', 'GLUT', 'CoreFoundation', 'OpenGL', 'IOKit'] +else: + raylib_libs.append('OpenCL') +raylib_env.Program("_spinner", ["raylib/spinner.cc"], LIBS=raylib_libs, FRAMEWORKS=raylib_frameworks) diff --git a/selfdrive/ui/qt/spinner.cc b/selfdrive/ui/qt/spinner.cc deleted file mode 100644 index 2404efa668408b..00000000000000 --- a/selfdrive/ui/qt/spinner.cc +++ /dev/null @@ -1,120 +0,0 @@ -#include "selfdrive/ui/qt/spinner.h" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "system/hardware/hw.h" -#include "selfdrive/ui/qt/qt_window.h" -#include "selfdrive/ui/qt/util.h" - -TrackWidget::TrackWidget(QWidget *parent) : QWidget(parent) { - setAttribute(Qt::WA_OpaquePaintEvent); - setFixedSize(spinner_size); - - // pre-compute all the track imgs. make this a gif instead? - QPixmap comma_img = loadPixmap("../assets/img_spinner_comma.png", spinner_size); - QPixmap track_img = loadPixmap("../assets/img_spinner_track.png", spinner_size); - - QTransform transform(1, 0, 0, 1, width() / 2, height() / 2); - QPixmap pm(spinner_size); - QPainter p(&pm); - p.setRenderHint(QPainter::SmoothPixmapTransform); - for (int i = 0; i < track_imgs.size(); ++i) { - p.resetTransform(); - p.fillRect(0, 0, spinner_size.width(), spinner_size.height(), Qt::black); - p.drawPixmap(0, 0, comma_img); - p.setTransform(transform.rotate(360 / spinner_fps)); - p.drawPixmap(-width() / 2, -height() / 2, track_img); - track_imgs[i] = pm.copy(); - } - - m_anim.setDuration(1000); - m_anim.setStartValue(0); - m_anim.setEndValue(int(track_imgs.size() -1)); - m_anim.setLoopCount(-1); - m_anim.start(); - connect(&m_anim, SIGNAL(valueChanged(QVariant)), SLOT(update())); -} - -void TrackWidget::paintEvent(QPaintEvent *event) { - QPainter painter(this); - painter.drawPixmap(0, 0, track_imgs[m_anim.currentValue().toInt()]); -} - -// Spinner - -Spinner::Spinner(QWidget *parent) : QWidget(parent) { - QGridLayout *main_layout = new QGridLayout(this); - main_layout->setSpacing(0); - main_layout->setMargin(200); - - main_layout->addWidget(new TrackWidget(this), 0, 0, Qt::AlignHCenter | Qt::AlignVCenter); - - text = new QLabel(); - text->setWordWrap(true); - text->setVisible(false); - text->setAlignment(Qt::AlignCenter); - main_layout->addWidget(text, 1, 0, Qt::AlignHCenter); - - progress_bar = new QProgressBar(); - progress_bar->setRange(5, 100); - progress_bar->setTextVisible(false); - progress_bar->setVisible(false); - progress_bar->setFixedHeight(20); - main_layout->addWidget(progress_bar, 1, 0, Qt::AlignHCenter); - - setStyleSheet(R"( - Spinner { - background-color: black; - } - QLabel { - color: white; - font-size: 80px; - background-color: transparent; - } - QProgressBar { - background-color: #373737; - width: 1000px; - border solid white; - border-radius: 10px; - } - QProgressBar::chunk { - border-radius: 10px; - background-color: white; - } - )"); - - notifier = new QSocketNotifier(fileno(stdin), QSocketNotifier::Read); - QObject::connect(notifier, &QSocketNotifier::activated, this, &Spinner::update); -} - -void Spinner::update(int n) { - std::string line; - std::getline(std::cin, line); - - if (line.length()) { - bool number = std::all_of(line.begin(), line.end(), ::isdigit); - text->setVisible(!number); - progress_bar->setVisible(number); - text->setText(QString::fromStdString(line)); - if (number) { - progress_bar->setValue(std::stoi(line)); - } - } -} - -int main(int argc, char *argv[]) { - initApp(argc, argv); - QApplication a(argc, argv); - Spinner spinner; - setMainWindow(&spinner); - return a.exec(); -} diff --git a/selfdrive/ui/qt/spinner.h b/selfdrive/ui/qt/spinner.h deleted file mode 100644 index 43d90a75b01036..00000000000000 --- a/selfdrive/ui/qt/spinner.h +++ /dev/null @@ -1,37 +0,0 @@ -#include - -#include -#include -#include -#include -#include -#include - -constexpr int spinner_fps = 30; -constexpr QSize spinner_size = QSize(360, 360); - -class TrackWidget : public QWidget { - Q_OBJECT -public: - TrackWidget(QWidget *parent = nullptr); - -private: - void paintEvent(QPaintEvent *event) override; - std::array track_imgs; - QVariantAnimation m_anim; -}; - -class Spinner : public QWidget { - Q_OBJECT - -public: - explicit Spinner(QWidget *parent = 0); - -private: - QLabel *text; - QProgressBar *progress_bar; - QSocketNotifier *notifier; - -public slots: - void update(int n); -}; diff --git a/selfdrive/ui/raylib/spinner.cc b/selfdrive/ui/raylib/spinner.cc new file mode 100644 index 00000000000000..b1e48e66d794ec --- /dev/null +++ b/selfdrive/ui/raylib/spinner.cc @@ -0,0 +1,69 @@ +#include +#include +#include + +#include "selfdrive/ui/raylib/util.h" +#include "third_party/raylib/include/raylib.h" + +constexpr int kProgressBarWidth = 1000; +constexpr int kProgressBarHeight = 20; +constexpr float kRotationRate = 12.0f; +constexpr int kMargin = 200; +constexpr int kTextureSize = 360; +constexpr int kFontSize = 80; + +int main(int argc, char *argv[]) { + initApp("spinner", 30); + + // Turn off input buffering for std::cin + std::cin.sync_with_stdio(false); + std::cin.tie(nullptr); + + Texture2D commaTexture = LoadTextureResized("../assets/img_spinner_comma.png", kTextureSize); + Texture2D spinnerTexture = LoadTextureResized("../assets/img_spinner_track.png", kTextureSize); + + float rotation = 0.0f; + std::string userInput; + + while (!WindowShouldClose()) { + BeginDrawing(); + ClearBackground(BLACK); + + rotation = fmod(rotation + kRotationRate, 360.0f); + Vector2 center = {GetScreenWidth() / 2.0f, GetScreenHeight() / 2.0f}; + const Vector2 spinnerOrigin{kTextureSize / 2.0f, kTextureSize / 2.0f}; + const Vector2 commaPosition{center.x - kTextureSize / 2.0f, center.y - kTextureSize / 2.0f}; + + // Draw rotating spinner and static comma logo + DrawTexturePro(spinnerTexture, {0, 0, (float)kTextureSize, (float)kTextureSize}, + {center.x, center.y, (float)kTextureSize, (float)kTextureSize}, + spinnerOrigin, rotation, WHITE); + DrawTextureV(commaTexture, commaPosition, WHITE); + + // Check for user input + if (std::cin.rdbuf()->in_avail() > 0) { + std::getline(std::cin, userInput); + } + + // Display either a progress bar or user input text based on input + if (!userInput.empty()) { + float yPos = GetScreenHeight() - kMargin - kProgressBarHeight; + if (std::all_of(userInput.begin(), userInput.end(), ::isdigit)) { + Rectangle bar = {center.x - kProgressBarWidth / 2.0f, yPos, kProgressBarWidth, kProgressBarHeight}; + DrawRectangleRounded(bar, 0.5f, 10, GRAY); + + int progress = std::clamp(std::stoi(userInput), 0, 100); + bar.width *= progress / 100.0f; + DrawRectangleRounded(bar, 0.5f, 10, RAYWHITE); + } else { + Vector2 textSize = MeasureTextEx(getFont(), userInput.c_str(), kFontSize, 1.0); + DrawTextEx(getFont(), userInput.c_str(), {center.x - textSize.x / 2, yPos}, kFontSize, 1.0, WHITE); + } + } + + EndDrawing(); + } + + CloseWindow(); + return 0; +} diff --git a/selfdrive/ui/raylib/util.cc b/selfdrive/ui/raylib/util.cc new file mode 100644 index 00000000000000..976b253131332b --- /dev/null +++ b/selfdrive/ui/raylib/util.cc @@ -0,0 +1,56 @@ +#include "selfdrive/ui/raylib/util.h" + +#include + +#undef GREEN +#undef RED +#undef YELLOW +#include "common/swaglog.h" +#include "system/hardware/hw.h" + +constexpr std::array(FontWeight::Count)> FONT_FILE_PATHS = { + "../assets/fonts/Inter-Black.ttf", + "../assets/fonts/Inter-Bold.ttf", + "../assets/fonts/Inter-ExtraBold.ttf", + "../assets/fonts/Inter-ExtraLight.ttf", + "../assets/fonts/Inter-Medium.ttf", + "../assets/fonts/Inter-Regular.ttf", + "../assets/fonts/Inter-SemiBold.ttf", + "../assets/fonts/Inter-Thin.ttf", +}; + +struct FontManager { + FontManager() { + for (int i = 0; i < fonts.size(); ++i) { + fonts[i] = LoadFontEx(FONT_FILE_PATHS[i], 120, nullptr, 250); + SetTextureFilter(fonts[i].texture, TEXTURE_FILTER_TRILINEAR); + } + } + + ~FontManager() { + for (auto &f : fonts) UnloadFont(f); + } + + std::array(FontWeight::Count)> fonts; +}; + +const Font& getFont(FontWeight weight) { + static FontManager font_manager; + return font_manager.fonts[(int)weight]; +} + +Texture2D LoadTextureResized(const char *fileName, int size) { + Image img = LoadImage(fileName); + ImageResize(&img, size, size); + Texture2D texture = LoadTextureFromImage(img); + SetTextureFilter(texture, TEXTURE_FILTER_TRILINEAR); + return texture; +} + +void initApp(const char *title, int fps) { + Hardware::set_display_power(true); + Hardware::set_brightness(65); + SetTraceLogLevel(LOG_NONE); + InitWindow(0, 0, title); + SetTargetFPS(fps); +} diff --git a/selfdrive/ui/raylib/util.h b/selfdrive/ui/raylib/util.h new file mode 100644 index 00000000000000..da2ec7118be5ef --- /dev/null +++ b/selfdrive/ui/raylib/util.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +#include "third_party/raylib/include/raylib.h" + +enum class FontWeight { + Normal, + Bold, + ExtraBold, + ExtraLight, + Medium, + Regular, + SemiBold, + Thin, + Count // To represent the total number of fonts +}; + +void initApp(const char *title, int fps); +const Font& getFont(FontWeight weight = FontWeight::Normal); +Texture2D LoadTextureResized(const char *fileName, int size);