mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-07-16 14:52:15 +08:00
ui: replace qt spinner with raylib (#35059)
* use raylib spinner * remove qt spinner * use wrapper, render spinner in thread * english * fix? * match Qt font size more closely
This commit is contained in:
@@ -11,7 +11,6 @@
|
||||
|
||||
selfdrive/car/tests/test_models_segs.txt filter=lfs diff=lfs merge=lfs -text
|
||||
system/hardware/tici/updater filter=lfs diff=lfs merge=lfs -text
|
||||
selfdrive/ui/qt/spinner_larch64 filter=lfs diff=lfs merge=lfs -text
|
||||
selfdrive/ui/qt/text_larch64 filter=lfs diff=lfs merge=lfs -text
|
||||
third_party/**/*.a filter=lfs diff=lfs merge=lfs -text
|
||||
third_party/**/*.so filter=lfs diff=lfs merge=lfs -text
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
import os
|
||||
import subprocess
|
||||
from openpilot.common.basedir import BASEDIR
|
||||
|
||||
|
||||
class Spinner:
|
||||
def __init__(self):
|
||||
try:
|
||||
self.spinner_proc = subprocess.Popen(["./spinner"],
|
||||
stdin=subprocess.PIPE,
|
||||
cwd=os.path.join(BASEDIR, "selfdrive", "ui"),
|
||||
close_fds=True)
|
||||
except OSError:
|
||||
self.spinner_proc = None
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def update(self, spinner_text: str):
|
||||
if self.spinner_proc is not None:
|
||||
self.spinner_proc.stdin.write(spinner_text.encode('utf8') + b"\n")
|
||||
try:
|
||||
self.spinner_proc.stdin.flush()
|
||||
except BrokenPipeError:
|
||||
pass
|
||||
|
||||
def update_progress(self, cur: float, total: float):
|
||||
self.update(str(round(100 * cur / total)))
|
||||
|
||||
def close(self):
|
||||
if self.spinner_proc is not None:
|
||||
self.spinner_proc.kill()
|
||||
try:
|
||||
self.spinner_proc.communicate(timeout=2.)
|
||||
except subprocess.TimeoutExpired:
|
||||
print("WARNING: failed to kill spinner")
|
||||
self.spinner_proc = None
|
||||
|
||||
def __del__(self):
|
||||
self.close()
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
self.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import time
|
||||
with Spinner() as s:
|
||||
s.update("Spinner text")
|
||||
time.sleep(5.0)
|
||||
print("gone")
|
||||
time.sleep(5.0)
|
||||
@@ -4,7 +4,6 @@ moc_*
|
||||
translations/main_test_en.*
|
||||
|
||||
_text
|
||||
_spinner
|
||||
|
||||
ui
|
||||
mui
|
||||
|
||||
@@ -66,9 +66,8 @@ if GetOption('extras'):
|
||||
|
||||
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)
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
#include "selfdrive/ui/qt/spinner.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include <QApplication>
|
||||
#include <QGridLayout>
|
||||
#include <QPainter>
|
||||
#include <QString>
|
||||
#include <QTransform>
|
||||
|
||||
#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();
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
#include <array>
|
||||
|
||||
#include <QLabel>
|
||||
#include <QPixmap>
|
||||
#include <QProgressBar>
|
||||
#include <QSocketNotifier>
|
||||
#include <QVariantAnimation>
|
||||
#include <QWidget>
|
||||
|
||||
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<QPixmap, spinner_fps> 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);
|
||||
};
|
||||
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:81d7073d16e8ddc40d4d81fc88f8fc11c434df241593b455e2787935371383e4
|
||||
size 3821728
|
||||
@@ -1,7 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
if [ -f /TICI ] && [ ! -f _spinner ]; then
|
||||
cp qt/spinner_larch64 _spinner
|
||||
fi
|
||||
|
||||
exec ./_spinner "$1"
|
||||
@@ -7,10 +7,10 @@ from pathlib import Path
|
||||
from datetime import datetime, timedelta, UTC
|
||||
from openpilot.common.api import api_get
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.spinner import Spinner
|
||||
from openpilot.selfdrive.selfdrived.alertmanager import set_offroad_alert
|
||||
from openpilot.system.hardware import HARDWARE, PC
|
||||
from openpilot.system.hardware.hw import Paths
|
||||
from openpilot.system.ui.spinner import Spinner
|
||||
from openpilot.common.swaglog import cloudlog
|
||||
|
||||
|
||||
|
||||
@@ -5,10 +5,10 @@ from pathlib import Path
|
||||
|
||||
# NOTE: Do NOT import anything here that needs be built (e.g. params)
|
||||
from openpilot.common.basedir import BASEDIR
|
||||
from openpilot.common.spinner import Spinner
|
||||
from openpilot.common.text_window import TextWindow
|
||||
from openpilot.system.hardware import HARDWARE, AGNOS
|
||||
from openpilot.common.swaglog import cloudlog, add_file_handler
|
||||
from openpilot.system.hardware import HARDWARE, AGNOS
|
||||
from openpilot.system.ui.spinner import Spinner
|
||||
from openpilot.system.version import get_build_metadata
|
||||
|
||||
MAX_CACHE_SIZE = 4e9 if "CI" in os.environ else 2e9
|
||||
@@ -88,7 +88,7 @@ def build(spinner: Spinner, dirty: bool = False, minimal: bool = False) -> None:
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
spinner = Spinner()
|
||||
spinner.update_progress(0, 100)
|
||||
build_metadata = get_build_metadata()
|
||||
build(spinner, build_metadata.openpilot.is_dirty, minimal = AGNOS)
|
||||
with Spinner() as spinner:
|
||||
spinner.update_progress(0, 100)
|
||||
build_metadata = get_build_metadata()
|
||||
build(spinner, build_metadata.openpilot.is_dirty, minimal = AGNOS)
|
||||
|
||||
+56
-9
@@ -2,6 +2,7 @@
|
||||
import pyray as rl
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
|
||||
from openpilot.common.basedir import BASEDIR
|
||||
from openpilot.system.ui.lib.application import gui_app
|
||||
@@ -13,8 +14,8 @@ PROGRESS_BAR_HEIGHT = 20
|
||||
DEGREES_PER_SECOND = 360.0 # one full rotation per second
|
||||
MARGIN_H = 100
|
||||
TEXTURE_SIZE = 360
|
||||
FONT_SIZE = 88
|
||||
LINE_HEIGHT = 96
|
||||
FONT_SIZE = 96
|
||||
LINE_HEIGHT = 104
|
||||
DARKGRAY = (55, 55, 55, 255)
|
||||
|
||||
|
||||
@@ -22,7 +23,7 @@ def clamp(value, min_value, max_value):
|
||||
return max(min(value, max_value), min_value)
|
||||
|
||||
|
||||
class Spinner:
|
||||
class SpinnerRenderer:
|
||||
def __init__(self):
|
||||
self._comma_texture = gui_app.load_texture_from_image(os.path.join(BASEDIR, "selfdrive/assets/img_spinner_comma.png"), TEXTURE_SIZE, TEXTURE_SIZE)
|
||||
self._spinner_texture = gui_app.load_texture_from_image(os.path.join(BASEDIR, "selfdrive/assets/img_spinner_track.png"), TEXTURE_SIZE, TEXTURE_SIZE,
|
||||
@@ -70,7 +71,7 @@ class Spinner:
|
||||
spinner_origin, self._rotation, rl.WHITE)
|
||||
rl.draw_texture_v(self._comma_texture, comma_position, rl.WHITE)
|
||||
|
||||
# Display progress bar or text based on user input
|
||||
# Display the progress bar or text based on user input
|
||||
if progress is not None:
|
||||
bar = rl.Rectangle(center.x - PROGRESS_BAR_WIDTH / 2.0, y_pos, PROGRESS_BAR_WIDTH, PROGRESS_BAR_HEIGHT)
|
||||
rl.draw_rectangle_rounded(bar, 1, 10, DARKGRAY)
|
||||
@@ -84,9 +85,55 @@ class Spinner:
|
||||
FONT_SIZE, 0.0, rl.WHITE)
|
||||
|
||||
|
||||
class Spinner:
|
||||
def __init__(self):
|
||||
self._renderer: SpinnerRenderer | None = None
|
||||
self._stop_event = threading.Event()
|
||||
self._thread = threading.Thread(target=self._run)
|
||||
self._thread.start()
|
||||
|
||||
# wait for the renderer to be initialized
|
||||
while self._renderer is None and self._thread.is_alive():
|
||||
time.sleep(0.01)
|
||||
|
||||
def update(self, spinner_text: str):
|
||||
if self._renderer is not None:
|
||||
self._renderer.set_text(spinner_text)
|
||||
|
||||
def update_progress(self, cur: float, total: float):
|
||||
self.update(str(round(100 * cur / total)))
|
||||
|
||||
def _run(self):
|
||||
if os.getenv("CI") is not None:
|
||||
return
|
||||
gui_app.init_window("Spinner")
|
||||
self._renderer = renderer = SpinnerRenderer()
|
||||
try:
|
||||
for _ in gui_app.render():
|
||||
if self._stop_event.is_set():
|
||||
break
|
||||
renderer.render()
|
||||
finally:
|
||||
gui_app.close()
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def close(self):
|
||||
if self._thread.is_alive():
|
||||
self._stop_event.set()
|
||||
self._thread.join(timeout=2.0)
|
||||
if self._thread.is_alive():
|
||||
print("WARNING: failed to join spinner thread")
|
||||
|
||||
def __del__(self):
|
||||
self.close()
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
gui_app.init_window("Spinner")
|
||||
spinner = Spinner()
|
||||
spinner.set_text("Spinner text")
|
||||
for _ in gui_app.render():
|
||||
spinner.render()
|
||||
with Spinner() as s:
|
||||
s.update("Spinner text")
|
||||
time.sleep(5)
|
||||
|
||||
Reference in New Issue
Block a user