From a07ff7d6b70796325f16cc7aeb3625e26cb975b9 Mon Sep 17 00:00:00 2001 From: lenn Date: Tue, 4 Nov 2025 10:47:41 +0800 Subject: [PATCH] feat:heapmap with value;fix:qcustomplot warning --- CMakeLists.txt | 2 + components/charts/heatmap.impl.hh | 261 +++++++++----- components/ffmsep/cpstream_core.cc | 150 +++++--- components/ffmsep/cpstream_core.hh | 21 +- components/ffmsep/presist/presist.cc | 186 ++++++++++ components/ffmsep/presist/presist.hh | 86 +++++ components/view.cc | 501 ++++++++++++++------------- examples/cpstream_demo.cc | 31 +- modern-qt/utility/material-icon.hh | 3 - 9 files changed, 852 insertions(+), 389 deletions(-) create mode 100644 components/ffmsep/presist/presist.cc create mode 100644 components/ffmsep/presist/presist.hh diff --git a/CMakeLists.txt b/CMakeLists.txt index c0dc24c..fece4b2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,11 +71,13 @@ file( set(FFMSEP_SOURCES components/ffmsep/cpdecoder.cc components/ffmsep/cpstream_core.cc + components/ffmsep/presist/presist.cc components/ffmsep/tactile/tacdec.cc ) set(FFMSEP_HEADERS components/ffmsep/cpdecoder.hh components/ffmsep/cpstream_core.hh + components/ffmsep/presist/presist.hh components/ffmsep/tactile/tacdec.hh ) set(FFMSEP_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/components/ffmsep") diff --git a/components/charts/heatmap.impl.hh b/components/charts/heatmap.impl.hh index acf64a0..f975341 100644 --- a/components/charts/heatmap.impl.hh +++ b/components/charts/heatmap.impl.hh @@ -8,13 +8,17 @@ #include "heatmap.hh" #include "modern-qt/utility/theme/theme.hh" #include "modern-qt/widget/sliders.hh" -#include -#include -#include -using namespace creeper::plot_widget::internal; - -struct BasicPlot::Impl { - explicit Impl(BasicPlot& self) noexcept : self{self}, initialized(false), matrix_size(QSize{3, 4}) {} +#include "qcustomplot/qcustomplot.h" +#include +#include +#include +#include +#include +#include +using namespace creeper::plot_widget::internal; + +struct BasicPlot::Impl { + explicit Impl(BasicPlot& self) noexcept : self{self}, initialized(false), matrix_size(QSize{3, 4}) {} public: auto set_xlabel_text(const QString& text) -> void { @@ -51,31 +55,31 @@ public: }); } - auto set_color_gradient_range(const double& min, const double& max) -> void { - if (initialized && self.plottableCount() > 0) { - auto* cpmp = static_cast(self.plottable(0)); - cpmp->setDataRange(QCPRange(min, max)); - self.replot(); - } - color_min = min; - color_max = max; + auto set_color_gradient_range(const double& min, const double& max) -> void { + if (initialized && color_map) { + color_map->setDataRange(QCPRange(min, max)); + self.replot(); + } + color_min = min; + color_max = max; } - auto set_data(const QVector& data) -> void { - data_points = data; - if (initialized) { - update_plot_data(); - } - } - - auto initialize_plot() -> void { - if (initialized) return; - -QCPColorMap* cpmp = new QCPColorMap(self.xAxis, self.yAxis); - cpmp->data()->setSize(matrix_size.width(), matrix_size.height()); - cpmp->data()->setRange(QCPRange(0.5, matrix_size.width() - 0.5), - QCPRange(0.5, matrix_size.height() - 0.5)); - + auto set_data(const QVector& data) -> void { + data_points = data; + if (initialized && color_map) { + update_plot_data(); + } + } + + auto initialize_plot() -> void { + if (initialized) return; + + color_map = new QCPColorMap(self.xAxis, self.yAxis); + auto* cpmp = color_map; + cpmp->data()->setSize(matrix_size.width(), matrix_size.height()); + cpmp->data()->setRange(QCPRange(0.5, matrix_size.width() - 0.5), + QCPRange(0.5, matrix_size.height() - 0.5)); + QSharedPointer xticker(new QCPAxisTickerText); QSharedPointer yticker(new QCPAxisTickerText); xticker->setSubTickCount(1); @@ -103,23 +107,35 @@ QCPColorMap* cpmp = new QCPColorMap(self.xAxis, self.yAxis); if (!xlabel.isEmpty()) self.xAxis->setLabel(xlabel); if (!ylabel.isEmpty()) self.yAxis->setLabel(ylabel); - QCPColorScale* color_scale = new QCPColorScale(&self); - color_scale->setType(QCPAxis::atBottom); - self.plotLayout()->addElement(1, 0, color_scale); - cpmp->setColorScale(color_scale); - - QCPColorGradient gradient; - gradient.setColorStopAt(0.0, QColor(246, 239, 166)); // F6EFA6 - gradient.setColorStopAt(1.0, QColor(191, 68, 76)); // BF444C + if (!color_scale) { + color_scale = new QCPColorScale(&self); + color_scale->setType(QCPAxis::atBottom); + } + if (self.plotLayout()) { + auto existing = self.plotLayout()->element(1, 0); + if (existing && existing != color_scale) { + self.plotLayout()->take(existing); + } + if (!existing || existing != color_scale) { + self.plotLayout()->addElement(1, 0, color_scale); + } + } + cpmp->setColorScale(color_scale); + + QCPColorGradient gradient; + gradient.setColorStopAt(0.0, QColor(240, 246, 255)); // 低值淡色 + gradient.setColorStopAt(0.35, QColor(142, 197, 252)); + gradient.setColorStopAt(0.7, QColor(56, 128, 199)); + gradient.setColorStopAt(1.0, QColor(8, 36, 95)); // 高值深色 cpmp->setGradient(gradient); cpmp->setDataRange(QCPRange(color_min, color_max)); cpmp->setInterpolate(false); - - QCPMarginGroup *margin_group = new QCPMarginGroup(&self); - self.axisRect()->setMarginGroup(QCP::msLeft | QCP::msRight, margin_group); - color_scale->setMarginGroup(QCP::msLeft | QCP::msRight, margin_group); - + + QCPMarginGroup *margin_group = new QCPMarginGroup(&self); + self.axisRect()->setMarginGroup(QCP::msLeft | QCP::msRight, margin_group); + color_scale->setMarginGroup(QCP::msLeft | QCP::msRight, margin_group); + initialized = true; if (!data_points.isEmpty()) { @@ -127,33 +143,46 @@ QCPColorMap* cpmp = new QCPColorMap(self.xAxis, self.yAxis); } } - auto reset_plot() -> void { - // 清除所有绘图元素 - self.clearPlottables(); - self.clearGraphs(); - self.clearItems(); - self.clearFocus(); - - // 重新初始化 - initialized = false; - initialize_plot(); - } - auto update_plot_data() -> void { - if (!initialized || self.plottableCount() == 0) return; - - auto* cpmp = static_cast(self.plottable(0)); - - // 设置新数据 - for (const auto& item : data_points) { - if (item.x >= 0 && item.x < matrix_size.width() && - item.y >= 0 && item.y < matrix_size.height()) { - cpmp->data()->setCell(item.x, item.y, item.z); - } - } - - // 重绘 - self.replot(); - } + auto reset_plot() -> void { + // 清除所有绘图元素 + self.clearPlottables(); + self.clearGraphs(); + self.clearItems(); + self.clearFocus(); + color_map = nullptr; + cell_labels.clear(); + + // 重新初始化 + initialized = false; + initialize_plot(); + } + auto update_plot_data() -> void { + if (!initialized || !color_map) return; + + ensure_labels(); + + const int width = matrix_size.width(); + const int height = matrix_size.height(); + const int expected = width * height; + std::vector values(static_cast(expected), 0.0); + + // 设置新数据 + for (const auto& item : data_points) { + if (item.x >= 0 && item.x < matrix_size.width() && + item.y >= 0 && item.y < matrix_size.height()) { + color_map->data()->setCell(item.x, item.y, item.z); + const int idx = static_cast(item.y) * width + static_cast(item.x); + if (idx >= 0 && idx < expected) { + values[static_cast(idx)] = item.z; + } + } + } + + update_label_values(values); + + // 重绘 + self.replot(); + } auto is_plot_initialized() const -> bool { return initialized; @@ -167,11 +196,87 @@ private: QString xlabel; QString ylabel; QSize matrix_size; - QVector data_points; - double color_min = 0.0; - double color_max = 800.0; - bool initialized; - BasicPlot& self; -}; - -#endif // TOUCHSENSOR_HEATMAP_IMPL_HH + QVector data_points; + double color_min = 0.0; + double color_max = 800.0; + bool initialized; + BasicPlot& self; + QCPColorScale* color_scale = nullptr; + QCPColorMap* color_map = nullptr; + QVector cell_labels; + + void clear_labels() { + for (auto* label : cell_labels) { + if (label) { + self.removeItem(label); + } + } + cell_labels.clear(); + } + + void ensure_labels() { + const int width = matrix_size.width(); + const int height = matrix_size.height(); + const int expected = width * height; + if (expected <= 0) { + clear_labels(); + return; + } + if (cell_labels.size() == expected) { + return; + } + + clear_labels(); + cell_labels.reserve(expected); + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + auto* label = new QCPItemText(&self); + label->position->setType(QCPItemPosition::ptPlotCoords); + label->setClipToAxisRect(true); + label->setClipAxisRect(self.axisRect()); + label->setPositionAlignment(Qt::AlignCenter); + label->position->setCoords(x + 0.5, y + 0.5); + label->setBrush(Qt::NoBrush); + label->setPen(Qt::NoPen); + QFont font = label->font(); + if (font.pointSize() > 0) { + font.setPointSize(std::max(font.pointSize() - 1, 6)); + } else { + font.setPointSize(8); + } + label->setFont(font); + label->setColor(Qt::black); + label->setSelectable(false); + cell_labels.push_back(label); + } + } + } + + void update_label_values(const std::vector& values) { + const int width = matrix_size.width(); + const int height = matrix_size.height(); + const double range = std::max(color_max - color_min, 1.0); + const int expected = width * height; + for (int idx = 0; idx < expected && idx < cell_labels.size(); ++idx) { + auto* label = cell_labels[idx]; + if (!label) { + continue; + } + const double value = values.size() > static_cast(idx) + ? values[static_cast(idx)] + : 0.0; + label->setText(QString::number(value, 'f', 0)); + const double normalized = std::clamp((value - color_min) / range, 0.0, 1.0); + if (normalized > 0.55) { + label->setColor(Qt::white); + } else { + label->setColor(QColor(25, 25, 25)); + } + const int x = idx % width; + const int y = idx / width; + label->position->setCoords(x + 0.5, y + 0.5); + } + } +}; + +#endif // TOUCHSENSOR_HEATMAP_IMPL_HH diff --git a/components/ffmsep/cpstream_core.cc b/components/ffmsep/cpstream_core.cc index 1aa1261..65729c1 100644 --- a/components/ffmsep/cpstream_core.cc +++ b/components/ffmsep/cpstream_core.cc @@ -1,5 +1,8 @@ #include "components/ffmsep/cpstream_core.hh" +#include "components/ffmsep/presist/presist.hh" +#include "dlog/dlog.hh" + #include #include #include @@ -7,19 +10,21 @@ #include #include #include +#include #include #include #include #include #include #include +using namespace std::chrono_literals; namespace ffmsep { namespace { -constexpr auto kReaderIdleSleep = std::chrono::milliseconds(5); -constexpr auto kDecoderIdleSleep = std::chrono::milliseconds(1); +constexpr auto kReaderIdleSleep = 5ms; +constexpr auto kDecoderIdleSleep = 1ms; const CPCodec* resolve_requested_codec(const CPStreamConfig& config) { if (!config.codec_name.empty()) { @@ -48,6 +53,7 @@ struct CPStreamCore::Impl { explicit Impl(CPStreamConfig config) : config_(std::move(config)) { normalize_config(); + frame_writer_ = std::make_unique(); } ~Impl() = default; @@ -63,7 +69,7 @@ struct CPStreamCore::Impl { config_.frame_queue_capacity = 1U; } if (config_.slave_request_interval.count() < 0) { - config_.slave_request_interval = std::chrono::milliseconds{0}; + config_.slave_request_interval = 0ms; } frame_queue_capacity_ = config_.frame_queue_capacity; } @@ -115,7 +121,7 @@ struct CPStreamCore::Impl { serial->flush(); { - std::lock_guard lock(serial_mutex_); + std::lock_guard lock(serial_mutex_); serial_ = std::move(serial); } } catch (const serial::IOException& ex) { @@ -139,11 +145,11 @@ struct CPStreamCore::Impl { } { - std::lock_guard lock(packet_mutex_); + std::lock_guard lock(packet_mutex_); packet_queue_.clear(); } { - std::lock_guard lock(frame_mutex_); + std::lock_guard lock(frame_mutex_); frame_queue_.clear(); } pts_counter_.store(0, std::memory_order_relaxed); @@ -164,7 +170,7 @@ struct CPStreamCore::Impl { stop(); { - std::lock_guard lock(serial_mutex_); + std::lock_guard lock(serial_mutex_); if (serial_) { try { if (serial_->isOpen()) { @@ -184,12 +190,13 @@ struct CPStreamCore::Impl { } { - std::lock_guard lock(packet_mutex_); + std::lock_guard lock(packet_mutex_); packet_queue_.clear(); } { - std::lock_guard lock(frame_mutex_); + std::lock_guard lock(frame_mutex_); frame_queue_.clear(); + frame_record_queue_.clear(); } } @@ -200,7 +207,7 @@ struct CPStreamCore::Impl { std::shared_ptr serial_copy; { - std::lock_guard lock(serial_mutex_); + std::lock_guard lock(serial_mutex_); serial_copy = serial_; } if (!serial_copy || !serial_copy->isOpen()) { @@ -249,7 +256,7 @@ struct CPStreamCore::Impl { stop_requested_.store(false, std::memory_order_release); { - std::lock_guard lock(packet_mutex_); + std::lock_guard lock(packet_mutex_); packet_queue_.clear(); } @@ -259,7 +266,7 @@ struct CPStreamCore::Impl { } bool is_open() const { - std::lock_guard lock(serial_mutex_); + std::lock_guard lock(serial_mutex_); return serial_ && serial_->isOpen(); } @@ -278,7 +285,7 @@ struct CPStreamCore::Impl { std::shared_ptr serial_copy; { - std::lock_guard lock(serial_mutex_); + std::lock_guard lock(serial_mutex_); serial_copy = serial_; } @@ -300,17 +307,17 @@ struct CPStreamCore::Impl { return false; } - std::optional try_pop_frame() { - std::lock_guard lock(frame_mutex_); + std::optional> try_pop_frame() { + std::lock_guard lock(frame_mutex_); if (frame_queue_.empty()) { return std::nullopt; } - DecodedFrame frame = std::move(frame_queue_.front()); + std::shared_ptr frame = std::move(frame_queue_.front()); frame_queue_.pop_front(); return frame; } - bool wait_for_frame(DecodedFrame& frame, std::chrono::milliseconds timeout) { + bool wait_for_frame(std::shared_ptr& frame, std::chrono::milliseconds timeout) { std::unique_lock lock(frame_mutex_); if (!frame_cv_.wait_for(lock, timeout, [&] { return !frame_queue_.empty(); @@ -323,7 +330,7 @@ struct CPStreamCore::Impl { } void clear_frames() { - std::lock_guard lock(frame_mutex_); + std::lock_guard lock(frame_mutex_); frame_queue_.clear(); } @@ -332,7 +339,7 @@ struct CPStreamCore::Impl { capacity = 1U; } { - std::lock_guard lock(frame_mutex_); + std::lock_guard lock(frame_mutex_); frame_queue_capacity_ = capacity; config_.frame_queue_capacity = capacity; while (frame_queue_.size() > frame_queue_capacity_) { @@ -341,8 +348,46 @@ struct CPStreamCore::Impl { } } + void clear_recorded_frames() { + std::lock_guard lock(frame_mutex_); + frame_record_queue_.clear(); + } + + std::size_t recorded_frame_count() const { + std::lock_guard lock(frame_mutex_); + return frame_record_queue_.size(); + } + + std::future export_recorded_frames(const std::string& path, bool clear_after_export) { + if (!frame_writer_) { + frame_writer_ = std::make_unique(); + } + + std::deque> snapshot; + { + std::lock_guard lock(frame_mutex_); + snapshot = frame_record_queue_; + if (clear_after_export) { + frame_record_queue_.clear(); + } + } + + if (snapshot.empty()) { + std::promise promise; + auto future = promise.get_future(); + promise.set_value(persist::WriteResult{ + false, + "no recorded frames available", + path + }); + return future; + } + + return frame_writer_->enqueue(path, std::move(snapshot)); + } + void set_frame_callback(FrameCallback callback) { - std::lock_guard lock(callback_mutex_); + std::lock_guard lock(callback_mutex_); frame_callback_ = std::move(callback); } @@ -351,7 +396,7 @@ struct CPStreamCore::Impl { } std::string last_error() const { - std::lock_guard lock(last_error_mutex_); + std::lock_guard lock(last_error_mutex_); return last_error_; } @@ -365,7 +410,7 @@ struct CPStreamCore::Impl { while (!stop_requested_.load(std::memory_order_acquire)) { std::shared_ptr serial_copy; { - std::lock_guard lock(serial_mutex_); + std::lock_guard lock(serial_mutex_); serial_copy = serial_; } if (!serial_copy || !serial_copy->isOpen()) { @@ -400,7 +445,7 @@ struct CPStreamCore::Impl { packet.pts = pts_counter_.fetch_add(1, std::memory_order_relaxed); { - std::lock_guard lock(packet_mutex_); + std::lock_guard lock(packet_mutex_); if (packet_queue_.size() >= config_.packet_queue_capacity) { packet_queue_.pop_front(); } @@ -414,7 +459,7 @@ struct CPStreamCore::Impl { const auto command = config_.slave_request_command; auto interval = config_.slave_request_interval; if (interval.count() < 0) { - interval = std::chrono::milliseconds{0}; + interval = 0ms; } const bool repeat = interval.count() > 0; @@ -484,23 +529,24 @@ struct CPStreamCore::Impl { CPFrame frame; rc = cpcodec_receive_frame(codec_ctx_, &frame); if (rc == CP_SUCCESS) { - DecodedFrame decoded; - decoded.pts = frame.pts; - decoded.received_at = std::chrono::steady_clock::now(); - decoded.frame = std::move(frame); - if (codec_descriptor_ && codec_descriptor_->id == CPCodecID::Tactile) { - if (auto parsed = tactile::parse_frame(decoded.frame)) { - decoded.tactile = parsed; - decoded.tactile_pressures = tactile::parse_pressure_values(*parsed); + auto decoded = std::make_shared(); + decoded->pts = frame.pts; + decoded->received_at = std::chrono::steady_clock::now(); + decoded->frame = std::move(frame); + decoded->id = codec_descriptor_ ? codec_descriptor_->id : CPCodecID::Unknow; + if (decoded->id == CPCodecID::Tactile) { + if (auto parsed = tactile::parse_frame(decoded->frame)) { + decoded->tactile = parsed; + decoded->tactile_pressures = tactile::parse_pressure_values(*parsed); if (auto matrix = tactile::parse_matrix_size_payload(*parsed)) { - decoded.tactile_matrix_size = matrix; + decoded->tactile_matrix_size = matrix; } } } FrameCallback callback_copy; { - std::lock_guard lock(callback_mutex_); + std::lock_guard lock(callback_mutex_); callback_copy = frame_callback_; } if (callback_copy) { @@ -508,11 +554,14 @@ struct CPStreamCore::Impl { } { - std::lock_guard lock(frame_mutex_); + std::lock_guard lock(frame_mutex_); if (frame_queue_.size() >= frame_queue_capacity_) { frame_queue_.pop_front(); } - frame_queue_.push_back(std::move(decoded)); + frame_queue_.push_back(decoded); + if (decoded->id == CPCodecID::Tactile) { + frame_record_queue_.push_back(decoded); + } } frame_cv_.notify_one(); } else if (rc == CP_ERROR_EAGAIN) { @@ -536,7 +585,7 @@ struct CPStreamCore::Impl { packet.flush = true; packet.end_of_stream = end_of_stream; { - std::lock_guard lock(packet_mutex_); + std::lock_guard lock(packet_mutex_); packet_queue_.push_back(std::move(packet)); } packet_cv_.notify_one(); @@ -554,7 +603,7 @@ struct CPStreamCore::Impl { } void set_last_error(std::string message) { - std::lock_guard lock(last_error_mutex_); + std::lock_guard lock(last_error_mutex_); last_error_ = std::move(message); } @@ -574,9 +623,12 @@ struct CPStreamCore::Impl { std::condition_variable packet_cv_; std::deque packet_queue_; - std::mutex frame_mutex_; + mutable std::mutex frame_mutex_; std::condition_variable frame_cv_; - std::deque frame_queue_; + // std::deque frame_queue_; + // 更新为智能指针,我们需要更长的生命周期😊 + std::deque> frame_queue_; + std::deque> frame_record_queue_; std::size_t frame_queue_capacity_ = 16; FrameCallback frame_callback_; @@ -588,6 +640,8 @@ struct CPStreamCore::Impl { std::string last_error_; mutable std::mutex last_error_mutex_; + + std::unique_ptr frame_writer_; }; CPStreamCore::CPStreamCore(CPStreamConfig config) @@ -640,11 +694,11 @@ bool CPStreamCore::send(const std::uint8_t* data, std::size_t size) { return impl_->send(data, size); } -std::optional CPStreamCore::try_pop_frame() { +std::optional> CPStreamCore::try_pop_frame() { return impl_->try_pop_frame(); } -bool CPStreamCore::wait_for_frame(DecodedFrame& frame, std::chrono::milliseconds timeout) { +bool CPStreamCore::wait_for_frame(std::shared_ptr& frame, std::chrono::milliseconds timeout) { return impl_->wait_for_frame(frame, timeout); } @@ -656,6 +710,18 @@ void CPStreamCore::set_frame_queue_capacity(std::size_t capacity) { impl_->set_frame_queue_capacity(capacity); } +void CPStreamCore::clear_recorded_frames() { + impl_->clear_recorded_frames(); +} + +std::size_t CPStreamCore::recorded_frame_count() const { + return impl_->recorded_frame_count(); +} + +std::future CPStreamCore::export_recorded_frames(const std::string& path, bool clear_after_export) { + return impl_->export_recorded_frames(path, clear_after_export); +} + void CPStreamCore::set_frame_callback(FrameCallback callback) { impl_->set_frame_callback(std::move(callback)); } diff --git a/components/ffmsep/cpstream_core.hh b/components/ffmsep/cpstream_core.hh index f04c14f..e3c2d31 100644 --- a/components/ffmsep/cpstream_core.hh +++ b/components/ffmsep/cpstream_core.hh @@ -5,16 +5,23 @@ #include #include #include +#include #include #include #include #include #include +using namespace std::chrono_literals; namespace ffmsep { +namespace persist { +struct WriteResult; +} // namespace persist + struct DecodedFrame { CPFrame frame; + CPCodecID id = CPCodecID::Unknow; std::chrono::steady_clock::time_point received_at{}; std::int64_t pts = 0; std::optional tactile; @@ -36,12 +43,12 @@ struct CPStreamConfig { CPCodecID codec_id = CPCodecID::Unknow; std::string codec_name; std::vector slave_request_command{}; - std::chrono::milliseconds slave_request_interval{200}; + std::chrono::milliseconds slave_request_interval{200ms}; }; class CPStreamCore { public: - using FrameCallback = std::function; + using FrameCallback = std::function)>; explicit CPStreamCore(CPStreamConfig config = {}); ~CPStreamCore(); @@ -63,10 +70,14 @@ public: bool send(const std::vector& data); bool send(const std::uint8_t* data, std::size_t size); - std::optional try_pop_frame(); - bool wait_for_frame(DecodedFrame& frame, std::chrono::milliseconds timeout); + std::optional> try_pop_frame(); + bool wait_for_frame(std::shared_ptr& frame, std::chrono::milliseconds timeout); void clear_frames(); void set_frame_queue_capacity(std::size_t capacity); + void clear_recorded_frames(); + [[nodiscard]] std::size_t recorded_frame_count() const; + std::future export_recorded_frames(const std::string& path, + bool clear_after_export = false); void set_frame_callback(FrameCallback callback); @@ -75,6 +86,8 @@ public: static std::vector list_available_ports(); + + private: struct Impl; std::unique_ptr impl_; diff --git a/components/ffmsep/presist/presist.cc b/components/ffmsep/presist/presist.cc new file mode 100644 index 0000000..770f636 --- /dev/null +++ b/components/ffmsep/presist/presist.cc @@ -0,0 +1,186 @@ +// +// Created by Lenn on 2025/10/31. +// + +#include "components/ffmsep/presist/presist.hh" + +#include "components/ffmsep/cpstream_core.hh" + +#include +#include +#include +#include +#include +#include + +namespace ffmsep::persist { + +namespace { + +using nlohmann::json; + +json serialize_tactile_frame(const DecodedFrame& frame) { + json result = { + {"pts", frame.pts}, + {"raw_frame", frame.frame.data}, + {"pressures", frame.tactile_pressures}, + }; + + const auto received = frame.received_at.time_since_epoch(); + result["received_at_ns"] = + std::chrono::duration_cast(received).count(); + + if (frame.tactile_matrix_size) { + result["matrix"] = { + {"long_edge", frame.tactile_matrix_size->long_edge}, + {"short_edge", frame.tactile_matrix_size->short_edge}, + }; + } + + if (frame.tactile) { + const auto& tactile = *frame.tactile; + result["tactile"] = { + {"device_address", tactile.device_address}, + {"response_function", tactile.response_function}, + {"function", static_cast(tactile.function)}, + {"start_address", tactile.start_address}, + {"return_byte_count", tactile.return_byte_count}, + {"status", tactile.status}, + {"payload", tactile.payload}, + }; + } + + return result; +} + +} // namespace + +bool WriteQueue::push(WriteRequest&& req) { + { + std::lock_guard lock(mutex_); + if (stopped_) { + return false; + } + queue_.push(std::move(req)); + } + cond_.notify_one(); + return true; +} + +bool WriteQueue::pop(WriteRequest& out) { + std::unique_lock lock(mutex_); + cond_.wait(lock, [&] { + return stopped_ || !queue_.empty(); + }); + + if (queue_.empty()) { + return false; + } + + out = std::move(queue_.front()); + queue_.pop(); + return true; +} + +void WriteQueue::stop() { + { + std::lock_guard lock(mutex_); + stopped_ = true; + } + cond_.notify_all(); +} + +JsonWritter::JsonWritter() + : write_thread_([this] { run(); }) {} + +JsonWritter::~JsonWritter() { + stop(); +} + +std::future JsonWritter::enqueue(std::string path, + std::deque> frames) { + std::promise promise; + auto future = promise.get_future(); + + WriteRequest request{std::move(path), std::move(frames), std::move(promise)}; + if (!write_queue_.push(std::move(request))) { + WriteResult result{false, "writer has been stopped", request.path}; + request.promise.set_value(std::move(result)); + } + + return future; +} + +void JsonWritter::run() { + WriteRequest request; + while (write_queue_.pop(request)) { + try { + auto result = write_once(request.path, std::move(request.frames)); + request.promise.set_value(std::move(result)); + } catch (const std::exception& ex) { + request.promise.set_value(WriteResult{false, ex.what(), request.path}); + } catch (...) { + request.promise.set_value(WriteResult{false, "unknown error", request.path}); + } + } +} + +WriteResult JsonWritter::write_once(const std::string& path, + std::deque> frames) { + if (path.empty()) { + return {false, "export path is empty", path}; + } + + json tactile_frames = json::array(); + + for (const auto& frame : frames) { + if (!frame) { + continue; + } + if (frame->id != CPCodecID::Tactile || !frame->tactile) { + continue; + } + tactile_frames.push_back(serialize_tactile_frame(*frame)); + } + + if (tactile_frames.empty()) { + return {false, "no tactile frames available for export", path}; + } + + json root; + root["codec"] = "tactile"; + root["frames"] = std::move(tactile_frames); + + std::filesystem::path fs_path(path); + if (fs_path.has_parent_path()) { + std::error_code ec; + std::filesystem::create_directories(fs_path.parent_path(), ec); + if (ec) { + return {false, "failed to create export directory: " + ec.message(), path}; + } + } + + std::ofstream stream(path, std::ios::binary | std::ios::trunc); + if (!stream.is_open()) { + return {false, "failed to open export file", path}; + } + + stream << std::setw(2) << root; + stream.flush(); + if (!stream.good()) { + return {false, "failed to write export file", path}; + } + + return {true, {}, path}; +} + +void JsonWritter::stop() { + if (!stopped_.exchange(true)) { + write_queue_.stop(); + if (write_thread_.joinable()) { + write_thread_.join(); + } + } +} + +} // namespace ffmsep::persist diff --git a/components/ffmsep/presist/presist.hh b/components/ffmsep/presist/presist.hh new file mode 100644 index 0000000..5f8cd39 --- /dev/null +++ b/components/ffmsep/presist/presist.hh @@ -0,0 +1,86 @@ +// +// Created by Lenn on 2025/10/31. +// + +#ifndef TOUCHSENSOR_PRESIST_HH +#define TOUCHSENSOR_PRESIST_HH + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ffmsep { + +struct DecodedFrame; + +namespace persist { + +struct WriteResult { + bool ok = false; + std::string error; + std::string path; +}; + +struct WriteRequest { + WriteRequest() = default; + WriteRequest(std::string p, + std::deque> f, + std::promise&& pr) + : path(std::move(p)) + , frames(std::move(f)) + , promise(std::move(pr)) {} + + WriteRequest(const WriteRequest&) = delete; + WriteRequest& operator=(const WriteRequest&) = delete; + WriteRequest(WriteRequest&&) noexcept = default; + WriteRequest& operator=(WriteRequest&&) noexcept = default; + + std::string path; + std::deque> frames; + std::promise promise; +}; + +class WriteQueue { +public: + bool push(WriteRequest&& req); + bool pop(WriteRequest& out); + void stop(); + +private: + std::mutex mutex_; + std::condition_variable cond_; + std::queue queue_; + bool stopped_ = false; +}; + +class JsonWritter { +public: + JsonWritter(); + ~JsonWritter(); + + std::future enqueue(std::string path, + std::deque> frames); + + void stop(); + +private: + void run(); + WriteResult write_once(const std::string& path, + std::deque> frames); + +private: + std::thread write_thread_; + WriteQueue write_queue_; + std::atomic_bool stopped_{false}; +}; + +} // namespace persist +} // namespace ffmsep + +#endif // TOUCHSENSOR_PRESIST_HH diff --git a/components/view.cc b/components/view.cc index 9c91dad..36c4ea0 100644 --- a/components/view.cc +++ b/components/view.cc @@ -2,25 +2,25 @@ // Created by Lenn on 2025/10/14. // -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "component.hh" #include "cpstream_core.hh" #include "modern-qt/utility/theme/theme.hh" @@ -43,6 +43,7 @@ #include #include "components/ffmsep/tactile/tacdec.hh" +#define DEBUG 0 using namespace creeper; namespace capro = card::pro; @@ -98,10 +99,10 @@ public: reset_core(); } - bool start(const QString& requested_port, std::uint32_t baudrate) { - if (is_connected()) { - return true; - } + bool start(const QString& requested_port, std::uint32_t baudrate) { + if (is_connected()) { + return true; + } const auto ports = ffmsep::CPStreamCore::list_available_ports(); std::string port_utf8; @@ -112,17 +113,23 @@ public: [&](const serial::PortInfo& info) { return info.port == port_utf8; }); if (it == ports.end()) { if (ports.empty()) { +#if DEBUG std::cerr << "SensorStreamController: requested port '" << port_utf8 << "' not available and no other ports detected.\n"; +#endif last_error_ = QString::fromUtf8("未检测到串口"); return false; } +#if DEBUG std::cerr << "SensorStreamController: requested port '" << port_utf8 << "' not available, falling back to first detected port.\n"; +#endif port_utf8 = ports.front().port; } } else if (!ports.empty()) { port_utf8 = ports.front().port; } else { +#if DEBUG std::cerr << "SensorStreamController: no serial ports available\n"; +#endif last_error_ = QString::fromUtf8("未检测到串口"); return false; } @@ -149,7 +156,7 @@ public: return false; } - core_->set_frame_callback([this](const ffmsep::DecodedFrame& frame) { + core_->set_frame_callback([this](std::shared_ptr frame) { handle_frame(frame); }); @@ -161,44 +168,44 @@ public: } active_port_ = QString::fromStdString(cfg.port); - last_error_.clear(); - connected_ = true; - return true; - } - - void stop() { - reset_core(); - active_port_.clear(); - if (heatmap_data_ && matrix_context_) { - heatmap_data_->set(make_flat_points(matrix_context_->get())); - } - connected_ = false; - } - - [[nodiscard]] bool is_running() const noexcept { - return core_ && core_->is_running(); - } - - [[nodiscard]] bool is_connected() const noexcept { - return connected_; - } - - [[nodiscard]] QString active_port() const { - return active_port_; - } + last_error_.clear(); + connected_ = true; + return true; + } + + void stop() { + reset_core(); + active_port_.clear(); + if (heatmap_data_ && matrix_context_) { + heatmap_data_->set(make_flat_points(matrix_context_->get())); + } + connected_ = false; + } + + [[nodiscard]] bool is_running() const noexcept { + return core_ && core_->is_running(); + } + + [[nodiscard]] bool is_connected() const noexcept { + return connected_; + } + + [[nodiscard]] QString active_port() const { + return active_port_; + } [[nodiscard]] QString last_error() const { return last_error_; } private: - void reset_core() { - connected_ = false; - if (!core_) { - return; - } - core_->set_frame_callback({}); - if (core_->is_running()) { + void reset_core() { + connected_ = false; + if (!core_) { + return; + } + core_->set_frame_callback({}); + if (core_->is_running()) { core_->stop(); } if (core_->is_open()) { @@ -214,51 +221,51 @@ private: }; } - void handle_frame(const ffmsep::DecodedFrame& frame) { - if (!frame.tactile || frame.tactile_pressures.empty()) { + void handle_frame(std::shared_ptr frame) { + if (!frame->tactile || frame->tactile_pressures.empty()) { return; } - auto pressures = frame.tactile_pressures; - auto size_hint = frame.tactile_matrix_size; - auto frame_bytes = frame.frame.data; - std::vector raw_payload; - if (frame.tactile) { - raw_payload = frame.tactile->payload; - } - - QMetaObject::invokeMethod( - this, - [this, - pressures = std::move(pressures), - size_hint, - frame_bytes = std::move(frame_bytes), - raw_payload = std::move(raw_payload)]() { - const auto format_raw = [](const std::vector& data) -> std::string { - if (data.empty()) { - return "[]"; - } - std::ostringstream oss; - oss << '['; - oss << std::uppercase << std::setfill('0'); - for (std::size_t idx = 0; idx < data.size(); ++idx) { - if (idx != 0U) { - oss << ' '; - } - oss << std::setw(2) << std::hex << static_cast(data[idx]); - } - oss << ']'; - return oss.str(); - }; - - std::cout << "[Sensor] frame=" << format_raw(frame_bytes); - std::cout << " payload=" << format_raw(raw_payload); - std::cout << " received " << pressures.size() << " pressure values"; - if (size_hint) { - std::cout << " matrix=" << int(size_hint->long_edge) - << "x" << int(size_hint->short_edge); - } - const std::size_t preview = std::min(pressures.size(), 12); + auto pressures = frame->tactile_pressures; + auto size_hint = frame->tactile_matrix_size; + auto frame_bytes = frame->frame.data; + std::vector raw_payload; + if (frame->tactile) { + raw_payload = frame->tactile->payload; + } + + QMetaObject::invokeMethod( + this, + [this, + pressures = std::move(pressures), + size_hint, + frame_bytes = std::move(frame_bytes), + raw_payload = std::move(raw_payload)]() { + const auto format_raw = [](const std::vector& data) -> std::string { + if (data.empty()) { + return "[]"; + } + std::ostringstream oss; + oss << '['; + oss << std::uppercase << std::setfill('0'); + for (std::size_t idx = 0; idx < data.size(); ++idx) { + if (idx != 0U) { + oss << ' '; + } + oss << std::setw(2) << std::hex << static_cast(data[idx]); + } + oss << ']'; + return oss.str(); + }; + + std::cout << "[Sensor] frame=" << format_raw(frame_bytes); + std::cout << " payload=" << format_raw(raw_payload); + std::cout << " received " << pressures.size() << " pressure values"; + if (size_hint) { + std::cout << " matrix=" << int(size_hint->long_edge) + << "x" << int(size_hint->short_edge); + } + const std::size_t preview = std::min(pressures.size(), 12); if (preview > 0) { std::cout << " values=["; for (std::size_t idx = 0; idx < preview; ++idx) { @@ -305,107 +312,107 @@ private: Qt::QueuedConnection); } - [[nodiscard]] QSize normalize_matrix(QSize candidate, std::size_t value_count) const { - if (value_count == 0U) { - return QSize{}; - } - - const auto adapt_from = [value_count](const QSize& hint) -> std::optional { - if (hint.width() <= 0 && hint.height() <= 0) { - return std::nullopt; - } - - if (hint.width() > 0 && hint.height() > 0) { - const auto cells = static_cast(hint.width()) * - static_cast(hint.height()); - if (cells == value_count) { - return hint; - } - } - - if (hint.width() > 0) { - const auto width = static_cast(hint.width()); - if (width != 0U && (value_count % width) == 0U) { - const auto height = static_cast(value_count / width); - return QSize{hint.width(), height}; - } - } - - if (hint.height() > 0) { - const auto height = static_cast(hint.height()); - if (height != 0U && (value_count % height) == 0U) { - const auto width = static_cast(value_count / height); - return QSize{width, hint.height()}; - } - } - - return std::nullopt; - }; - - if (auto adjusted = adapt_from(candidate)) { - return *adjusted; - } - - if (auto adjusted = adapt_from(matrix_context_->get())) { - return *adjusted; - } - - const auto root = static_cast(std::sqrt(static_cast(value_count))); - for (int width = root; width >= 1; --width) { - const auto divisor = static_cast(width); - if (divisor == 0U) { - continue; - } - if ((value_count % divisor) == 0U) { - const auto height = static_cast(value_count / divisor); - return QSize{width, height}; - } - } - - return QSize{static_cast(value_count), 1}; - } + [[nodiscard]] QSize normalize_matrix(QSize candidate, std::size_t value_count) const { + if (value_count == 0U) { + return QSize{}; + } - std::shared_ptr>> heatmap_data_; - std::shared_ptr> matrix_context_; - std::unique_ptr core_; - QString active_port_; - QString last_error_; - bool connected_ = false; -}; + const auto adapt_from = [value_count](const QSize& hint) -> std::optional { + if (hint.width() <= 0 && hint.height() <= 0) { + return std::nullopt; + } -struct SensorUiState { - std::shared_ptr> link_icon = - std::make_shared>(QString::fromLatin1(material::icon::kAddLink)); - std::shared_ptr>> heatmap_data = - std::make_shared>>(); - std::shared_ptr> heatmap_matrix = - std::make_shared>(); - std::shared_ptr> port_items = - std::make_shared>(); - QString selected_port; - std::uint32_t selected_baud = 115200; - std::unique_ptr controller; + if (hint.width() > 0 && hint.height() > 0) { + const auto cells = static_cast(hint.width()) * + static_cast(hint.height()); + if (cells == value_count) { + return hint; + } + } - SensorUiState() { - const QSize size{3, 4}; - heatmap_matrix->set_silent(size); - heatmap_data->set_silent(make_flat_points(size)); - - // 初始化串口列表 - QStringList ports_list; - const auto ports = ffmsep::CPStreamCore::list_available_ports(); - ports_list.reserve(static_cast(ports.size())); - for (const auto& info : ports) { - ports_list.emplace_back(QString::fromStdString(info.port)); - } - port_items->set_silent(ports_list); - if (selected_port.isEmpty() && !ports_list.isEmpty()) { - selected_port = ports_list.front(); - } - - controller = std::make_unique(heatmap_data, heatmap_matrix); - } -}; + if (hint.width() > 0) { + const auto width = static_cast(hint.width()); + if (width != 0U && (value_count % width) == 0U) { + const auto height = static_cast(value_count / width); + return QSize{hint.width(), height}; + } + } + + if (hint.height() > 0) { + const auto height = static_cast(hint.height()); + if (height != 0U && (value_count % height) == 0U) { + const auto width = static_cast(value_count / height); + return QSize{width, hint.height()}; + } + } + + return std::nullopt; + }; + + if (auto adjusted = adapt_from(candidate)) { + return *adjusted; + } + + if (auto adjusted = adapt_from(matrix_context_->get())) { + return *adjusted; + } + + const auto root = static_cast(std::sqrt(static_cast(value_count))); + for (int width = root; width >= 1; --width) { + const auto divisor = static_cast(width); + if (divisor == 0U) { + continue; + } + if ((value_count % divisor) == 0U) { + const auto height = static_cast(value_count / divisor); + return QSize{width, height}; + } + } + + return QSize{static_cast(value_count), 1}; + } + + std::shared_ptr>> heatmap_data_; + std::shared_ptr> matrix_context_; + std::unique_ptr core_; + QString active_port_; + QString last_error_; + bool connected_ = false; +}; + +struct SensorUiState { + std::shared_ptr> link_icon = + std::make_shared>(QString::fromLatin1(material::icon::kAddLink)); + std::shared_ptr>> heatmap_data = + std::make_shared>>(); + std::shared_ptr> heatmap_matrix = + std::make_shared>(); + std::shared_ptr> port_items = + std::make_shared>(); + QString selected_port; + std::uint32_t selected_baud = 115200; + std::unique_ptr controller; + + SensorUiState() { + const QSize size{3, 4}; + heatmap_matrix->set_silent(size); + heatmap_data->set_silent(make_flat_points(size)); + + // 初始化串口列表 + QStringList ports_list; + const auto ports = ffmsep::CPStreamCore::list_available_ports(); + ports_list.reserve(static_cast(ports.size())); + for (const auto& info : ports) { + ports_list.emplace_back(QString::fromStdString(info.port)); + } + port_items->set_silent(ports_list); + if (selected_port.isEmpty() && !ports_list.isEmpty()) { + selected_port = ports_list.front(); + } + + controller = std::make_unique(heatmap_data, heatmap_matrix); + } +}; SensorUiState& sensor_state() { static SensorUiState state; @@ -418,10 +425,10 @@ static auto ComConfigComponent(ThemeManager& manager) { auto& sensor = sensor_state(); auto link_icon_context = sensor.link_icon; - // 串口下拉:改为绑定可变数据源,初始值由 SensorUiState 构造时填充 - if (sensor.selected_port.isEmpty() && !sensor.port_items->get().isEmpty()) { - sensor.selected_port = sensor.port_items->get().front(); - } + // 串口下拉:改为绑定可变数据源,初始值由 SensorUiState 构造时填充 + if (sensor.selected_port.isEmpty() && !sensor.port_items->get().isEmpty()) { + sensor.selected_port = sensor.port_items->get().front(); + } const QStringList baud_items{ QString::fromLatin1("9600"), @@ -440,21 +447,21 @@ static auto ComConfigComponent(ThemeManager& manager) { // slogen_context, // }, // }, - lnpro::Item { - slpro::ThemeManager {manager}, - slpro::LeadingIcon {material::icon::kArrowDropDown, material::regular::font}, - slpro::IndexChanged {[sensor_ptr = &sensor](auto& self){ - const auto text = self.currentText(); - if (!text.isEmpty()) { - sensor_ptr->selected_port = text; - } - }}, - slpro::LeadingText {"COM"}, - MutableForward { - slpro::SelectItems {}, - sensor.port_items, - }, - }, + lnpro::Item { + slpro::ThemeManager {manager}, + slpro::LeadingIcon {material::icon::kArrowDropDown, material::regular::font}, + slpro::IndexChanged {[sensor_ptr = &sensor](auto& self){ + const auto text = self.currentText(); + if (!text.isEmpty()) { + sensor_ptr->selected_port = text; + } + }}, + slpro::LeadingText {"COM"}, + MutableForward { + slpro::SelectItems {}, + sensor.port_items, + }, + }, lnpro::Item { slpro::ThemeManager {manager }, slpro::LeadingIcon { material::icon::kArrowDropDown, material::regular::font}, @@ -484,10 +491,10 @@ static auto ComConfigComponent(ThemeManager& manager) { if (!sensor.controller) { return; } - if (sensor.controller->is_connected()) { - sensor.controller->stop(); - link_icon_context->set(QString::fromLatin1(material::icon::kAddLink)); - } else { + if (sensor.controller->is_connected()) { + sensor.controller->stop(); + link_icon_context->set(QString::fromLatin1(material::icon::kAddLink)); + } else { const auto port = sensor.selected_port; const auto baud = sensor.selected_baud == 0U ? 115200U : sensor.selected_baud; if (sensor.controller->start(port, baud)) { @@ -501,34 +508,34 @@ static auto ComConfigComponent(ThemeManager& manager) { } } } }, - lnpro::Item { - ibpro::ThemeManager { manager }, - ibpro::FixedSize { 40, 40 }, - ibpro::Color { IconButton::Color::TONAL }, - ibpro::Font { material::kRoundSmallFont }, - ibpro::FontIcon { material::icon::kRefresh }, - ibpro::Clickable {[&sensor] { - // 刷新串口列表 - QStringList ports_list; - const auto ports = ffmsep::CPStreamCore::list_available_ports(); - ports_list.reserve(static_cast(ports.size())); - for (const auto& info : ports) { - ports_list.emplace_back(QString::fromStdString(info.port)); - } - - // 保持原选择(若仍然存在) - if (!sensor.selected_port.isEmpty()) { - const bool exists = ports_list.contains(sensor.selected_port); - if (!exists) { - sensor.selected_port = ports_list.isEmpty() ? QString{} : ports_list.front(); - } - } else if (!ports_list.isEmpty()) { - sensor.selected_port = ports_list.front(); - } - - sensor.port_items->set(std::move(ports_list)); - }}, - }, + lnpro::Item { + ibpro::ThemeManager { manager }, + ibpro::FixedSize { 40, 40 }, + ibpro::Color { IconButton::Color::TONAL }, + ibpro::Font { material::kRoundSmallFont }, + ibpro::FontIcon { material::icon::kRefresh }, + ibpro::Clickable {[&sensor] { + // 刷新串口列表 + QStringList ports_list; + const auto ports = ffmsep::CPStreamCore::list_available_ports(); + ports_list.reserve(static_cast(ports.size())); + for (const auto& info : ports) { + ports_list.emplace_back(QString::fromStdString(info.port)); + } + + // 保持原选择(若仍然存在) + if (!sensor.selected_port.isEmpty()) { + const bool exists = ports_list.contains(sensor.selected_port); + if (!exists) { + sensor.selected_port = ports_list.isEmpty() ? QString{} : ports_list.front(); + } + } else if (!ports_list.isEmpty()) { + sensor.selected_port = ports_list.front(); + } + + sensor.port_items->set(std::move(ports_list)); + }}, + }, }; return new Widget { diff --git a/examples/cpstream_demo.cc b/examples/cpstream_demo.cc index 77db21b..712795b 100644 --- a/examples/cpstream_demo.cc +++ b/examples/cpstream_demo.cc @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -115,40 +116,40 @@ int main(int argc, char** argv) { // Also demonstrate polling API (in case users don't want callbacks) while (g_running) { - ffmsep::DecodedFrame df; - if (core.wait_for_frame(df, 200ms)) { - std::cout << "Frame pts=" << df.pts - << " bytes=" << df.frame.data.size(); - if (df.tactile) { - const auto& tf = *df.tactile; + std::shared_ptr df; + if (core.wait_for_frame(df, 200ms) && df) { + std::cout << "Frame pts=" << df->pts + << " bytes=" << df->frame.data.size(); + if (df->tactile) { + const auto& tf = *df->tactile; std::cout << " addr=" << int(tf.device_address) << " func=0x" << std::hex << std::uppercase << int(tf.response_function) << std::dec; - if (df.tactile_matrix_size) { - const auto& ms = *df.tactile_matrix_size; + if (df->tactile_matrix_size) { + const auto& ms = *df->tactile_matrix_size; std::cout << " matrix=" << int(ms.long_edge) << "x" << int(ms.short_edge); } - if (!df.tactile_pressures.empty()) { - std::cout << " pressures=" << df.tactile_pressures.size() + if (!df->tactile_pressures.empty()) { + std::cout << " pressures=" << df->tactile_pressures.size() << " values=["; - const std::size_t preview = std::min(df.tactile_pressures.size(), 8); + const std::size_t preview = std::min(df->tactile_pressures.size(), 8); for (std::size_t idx = 0; idx < preview; ++idx) { if (idx != 0U) { std::cout << ", "; } - std::cout << df.tactile_pressures[idx]; + std::cout << df->tactile_pressures[idx]; } - if (preview < df.tactile_pressures.size()) { + if (preview < df->tactile_pressures.size()) { std::cout << ", ..."; } std::cout << "]"; } std::cout << "\n raw="; - print_hex(df.frame.data); + print_hex(df->frame.data); } else { std::cout << " raw="; - print_hex(df.frame.data); + print_hex(df->frame.data); } std::cout << "\n"; } diff --git a/modern-qt/utility/material-icon.hh b/modern-qt/utility/material-icon.hh index 4b4d683..72c0826 100644 --- a/modern-qt/utility/material-icon.hh +++ b/modern-qt/utility/material-icon.hh @@ -27,15 +27,12 @@ namespace material { private: static QString get_font_family(int fontId, const QString& fallback) { if (fontId == -1) { - qWarning() << "Failed to load font:" << fallback; return fallback; } QStringList families = QFontDatabase::applicationFontFamilies(fontId); if (families.isEmpty()) { - qWarning() << "No families found for font:" << fallback; return fallback; } - qDebug() << "families found for font:" << families; return families.first(); } };