默认暗色模式,添加值显示开关

This commit is contained in:
2025-12-16 19:18:21 +08:00
parent a1f7f337c2
commit 5b319cb76d
14 changed files with 372 additions and 75 deletions

View File

@@ -100,21 +100,21 @@ set(TOUCHSENSOR_HEADERS
qt6_add_resources(APP_RESOURCES resources.qrc) qt6_add_resources(APP_RESOURCES resources.qrc)
# add_executable(${PROJECT_NAME} WIN32 add_executable(${PROJECT_NAME} WIN32
# ${COMPONENT_SOURCES} ${COMPONENT_SOURCES}
# ${UTILITY_SOURCES} ${UTILITY_SOURCES}
# ${TOUCHSENSOR_HEADERS} ${TOUCHSENSOR_HEADERS}
# ${BASE_SOURCES} ${BASE_SOURCES}
# main.cc main.cc
# ) )
add_executable(${PROJECT_NAME} #add_executable(${PROJECT_NAME}
${COMPONENT_SOURCES} # ${COMPONENT_SOURCES}
${UTILITY_SOURCES} # ${UTILITY_SOURCES}
${TOUCHSENSOR_HEADERS} # ${TOUCHSENSOR_HEADERS}
${BASE_SOURCES} # ${BASE_SOURCES}
main.cc # main.cc
) #)
target_sources(${PROJECT_NAME} PRIVATE ${APP_RESOURCES}) target_sources(${PROJECT_NAME} PRIVATE ${APP_RESOURCES})
target_include_directories(${PROJECT_NAME} target_include_directories(${PROJECT_NAME}
PRIVATE PRIVATE

View File

@@ -1,31 +1,37 @@
#pragma once #pragma once
#include <creeper-qt/utility/theme/theme.hh> #include <creeper-qt/utility/theme/theme.hh>
#include <qwidget.h> #include <creeper-qt/utility/wrapper/mutable-value.hh>
#include <string_view> #include <functional>
#include <memory>
template <typename T> #include <qwidget.h>
using raw_pointer = T*; #include <string_view>
#include <tuple>
struct NavComponentState { #include <vector>
creeper::ThemeManager& manager;
std::function<void(int, const std::string_view&)> switch_callback; template <typename T>
std::vector<std::tuple<std::string_view, std::string_view>> buttons_context; using raw_pointer = T*;
std::function<void(int)> stacked_callback;
}; struct NavComponentState {
creeper::ThemeManager& manager;
auto NavComponent(NavComponentState&) noexcept -> raw_pointer<QWidget>; std::function<void(int, const std::string_view&)> switch_callback;
std::vector<std::tuple<std::string_view, std::string_view>> buttons_context;
struct ViewComponentState { std::function<void(int)> stacked_callback;
creeper::ThemeManager& manager; std::shared_ptr<creeper::MutableValue<bool>> heatmap_show_numbers;
}; };
auto ViewComponent(ViewComponentState&) noexcept -> raw_pointer<QWidget>;
auto NavComponent(NavComponentState&) noexcept -> raw_pointer<QWidget>;
struct SettingComponentState {
creeper::ThemeManager& manager; struct ViewComponentState {
}; creeper::ThemeManager& manager;
auto SettingComponent(SettingComponentState&) noexcept -> raw_pointer<QWidget>; };
auto ViewComponent(ViewComponentState&) noexcept -> raw_pointer<QWidget>;
struct SettingComponentState {
creeper::ThemeManager& manager;
};
auto SettingComponent(SettingComponentState&) noexcept -> raw_pointer<QWidget>;
struct HandViewComponentState { struct HandViewComponentState {
creeper::ThemeManager& manager; creeper::ThemeManager& manager;
}; };
@@ -33,3 +39,4 @@ auto HandViewComponent(HandViewComponentState&) noexcept -> raw_pointer<QWidget>
// 让其他模块可触发视图层的串口/配置刷新 // 让其他模块可触发视图层的串口/配置刷新
void RefreshProfilesForView(); void RefreshProfilesForView();
std::shared_ptr<creeper::MutableValue<bool>> HeatmapNumberVisibilityContext();

View File

@@ -52,6 +52,14 @@ void BasicPlot::set_data(const QVector<PointData>& data)const {
pimpl->set_data(data); pimpl->set_data(data);
} }
void BasicPlot::set_labels_visible(bool visible) const {
pimpl->set_labels_visible(visible);
}
bool BasicPlot::labels_visible() const {
return pimpl->labels_visible();
}
bool BasicPlot::is_initialized() const { bool BasicPlot::is_initialized() const {
return pimpl->is_plot_initialized(); return pimpl->is_plot_initialized();
} }
@@ -86,4 +94,4 @@ using namespace creeper;
void creeper::HeatMapPlot::paintEvent(QPaintEvent* event) { void creeper::HeatMapPlot::paintEvent(QPaintEvent* event) {
BasicPlot::paintEvent(event); BasicPlot::paintEvent(event);
} }

View File

@@ -43,7 +43,9 @@ public:
void set_matrix_size(const int& w, const int& h)const; void set_matrix_size(const int& w, const int& h)const;
void set_data(const QVector<PointData>& data)const; void set_data(const QVector<PointData>& data)const;
void set_color_gradient_range(const double& min, const double& max)const; void set_color_gradient_range(const double& min, const double& max)const;
void set_labels_visible(bool visible) const;
QSize get_matrix_size() const; QSize get_matrix_size() const;
bool labels_visible() const;
bool is_initialized() const; bool is_initialized() const;
public slots: public slots:

View File

@@ -19,7 +19,13 @@
using namespace creeper::plot_widget::internal; using namespace creeper::plot_widget::internal;
struct BasicPlot::Impl { struct BasicPlot::Impl {
explicit Impl(BasicPlot& self) noexcept : self{self}, initialized(false), matrix_size(QSize{3, 4}) {} explicit Impl(BasicPlot& self) noexcept
: matrix_size(QSize{3, 4})
, color_min(0.0)
, color_max(800.0)
, show_labels(false)
, initialized(false)
, self{self} { }
public: public:
std::optional<creeper::ColorScheme> scheme; std::optional<creeper::ColorScheme> scheme;
@@ -41,10 +47,15 @@ public:
auto set_matrix_size(const QSize& size) -> void { auto set_matrix_size(const QSize& size) -> void {
matrix_size = size; matrix_size = size;
const int expected = std::max(0, matrix_size.width() * matrix_size.height());
last_values.assign(static_cast<std::size_t>(expected), 0.0);
if (initialized) { if (initialized) {
reset_plot(); reset_plot();
if (!data_points.isEmpty()) { if (!data_points.isEmpty()) {
set_data(data_points); set_data(data_points);
} else if (show_labels) {
sync_labels(last_values);
self.replot();
} }
} }
} }
@@ -75,6 +86,18 @@ public:
} }
} }
auto set_labels_visible(bool visible) -> void {
show_labels = visible;
if (initialized) {
sync_labels(last_values);
self.replot();
}
}
auto labels_visible() const -> bool {
return show_labels;
}
auto initialize_plot() -> void { auto initialize_plot() -> void {
if (initialized) return; if (initialized) return;
@@ -149,12 +172,12 @@ public:
auto reset_plot() -> void { auto reset_plot() -> void {
// 清除所有绘图元素 // 清除所有绘图元素
clear_labels();
self.clearPlottables(); self.clearPlottables();
self.clearGraphs(); self.clearGraphs();
self.clearItems(); self.clearItems();
self.clearFocus(); self.clearFocus();
color_map = nullptr; color_map = nullptr;
cell_labels.clear();
// 重新初始化 // 重新初始化
initialized = false; initialized = false;
@@ -163,7 +186,7 @@ public:
auto update_plot_data() -> void { auto update_plot_data() -> void {
if (!initialized || !color_map) return; if (!initialized || !color_map) return;
ensure_labels(); // ensure_labels();
const int width = matrix_size.width(); const int width = matrix_size.width();
const int height = matrix_size.height(); const int height = matrix_size.height();
@@ -182,7 +205,8 @@ public:
} }
} }
update_label_values(values); last_values = values;
sync_labels(last_values);
// 重绘 // 重绘
self.replot(); self.replot();
@@ -201,8 +225,10 @@ private:
QString ylabel; QString ylabel;
QSize matrix_size; QSize matrix_size;
QVector<PointData> data_points; QVector<PointData> data_points;
std::vector<double> last_values;
double color_min = 0.0; double color_min = 0.0;
double color_max = 800.0; double color_max = 800.0;
bool show_labels = false;
bool initialized; bool initialized;
BasicPlot& self; BasicPlot& self;
QCPColorScale* color_scale = nullptr; QCPColorScale* color_scale = nullptr;
@@ -217,7 +243,7 @@ private:
text_color = scheme->on_surface; text_color = scheme->on_surface;
} }
} }
label_text_color = QColor(0, 0, 0); // 固定黑色 label_text_color = text_color;
const auto pen = QPen(text_color); const auto pen = QPen(text_color);
@@ -293,7 +319,6 @@ private:
void update_label_values(const std::vector<double>& values) { void update_label_values(const std::vector<double>& values) {
const int width = matrix_size.width(); const int width = matrix_size.width();
const int height = matrix_size.height(); const int height = matrix_size.height();
const double range = std::max(color_max - color_min, 1.0);
const int expected = width * height; const int expected = width * height;
for (int idx = 0; idx < expected && idx < cell_labels.size(); ++idx) { for (int idx = 0; idx < expected && idx < cell_labels.size(); ++idx) {
auto* label = cell_labels[idx]; auto* label = cell_labels[idx];
@@ -309,6 +334,18 @@ private:
label->position->setCoords(x + 0.5, y + 0.5); label->position->setCoords(x + 0.5, y + 0.5);
} }
} }
void sync_labels(const std::vector<double>& values) {
if (!initialized) {
return;
}
if (show_labels) {
ensure_labels();
update_label_values(values);
} else if (!cell_labels.isEmpty()) {
clear_labels();
}
}
}; };
#endif // TOUCHSENSOR_HEATMAP_IMPL_HH #endif // TOUCHSENSOR_HEATMAP_IMPL_HH

View File

@@ -4,6 +4,7 @@
#include "line_chart.hh" #include "line_chart.hh"
#include <QLinearGradient> #include <QLinearGradient>
#include <QMargins>
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
#include <limits> #include <limits>
@@ -86,6 +87,16 @@ void LinePlot::initialize_plot() {
graph_->setAntialiased(true); graph_->setAntialiased(true);
graph_->setAdaptiveSampling(true); graph_->setAdaptiveSampling(true);
current_label_ = new QCPItemText(this);
current_label_->position->setType(QCPItemPosition::ptAxisRectRatio);
current_label_->setPositionAlignment(Qt::AlignRight | Qt::AlignTop);
current_label_->position->setCoords(0.98, 0.04);
current_label_->setPadding(QMargins(6, 3, 6, 3));
current_label_->setBrush(QColor(0, 0, 0, 50));
current_label_->setPen(Qt::NoPen);
current_label_->setLayer("overlay");
current_label_->setClipToAxisRect(true);
initialized_ = true; initialized_ = true;
apply_theme(); apply_theme();
update_graph(); update_graph();
@@ -100,6 +111,7 @@ void LinePlot::apply_theme() {
QColor line_color{ 16, 54, 128 }; QColor line_color{ 16, 54, 128 };
QColor text_color{ 30, 30, 30 }; QColor text_color{ 30, 30, 30 };
QColor bg_color{ 232, 238, 248 }; QColor bg_color{ 232, 238, 248 };
QColor label_bg = QColor(0, 0, 0, 50);
if (scheme_.has_value()) { if (scheme_.has_value()) {
if (scheme_->primary.isValid()) { if (scheme_->primary.isValid()) {
line_color = scheme_->primary; line_color = scheme_->primary;
@@ -116,6 +128,11 @@ void LinePlot::apply_theme() {
else if (scheme_->surface.isValid()) { else if (scheme_->surface.isValid()) {
bg_color = scheme_->surface; bg_color = scheme_->surface;
} }
if (scheme_->surface_container.isValid()) {
label_bg = QColor(scheme_->surface_container.red(),
scheme_->surface_container.green(),
scheme_->surface_container.blue(), 90);
}
} }
QColor grid_color = bg_color; QColor grid_color = bg_color;
@@ -150,6 +167,14 @@ void LinePlot::apply_theme() {
scatter.setSize(7); scatter.setSize(7);
graph_->setScatterStyle(scatter); graph_->setScatterStyle(scatter);
} }
if (current_label_) {
current_label_->setColor(text_color);
current_label_->setBrush(label_bg);
QFont f = current_label_->font();
f.setBold(true);
current_label_->setFont(f);
}
} }
void LinePlot::update_graph() { void LinePlot::update_graph() {
@@ -164,6 +189,9 @@ void LinePlot::update_graph() {
if (points_.isEmpty()) { if (points_.isEmpty()) {
graph_->data()->clear(); graph_->data()->clear();
reset_graph_range(); reset_graph_range();
if (current_label_) {
current_label_->setText(QStringLiteral("--"));
}
replot(); replot();
return; return;
} }
@@ -203,6 +231,11 @@ void LinePlot::update_graph() {
yAxis->setRange(min_val - padding, max_val + padding); yAxis->setRange(min_val - padding, max_val + padding);
} }
if (current_label_) {
const double last_val = points_.back().y();
current_label_->setText(QString::number(last_val, 'f', 1));
}
replot(); replot();
} }

View File

@@ -11,6 +11,7 @@
#include "qcustomplot/qcustomplot.h" #include "qcustomplot/qcustomplot.h"
#include <QPaintEvent> #include <QPaintEvent>
#include <QPointF> #include <QPointF>
#include <QString>
#include <concepts> #include <concepts>
#include <optional> #include <optional>
#include <qsize.h> #include <qsize.h>
@@ -43,6 +44,7 @@ class LinePlot: public QCustomPlot {
bool initialized_ = false; bool initialized_ = false;
std::optional<ColorScheme> scheme_; std::optional<ColorScheme> scheme_;
QCPGraph* graph_ = nullptr; QCPGraph* graph_ = nullptr;
QCPItemText* current_label_ = nullptr;
int max_points_ = 240; int max_points_ = 240;
double default_y_range_ = 100.0; double default_y_range_ = 100.0;
}; };

View File

@@ -6,6 +6,10 @@
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
#include <vector> #include <vector>
#include <QLinearGradient>
#include <QPainter>
#include <QPainterPath>
#include <QPointF>
using creeper::vector_widget::internal::VectorPlot; using creeper::vector_widget::internal::VectorPlot;
@@ -45,6 +49,116 @@ void VectorPlot::paintEvent(QPaintEvent* event) {
initialize_plot(); initialize_plot();
} }
QCustomPlot::paintEvent(event); QCustomPlot::paintEvent(event);
// Custom glossy arrow rendering
const auto rect = axisRect();
if (!rect) {
return;
}
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
const QRectF plot_rect = rect->rect();
const double side_pad = plot_rect.width() * 0.24;
QRectF area = plot_rect.adjusted(side_pad, 8.0, -side_pad, -8.0);
const double cx_canvas = area.center().x();
const double cy_canvas = area.center().y();
const double scale = 0.72; // overall shrink to make it slimmer
area.setWidth(area.width() * scale);
area.setHeight(area.height() * scale);
area.moveCenter(QPointF(cx_canvas, cy_canvas));
if (area.width() <= 0.0 || area.height() <= 0.0) {
return;
}
const double w = area.width();
const double h = area.height();
const double cx = area.center().x();
const double shaft_w = w * 0.18;
const double shaft_r = shaft_w * 0.25;
const double head_h = h * 0.36;
const double head_w = w * 0.52;
const double shaft_h = h - head_h;
const double shaft_x = cx - shaft_w * 0.5;
const double shaft_y = area.top();
const double head_base_y = shaft_y + shaft_h - shaft_r * 0.4; // overlap slightly to avoid seam
QPainterPath shaft;
shaft.addRoundedRect(QRectF(shaft_x, shaft_y, shaft_w, shaft_h), shaft_r, shaft_r);
QPainterPath head;
head.moveTo(cx - head_w * 0.5, head_base_y);
head.lineTo(cx + head_w * 0.5, head_base_y);
head.lineTo(cx, area.bottom());
head.closeSubpath();
QPainterPath arrow = shaft.united(head);
// Vibrant orange palette (fixed)
QColor base_color(255, 140, 0);
QColor highlight = base_color.lighter(165);
QColor mid = base_color;
QColor shadow = base_color.darker(180);
QLinearGradient body_grad(area.topLeft(), QPointF(area.left(), area.bottom()));
body_grad.setColorAt(0.0, highlight);
body_grad.setColorAt(0.28, mid);
body_grad.setColorAt(0.72, base_color.darker(110));
body_grad.setColorAt(1.0, shadow);
const double angle_deg = std::atan2(arrow_dir_.y(), arrow_dir_.x()) * 180.0 / 3.14159265358979323846 - 90.0;
QTransform transform;
transform.translate(cx, (shaft_y + head_base_y + area.bottom()) / 3.0); // approximate center
transform.rotate(angle_deg);
transform.translate(-cx, -(shaft_y + head_base_y + area.bottom()) / 3.0);
auto draw_with_transform = [&](const QPainterPath& path, const QBrush& brush, const QPen* pen = nullptr) {
const QPainterPath rotated = transform.map(path);
painter.setBrush(brush);
if (pen) {
painter.setPen(*pen);
}
else {
painter.setPen(Qt::NoPen);
}
painter.drawPath(rotated);
};
draw_with_transform(arrow, QBrush(body_grad));
// Gloss highlight
QPainterPath gloss;
const double gloss_w = shaft_w * 0.42;
QRectF gloss_rect(cx - gloss_w * 0.5, shaft_y + h * 0.04, gloss_w, shaft_h * 0.5);
gloss.addRoundedRect(gloss_rect, gloss_w * 0.4, gloss_w * 0.4);
QLinearGradient gloss_grad(gloss_rect.topLeft(), gloss_rect.bottomLeft());
QColor gloss_hi = Qt::white;
gloss_hi.setAlpha(190);
QColor gloss_lo = Qt::white;
gloss_lo.setAlpha(40);
gloss_grad.setColorAt(0.0, gloss_hi);
gloss_grad.setColorAt(1.0, gloss_lo);
draw_with_transform(gloss, QBrush(gloss_grad));
// Head specular highlights
const double spec_w = head_w * 0.18;
QRectF spec_left(cx - head_w * 0.32, head_base_y + head_h * 0.08, spec_w, head_h * 0.2);
QRectF spec_right(cx + head_w * 0.14, head_base_y + head_h * 0.1, spec_w * 0.8, head_h * 0.2);
auto paint_spec = [&](const QRectF& r) {
QPainterPath p;
p.addRoundedRect(r, r.width() * 0.4, r.height() * 0.6);
QLinearGradient g(r.topLeft(), r.bottomLeft());
QColor hi = Qt::white;
hi.setAlpha(180);
QColor lo = Qt::white;
lo.setAlpha(30);
g.setColorAt(0.0, hi);
g.setColorAt(1.0, lo);
draw_with_transform(p, QBrush(g));
};
paint_spec(spec_left);
paint_spec(spec_right);
} }
void VectorPlot::initialize_plot() { void VectorPlot::initialize_plot() {
@@ -109,6 +223,7 @@ void VectorPlot::apply_color_scheme() {
strong_pen.setJoinStyle(Qt::MiterJoin); strong_pen.setJoinStyle(Qt::MiterJoin);
if (primary_arrow_) { if (primary_arrow_) {
primary_arrow_->setPen(strong_pen); primary_arrow_->setPen(strong_pen);
primary_arrow_->setVisible(false); // 使用自绘 3D 箭头
} }
} }
@@ -178,6 +293,7 @@ void VectorPlot::update_vectors() {
} }
dir_x /= dir_len; dir_x /= dir_len;
dir_y /= dir_len; dir_y /= dir_len;
arrow_dir_ = QPointF(dir_x, dir_y);
const double arrow_len = 0.48 * std::min(width, height); // 稍长的指针 const double arrow_len = 0.48 * std::min(width, height); // 稍长的指针
const double tail_ratio = 0.25; // 穿过中心的尾巴略长 const double tail_ratio = 0.25; // 穿过中心的尾巴略长
const double tail_len = arrow_len * tail_ratio; const double tail_len = arrow_len * tail_ratio;
@@ -189,7 +305,7 @@ void VectorPlot::update_vectors() {
if (primary_arrow_) { if (primary_arrow_) {
primary_arrow_->start->setCoords(cx - dir_x * tail_len, cy - dir_y * tail_len); primary_arrow_->start->setCoords(cx - dir_x * tail_len, cy - dir_y * tail_len);
primary_arrow_->end->setCoords(cx + dir_x * head_base, cy + dir_y * head_base); primary_arrow_->end->setCoords(cx + dir_x * head_base, cy + dir_y * head_base);
primary_arrow_->setVisible(true); primary_arrow_->setVisible(false); // 位置保留以备需要,但当前使用自绘 3D 箭头
} }
replot(); replot();

View File

@@ -14,6 +14,7 @@
#include <QPaintEvent> #include <QPaintEvent>
#include <concepts> #include <concepts>
#include <qsize.h> #include <qsize.h>
#include <qpoint.h>
#include <qvector.h> #include <qvector.h>
namespace creeper { namespace creeper {
@@ -39,12 +40,13 @@ class VectorPlot : public QCustomPlot {
void ensure_arrows(); void ensure_arrows();
void apply_color_scheme(); void apply_color_scheme();
QSize matrix_size_{ 3, 4 }; QSize matrix_size_{ 3, 4 };
QVector<PointData> data_points_; QVector<PointData> data_points_;
bool initialized_ = false; bool initialized_ = false;
std::optional<ColorScheme> scheme_; std::optional<ColorScheme> scheme_;
QColor arrow_color_{ 16, 54, 128 }; // 深蓝色 QColor arrow_color_{ 16, 54, 128 }; // 深蓝色
QCPItemLine* primary_arrow_ = nullptr; QPointF arrow_dir_{ 0.0, 1.0 };
QCPItemLine* primary_arrow_ = nullptr;
void ensure_primary_arrow(); void ensure_primary_arrow();
}; };

View File

@@ -27,10 +27,12 @@ using nlohmann::json;
// void dump_compact_json(...) // void dump_compact_json(...)
// json serialize_tactile_frame(const DecodedFrame& frame) { ... } // json serialize_tactile_frame(const DecodedFrame& frame) { ... }
std::string payload_to_csv_row(const std::vector<std::uint8_t>& payload) { std::string payload_to_csv_row(const std::string& timestamp,
// Combine every 2 bytes (little-endian) into one 16-bit value, output in decimal. const std::vector<std::uint8_t>& payload) {
// First column: receive local time (YYYYMMDDHHMMSS). Then payload every 2 bytes -> uint16.
std::ostringstream oss; std::ostringstream oss;
bool first = true; oss << timestamp;
bool first = false; // timestamp already placed
for (std::size_t idx = 0; idx + 1U < payload.size(); idx += 2U) { for (std::size_t idx = 0; idx + 1U < payload.size(); idx += 2U) {
const auto value = const auto value =
static_cast<std::uint16_t>(payload[idx]) | static_cast<std::uint16_t>(payload[idx + 1U] << 8U); static_cast<std::uint16_t>(payload[idx]) | static_cast<std::uint16_t>(payload[idx + 1U] << 8U);
@@ -49,6 +51,28 @@ std::string payload_to_csv_row(const std::vector<std::uint8_t>& payload) {
namespace { namespace {
using nlohmann::json; using nlohmann::json;
std::string format_receive_time(const std::chrono::steady_clock::time_point& received) {
// Map steady_clock timestamp to system_clock using the current offset, then format as YYYYMMDDHHMMSS.
const auto now_sys = std::chrono::system_clock::now();
const auto now_steady = std::chrono::steady_clock::now();
const auto steady_delta = received - now_steady;
const auto sys_tp = now_sys + steady_delta;
const auto sys_ms_tp = std::chrono::time_point_cast<std::chrono::milliseconds>(sys_tp);
const auto ms_part = std::chrono::duration_cast<std::chrono::milliseconds>(sys_ms_tp.time_since_epoch()) % 1000;
const auto sys_sec_tp = std::chrono::time_point_cast<std::chrono::seconds>(sys_ms_tp);
const std::time_t tt = std::chrono::system_clock::to_time_t(sys_sec_tp);
std::tm tm{};
#if defined(_WIN32)
localtime_s(&tm, &tt);
#else
localtime_r(&tt, &tm);
#endif
std::ostringstream oss;
oss << std::put_time(&tm, "%Y%m%d%H%M%S")
<< std::setw(3) << std::setfill('0') << ms_part.count();
return oss.str();
}
bool is_simple_array(const json& value) { bool is_simple_array(const json& value) {
if (!value.is_array()) { if (!value.is_array()) {
return false; return false;
@@ -261,7 +285,8 @@ std::deque<std::shared_ptr<DecodedFrame>> frames) {
continue; continue;
} }
const auto row = payload_to_csv_row(payload); const auto timestamp = format_receive_time(frame->received_at);
const auto row = payload_to_csv_row(timestamp, payload);
stream << row << '\n'; stream << row << '\n';
wrote_any = true; wrote_any = true;
} }

View File

@@ -92,6 +92,23 @@ auto NavComponent(NavComponentState& state) noexcept -> raw_pointer<QWidget> {
ic::FontIcon {material::icon::kLogout}, ic::FontIcon {material::icon::kLogout},
ic::Clickable {&app::quit}, ic::Clickable {&app::quit},
}, },
ln::Item<IconButton> {
{0, Qt::AlignHCenter},
navigation_icons_config,
ic::ColorFilled,
ic::FontIcon {material::icon::k123},
MutableTransform{ [](auto& self, bool show_numbers) {
self.set_types(show_numbers
? icon_button::internal::IconButton::Types::TOGGLE_SELECTED
: icon_button::internal::IconButton::Types::TOGGLE_UNSELECTED);
},
state.heatmap_show_numbers },
ic::Clickable{ [ctx = state.heatmap_show_numbers] {
if (ctx) {
ctx->set(!ctx->get());
}
} },
},
ln::Item<IconButton> { ln::Item<IconButton> {
{0, Qt::AlignHCenter}, {0, Qt::AlignHCenter},
navigation_icons_config, navigation_icons_config,
@@ -102,4 +119,3 @@ auto NavComponent(NavComponentState& state) noexcept -> raw_pointer<QWidget> {
} }
}; };
} }

View File

@@ -171,11 +171,14 @@ class SensorStreamController: public QObject {
std::shared_ptr<MutableValue<QVector<PointData>>> heatmap_data, std::shared_ptr<MutableValue<QVector<PointData>>> heatmap_data,
std::shared_ptr<MutableValue<QSize>> matrix_context, std::shared_ptr<MutableValue<QSize>> matrix_context,
std::shared_ptr<MutableValue<QVector<QPointF>>> line_series, std::shared_ptr<MutableValue<QVector<QPointF>>> line_series,
std::shared_ptr<MutableValue<QVector<QPointF>>> line_series_max,
int line_capacity = 240, int line_capacity = 240,
QObject* parent = nullptr): QObject(parent), heatmap_data_(std::move(heatmap_data)), QObject* parent = nullptr): QObject(parent), heatmap_data_(std::move(heatmap_data)),
matrix_context_(std::move(matrix_context)), matrix_context_(std::move(matrix_context)),
line_series_(std::move(line_series)), line_series_(std::move(line_series)),
line_series_capacity_(std::max(1, line_capacity)) { line_series_max_(std::move(line_series_max)),
line_series_capacity_(std::max(1, line_capacity)),
line_series_half_capacity_(std::max(1, line_capacity / 2)) {
std::call_once(codec_registration_flag(), std::call_once(codec_registration_flag(),
[] { [] {
ffmsep::tactile::register_tactile_codec(); ffmsep::tactile::register_tactile_codec();
@@ -197,6 +200,9 @@ class SensorStreamController: public QObject {
if (line_series_) { if (line_series_) {
line_series_->set(QVector<QPointF>{}); line_series_->set(QVector<QPointF>{});
} }
if (line_series_max_) {
line_series_max_->set(QVector<QPointF>{});
}
const auto ports = ffmsep::CPStreamCore::list_available_ports(); const auto ports = ffmsep::CPStreamCore::list_available_ports();
std::string port_utf8; std::string port_utf8;
@@ -335,6 +341,9 @@ class SensorStreamController: public QObject {
if (line_series_) { if (line_series_) {
line_series_->set(QVector<QPointF>{}); line_series_->set(QVector<QPointF>{});
} }
if (line_series_max_) {
line_series_max_->set(QVector<QPointF>{});
}
sample_counter_ = 0; sample_counter_ = 0;
} }
@@ -444,8 +453,13 @@ class SensorStreamController: public QObject {
} }
double total = 0.0; double total = 0.0;
double max_v = 0.0;
for (const auto value: pressures) { for (const auto value: pressures) {
total += static_cast<double>(value); const double v = static_cast<double>(value);
total += v;
if (v > max_v) {
max_v = v;
}
} }
if (line_series_) { if (line_series_) {
@@ -456,9 +470,19 @@ class SensorStreamController: public QObject {
const int start = series.size() - line_series_capacity_; const int start = series.size() - line_series_capacity_;
series = series.mid(start); series = series.mid(start);
} }
++sample_counter_;
line_series_->set(std::move(series)); line_series_->set(std::move(series));
} }
if (line_series_max_) {
auto series = line_series_max_->get();
series.append(QPointF(static_cast<double>(sample_counter_), max_v));
const int cap = std::max(1, line_series_half_capacity_);
if (series.size() > cap) {
const int start = series.size() - cap;
series = series.mid(start);
}
line_series_max_->set(std::move(series));
}
++sample_counter_;
QVector<PointData> points; QVector<PointData> points;
points.reserve(matrix.width() * matrix.height()); points.reserve(matrix.width() * matrix.height());
@@ -546,11 +570,13 @@ class SensorStreamController: public QObject {
std::shared_ptr<MutableValue<QVector<PointData>>> heatmap_data_; std::shared_ptr<MutableValue<QVector<PointData>>> heatmap_data_;
std::shared_ptr<MutableValue<QSize>> matrix_context_; std::shared_ptr<MutableValue<QSize>> matrix_context_;
std::shared_ptr<MutableValue<QVector<QPointF>>> line_series_; std::shared_ptr<MutableValue<QVector<QPointF>>> line_series_;
std::shared_ptr<MutableValue<QVector<QPointF>>> line_series_max_;
std::unique_ptr<ffmsep::CPStreamCore> core_; std::unique_ptr<ffmsep::CPStreamCore> core_;
QString active_port_; QString active_port_;
QString last_error_; QString last_error_;
std::uint64_t sample_counter_ = 0; std::uint64_t sample_counter_ = 0;
int line_series_capacity_ = 240; int line_series_capacity_ = 240;
int line_series_half_capacity_ = 120;
bool connected_ = false; bool connected_ = false;
static std::future<ffmsep::persist::WriteResult> static std::future<ffmsep::persist::WriteResult>
@@ -573,8 +599,12 @@ struct SensorUiState {
std::make_shared<MutableValue<QSize>>(); std::make_shared<MutableValue<QSize>>();
std::shared_ptr<MutableValue<QPair<int, int>>> heatmap_range = std::shared_ptr<MutableValue<QPair<int, int>>> heatmap_range =
std::make_shared<MutableValue<QPair<int, int>>>(QPair<int, int>{ 0, 300 }); std::make_shared<MutableValue<QPair<int, int>>>(QPair<int, int>{ 0, 300 });
std::shared_ptr<MutableValue<bool>> heatmap_show_numbers =
std::make_shared<MutableValue<bool>>(false);
std::shared_ptr<MutableValue<QVector<QPointF>>> line_series = std::shared_ptr<MutableValue<QVector<QPointF>>> line_series =
std::make_shared<MutableValue<QVector<QPointF>>>(); std::make_shared<MutableValue<QVector<QPointF>>>();
std::shared_ptr<MutableValue<QVector<QPointF>>> line_series_max =
std::make_shared<MutableValue<QVector<QPointF>>>();
int line_series_capacity = 240; int line_series_capacity = 240;
std::shared_ptr<MutableValue<QStringList>> port_items = std::shared_ptr<MutableValue<QStringList>> port_items =
std::make_shared<MutableValue<QStringList>>(); std::make_shared<MutableValue<QStringList>>();
@@ -631,7 +661,7 @@ struct SensorUiState {
controller = controller =
std::make_unique<SensorStreamController>( std::make_unique<SensorStreamController>(
heatmap_data, heatmap_matrix, line_series, line_series_capacity); heatmap_data, heatmap_matrix, line_series, line_series_max, line_series_capacity);
} }
}; };
@@ -674,6 +704,10 @@ class PortHoverRefreshFilter final: public QObject {
} // namespace } // namespace
std::shared_ptr<MutableValue<bool>> HeatmapNumberVisibilityContext() {
return sensor_state().heatmap_show_numbers;
}
void RefreshProfilesForView() { void RefreshProfilesForView() {
auto& sensor = sensor_state(); auto& sensor = sensor_state();
@@ -912,9 +946,10 @@ int /*index*/ = 0) noexcept {
auto& sensor = sensor_state(); auto& sensor = sensor_state();
const auto row = new Row{ const auto row = new Row{
lnpro::Item<HeatMapPlot>{ lnpro::Item<HeatMapPlot>{
plot_widget::pro::SizePolicy{ widget::pro::FixedSize {600, 600},
QSizePolicy::Expanding, // plot_widget::pro::SizePolicy{
}, // // QSizePolicy::Fixed,
// },
plot_widget::pro::ThemeManager{ manager }, plot_widget::pro::ThemeManager{ manager },
MutableForward{ MutableForward{
plot_widget::pro::PlotData{}, plot_widget::pro::PlotData{},
@@ -933,6 +968,10 @@ int /*index*/ = 0) noexcept {
widget.set_color_gradient_range(min, max); widget.set_color_gradient_range(min, max);
}, },
sensor.heatmap_range }, sensor.heatmap_range },
MutableTransform{ [](auto& widget, bool show_numbers) {
widget.set_labels_visible(show_numbers);
},
sensor.heatmap_show_numbers },
}, },
}; };
return new Widget{ return new Widget{
@@ -966,21 +1005,29 @@ static auto DisplayVectorComponent(ThemeManager& manager) noexcept {
static auto DisplayLineComponent(ThemeManager& manager) noexcept { static auto DisplayLineComponent(ThemeManager& manager) noexcept {
auto& sensor = sensor_state(); auto& sensor = sensor_state();
const auto row = new Row{ const int half_cap = std::max(1, sensor.line_series_capacity / 2);
const auto col = new Col{
lnpro::Item<SumLinePlot>{ lnpro::Item<SumLinePlot>{
lcpro::SizePolicy{ lcpro::SizePolicy{ QSizePolicy::Expanding },
QSizePolicy::Expanding,
},
lcpro::ThemeManager{ manager }, lcpro::ThemeManager{ manager },
lcpro::MaxPoints{ sensor.line_series_capacity }, lcpro::MaxPoints{ half_cap },
MutableForward{ MutableForward{
lcpro::PlotData{}, lcpro::PlotData{},
sensor.line_series, sensor.line_series,
}, },
}, },
lnpro::Item<SumLinePlot>{
lcpro::SizePolicy{ QSizePolicy::Expanding },
lcpro::ThemeManager{ manager },
lcpro::MaxPoints{ half_cap },
MutableForward{
lcpro::PlotData{},
sensor.line_series_max,
},
},
}; };
return new Widget{ return new Widget{
widget::pro::Layout{ row }, widget::pro::Layout{ col },
}; };
} }

View File

@@ -146,6 +146,7 @@ namespace material {
constexpr auto kCancel = "cancel"; constexpr auto kCancel = "cancel";
constexpr auto kOpenInNew = "open_in_new"; constexpr auto kOpenInNew = "open_in_new";
constexpr auto kLogout = "logout"; constexpr auto kLogout = "logout";
constexpr auto k123 = "123";
constexpr auto kRoutine = "routine"; constexpr auto kRoutine = "routine";
constexpr auto kDarkMode = "dark_mode"; constexpr auto kDarkMode = "dark_mode";
constexpr auto kFileExport = "file_export"; constexpr auto kFileExport = "file_export";

View File

@@ -33,7 +33,7 @@ auto main(int argc, char *argv[]) -> int {
auto stack_index = std::make_shared<MutableValue<int>>(); auto stack_index = std::make_shared<MutableValue<int>>();
stack_index->set_silent(0); stack_index->set_silent(0);
auto manager = ThemeManager {kBlueMikuThemePack}; auto manager = ThemeManager {kBlueMikuThemePack, ColorMode::DARK};
creeper::material::FontLoader::load_font(); creeper::material::FontLoader::load_font();
auto nav_component_state = NavComponentState { auto nav_component_state = NavComponentState {
.manager = manager, .manager = manager,
@@ -51,7 +51,8 @@ auto main(int argc, char *argv[]) -> int {
}, },
.stacked_callback = [&](int index) { .stacked_callback = [&](int index) {
*stack_index = index; *stack_index = index;
} },
.heatmap_show_numbers = HeatmapNumberVisibilityContext(),
}; };
auto setting_component_state = SettingComponentState { auto setting_component_state = SettingComponentState {
.manager = manager, .manager = manager,