Repository: commaai/openpilot
File: selfdrive/ui/qt/onroad/buttons.cc (C++)

Code before:
    engageable = eng;
    experimental_mode = cs.getExperimentalMode();
    update();
  }
}

void ExperimentalButton::paintEvent(QPaintEvent *event) {
  QPainter p(this);
  QPixmap img = experimental_mode ? experimental_img : engage_img;
  drawIcon(p, QPoint(btn_size / 2, btn_size / 2), img, QColor(0, 0, 0, 166), (isDown() || !engageable) ? 0.6 : 1.0);
}

// RecordingAudioButton
RecordingAudioButton::RecordingAudioButton(QWidget *parent) : recording_audio(false), QPushButton(parent) {
  setFixedSize(148, 148);
  QObject::connect(this, &QPushButton::clicked, [=]() { if (recording_audio) emit openSettings(2, "RecordAudio"); });
}

void RecordingAudioButton::updateState(const UIState &s) {
    if (s.scene.recording_audio != recording_audio) {
    recording_audio = s.scene.recording_audio;
    update();
  }
}

void RecordingAudioButton::paintEvent(QPaintEvent *event) {
  QPainter p(this);
  if (recording_audio) {
    p.setRenderHint(QPainter::Antialiasing);

    QPoint center(width() / 2, height() / 2);

    QColor bg(255, 255, 255, 127);
    QColor accent(255, 69, 58);

    if (isDown()) {
      accent.setAlphaF(0.7);
    }

    p.setPen(Qt::NoPen);
    p.setBrush(bg);
    p.drawEllipse(center, 74, 74);

    p.setPen(QPen(accent, 6));
    p.setBrush(Qt::NoBrush);
    p.drawEllipse(center, 42, 42);

    p.setPen(Qt::NoPen);
    p.setBrush(accent);
    p.drawEllipse(center, 22, 22);
  }

Review comment (suggestion):
Not sure if I like a large permanent dot on the onroad screen so prominently. This specific icon could also be mistaken for normal video recording. And it should be hidden behind events like DM icon. It should also match the style and size of the other icons.

How about something similar to phones where they show a small red dot and/or microphone icon in the status bar (our side bar)? That way it extends to the driver camera toggle if we want to add that.

![image](https://github.com/user-attachments/assets/ea594fbf-2af7-4b9e-acd4-6a98fca6f91c)


Code after:
#include "selfdrive/ui/qt/onroad/buttons.h"

#include <QPainter>

#include "selfdrive/ui/qt/util.h"

void drawIcon(QPainter &p, const QPoint &center, const QPixmap &img, const QBrush &bg, float opacity) {
  p.setRenderHint(QPainter::Antialiasing);
  p.setOpacity(1.0);  // bg dictates opacity of ellipse
  p.setPen(Qt::NoPen);
  p.setBrush(bg);
  p.drawEllipse(center, btn_size / 2, btn_size / 2);
  p.setOpacity(opacity);
  p.drawPixmap(center - QPoint(img.width() / 2, img.height() / 2), img);
  p.setOpacity(1.0);
}

// ExperimentalButton
ExperimentalButton::ExperimentalButton(QWidget *parent) : experimental_mode(false), engageable(false), QPushButton(parent) {
  setFixedSize(btn_size, btn_size);

  engage_img = loadPixmap("../assets/icons/chffr_wheel.png", {img_size, img_size});
  experimental_img = loadPixmap("../assets/icons/experimental.svg", {img_size, img_size});
  QObject::connect(this, &QPushButton::clicked, this, &ExperimentalButton::changeMode);
}

void ExperimentalButton::changeMode() {
  const auto cp = (*uiState()->sm)["carParams"].getCarParams();
  bool can_change = hasLongitudinalControl(cp) && params.getBool("ExperimentalModeConfirmed");
  if (can_change) {
    params.putBool("ExperimentalMode", !experimental_mode);
  }
}

void ExperimentalButton::updateState(const UIState &s) {
  const auto cs = (*s.sm)["selfdriveState"].getSelfdriveState();
  bool eng = cs.getEngageable() || cs.getEnabled();
  if ((cs.getExperimentalMode() != experimental_mode) || (eng != engageable)) {
    engageable = eng;
    experimental_mode = cs.getExperimentalMode();
    update();
  }
}

void ExperimentalButton::paintEvent(QPaintEvent *event) {
  QPainter p(this);
  QPixmap img = experimental_mode ? experimental_img : engage_img;
  drawIcon(p, QPoint(btn_size / 2, btn_size / 2), img, QColor(0, 0, 0, 166), (isDown() || !engageable) ? 0.6 : 1.0);
}