port C++ UI from broken: ready/splash, ClearPilot menu, onroad widgets, nightrider
Wholesale port of the 10 UI files (main.cc, ui.{cc,h}, onroad.{cc,h},
home.{cc,h}, window.{cc,h}, ready.{cc,h}, SConscript). sidebar.{cc,h}
unchanged.
Brings in:
- splash/ready screen rendered via Qt directly (replaces stock home)
- ready page shown when started but gear=park
- ClearPilot offroad menu (Home/Dashcam/Debug panels) replacing stock
- onroad UI: speed widget reading ClearpilotSpeedDisplay (gpsLocation
via speed_logicd), speed-limit widget, cruise-vs-limit warning sign
- nightrider mode: camera suppressed, lane lines/path drawn as 2px
outlines only (ScreenDisplayMode 1 or 4)
- screen power: ScreenDisplayMode 3 forces setAwake(false)
- ignition off blanks screen immediately (tap still wakes)
- Qt-based RPC widget-tree dump server at ipc:///tmp/clearpilot_ui_rpc
- crash handler in main.cc with stack trace
Deviation from broken (3 sites): no_lat_lane_change is read from
paramsMemory (controlsd writes it there in baseline) instead of broken's
custom cereal field frogpilotCarControl.noLatLaneChange. Keeps the
existing self-driving wiring intact.
SConscript drops screenrecorder.cc from qt_src (broken did the same;
omx_encoder.cc still built for dashcamd's future link).
Build clean. UI hasn't been launched yet — that comes after the
controlsd button-handler commit lands.
Translation .ts files are auto-regenerated by Qt's lupdate; included.
This commit is contained in:
+74
-13
@@ -30,11 +30,12 @@ ReadyWindow::ReadyWindow(QWidget *parent) : QWidget(parent) {
|
||||
|
||||
timer = new QTimer(this);
|
||||
timer->callOnTimeout(this, &ReadyWindow::refresh);
|
||||
uptime.start();
|
||||
}
|
||||
|
||||
void ReadyWindow::showEvent(QShowEvent *event) {
|
||||
refresh();
|
||||
timer->start(5 * 1000);
|
||||
timer->start(2 * 1000);
|
||||
}
|
||||
|
||||
void ReadyWindow::hideEvent(QHideEvent *event) {
|
||||
@@ -43,34 +44,94 @@ void ReadyWindow::hideEvent(QHideEvent *event) {
|
||||
|
||||
void ReadyWindow::paintEvent(QPaintEvent *event) {
|
||||
QPainter painter(this);
|
||||
QPixmap *img_shown = nullptr;
|
||||
painter.fillRect(rect(), Qt::black);
|
||||
|
||||
if (is_hot) {
|
||||
if (img_hot.isNull()) {
|
||||
img_hot.load("/data/openpilot/selfdrive/clearpilot/theme/clearpilot/images/hot.png");
|
||||
}
|
||||
img_shown = &img_hot;
|
||||
int x = (width() - img_hot.width()) / 2;
|
||||
int y = (height() - img_hot.height()) / 2;
|
||||
painter.drawPixmap(x, y, img_hot);
|
||||
} else {
|
||||
if (img_ready.isNull()) {
|
||||
img_ready.load("/data/openpilot/selfdrive/clearpilot/theme/clearpilot/images/ready.png");
|
||||
// Boot logo — same rotation as spinner (bg.jpg is pre-rotated 90° CW for framebuffer)
|
||||
if (img_bg.isNull()) {
|
||||
QPixmap raw("/usr/comma/bg.jpg");
|
||||
if (!raw.isNull()) {
|
||||
QTransform rot;
|
||||
rot.rotate(-90);
|
||||
img_bg = raw.transformed(rot);
|
||||
}
|
||||
}
|
||||
if (!img_bg.isNull()) {
|
||||
QPixmap scaled = img_bg.scaled(size(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
int x = (width() - scaled.width()) / 2;
|
||||
int y = (height() - scaled.height()) / 2;
|
||||
painter.drawPixmap(x, y, scaled);
|
||||
}
|
||||
img_shown = &img_ready;
|
||||
}
|
||||
|
||||
int x = (width() - img_shown->width()) / 2;
|
||||
int y = (height() - img_shown->height()) / 2;
|
||||
painter.drawPixmap(x, y, *img_shown);
|
||||
if (error_msg.isEmpty() && !has_driven) {
|
||||
// "READY!" 8-bit text sprite, 15% below center — only before first drive
|
||||
static QPixmap ready_text("/data/openpilot/selfdrive/clearpilot/theme/clearpilot/images/ready_text.png");
|
||||
if (!ready_text.isNull()) {
|
||||
int tx = (width() - ready_text.width()) / 2;
|
||||
int ty = height() / 2 + height() * 15 / 100;
|
||||
painter.drawPixmap(tx, ty, ready_text);
|
||||
}
|
||||
} else {
|
||||
// Error state: red text at 25% below center
|
||||
QFont font("Inter", 50, QFont::Bold);
|
||||
painter.setFont(font);
|
||||
painter.setPen(QColor(0xFF, 0x44, 0x44));
|
||||
int ty = height() / 2 + height() * 25 / 100;
|
||||
QRect text_rect(0, ty, width(), 100);
|
||||
painter.drawText(text_rect, Qt::AlignHCenter | Qt::AlignTop, error_msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ReadyWindow::refresh() {
|
||||
bool changed = false;
|
||||
|
||||
// Temperature check
|
||||
std::string bytes = params.get("Offroad_TemperatureTooHigh");
|
||||
bool was_hot = is_hot;
|
||||
if (!bytes.empty()) {
|
||||
auto doc = QJsonDocument::fromJson(bytes.data());
|
||||
is_hot = true;
|
||||
cur_temp = doc["extra"].toString();
|
||||
update();
|
||||
} else if (is_hot) {
|
||||
} else {
|
||||
is_hot = false;
|
||||
update();
|
||||
}
|
||||
if (is_hot != was_hot) changed = true;
|
||||
|
||||
// Error state checks (only when not hot — hot has its own display)
|
||||
if (!is_hot) {
|
||||
QString prev_error = error_msg;
|
||||
|
||||
// Panda check — same logic as sidebar, with 10s grace period on startup
|
||||
if (uptime.elapsed() > 10000 &&
|
||||
uiState()->scene.pandaType == cereal::PandaState::PandaType::UNKNOWN) {
|
||||
error_msg = "PANDA NOT CONNECTED";
|
||||
}
|
||||
// Car unrecognized check
|
||||
else if (!params.get("Offroad_CarUnrecognized").empty()) {
|
||||
error_msg = "CAR NOT RECOGNIZED";
|
||||
}
|
||||
else {
|
||||
error_msg = "";
|
||||
}
|
||||
|
||||
if (error_msg != prev_error) changed = true;
|
||||
}
|
||||
|
||||
// Reset has_driven on ignition off→on (power cycle)
|
||||
bool started = uiState()->scene.started;
|
||||
if (!last_started && started) {
|
||||
has_driven = false;
|
||||
changed = true;
|
||||
}
|
||||
last_started = started;
|
||||
|
||||
if (changed) update();
|
||||
}
|
||||
Reference in New Issue
Block a user