// // Created by Lenn on 2025/11/21. // #include "component.hh" #include "base/globalhelper.hh" #include "creeper-qt/utility/theme/theme.hh" #include "creeper-qt/utility/wrapper/layout.hh" #include "creeper-qt/utility/wrapper/widget.hh" #include "creeper-qt/layout/flow.hh" #include "creeper-qt/layout/linear.hh" #include "creeper-qt/layout/scroll.hh" #include "creeper-qt/widget/buttons/filled-button.hh" #include "creeper-qt/widget/cards/basic-card.hh" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "globalhelper.hh" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace repest_literals { template concept IndexInvocable = std::invocable; template void operator*(F&& f, std::size_t n) { std::ranges::for_each(std::views::iota(std::size_t{ 0 }, n), std::forward(f)); } template void operator*(std::size_t n, F&& f) { std::ranges::for_each(std::views::iota(std::size_t{ 0 }, n), std::forward(f)); } } // namespace repest_literals using namespace creeper; namespace capro = card::pro; namespace lnpro = linear::pro; namespace ibpro = icon_button::pro; namespace fbpro = filled_button::pro; static std::weak_ptr>> g_profiles_store; static std::function g_profiles_refresh; static QString TactileTypeToJsonString(Tactile_TYPE type) { switch (type) { case Tactile_TYPE::PiezoresistiveA: return QStringLiteral("PiezoresistiveA"); case Tactile_TYPE::PiezoresistiveB: return QStringLiteral("PiezoresistiveB"); case Tactile_TYPE::Hall: default: return QStringLiteral("Hall"); } } static Tactile_TYPE TactileTypeFromJsonString(const QString& str) { if (str == QStringLiteral("PiezoresistiveA")) { return Tactile_TYPE::PiezoresistiveA; } if (str == QStringLiteral("PiezoresistiveB")) { return Tactile_TYPE::PiezoresistiveB; } return Tactile_TYPE::Hall; } static QJsonObject ConfigProfileToJson(const ConfigProfile& profile) { QJsonObject obj; obj.insert(QStringLiteral("name"), profile.name); obj.insert(QStringLiteral("type"), TactileTypeToJsonString(profile.type)); obj.insert(QStringLiteral("matrix_width"), profile.matrix_width); obj.insert(QStringLiteral("matrix_height"), profile.matrix_height); obj.insert(QStringLiteral("range_left"), profile.range_left); obj.insert(QStringLiteral("range_right"), profile.range_right); obj.insert(QStringLiteral("baud_rate"), profile.baud_rate); return obj; } static bool ConfigProfileFromJson(const QJsonObject& obj, ConfigProfile& out_profile) { const auto name = obj.value(QStringLiteral("name")).toString(); if (name.isEmpty()) { return false; } out_profile.name = name; out_profile.type = TactileTypeFromJsonString(obj.value(QStringLiteral("type")).toString()); out_profile.matrix_width = obj.value(QStringLiteral("matrix_width")).toInt(0); out_profile.matrix_height = obj.value(QStringLiteral("matrix_height")).toInt(0); out_profile.range_left = obj.value(QStringLiteral("range_left")).toInt(0); out_profile.range_right = obj.value(QStringLiteral("range_right")).toInt(0); out_profile.baud_rate = obj.value(QStringLiteral("baud_rate")).toInt(0); return true; } static void ShowEditProfileDialog( const ConfigProfile& current, const std::shared_ptr>>& profiles_store) { QDialog dialog; dialog.setWindowTitle(QStringLiteral("修改配置")); auto* layout = new QVBoxLayout(&dialog); auto* form = new QFormLayout(); auto* name_edit = new QLineEdit(&dialog); name_edit->setText(current.name); auto* type_combo = new QComboBox(&dialog); type_combo->addItem(QStringLiteral("压阻A型")); type_combo->addItem(QStringLiteral("压阻B型")); type_combo->addItem(QStringLiteral("霍尔型")); switch (current.type) { case Tactile_TYPE::PiezoresistiveA: type_combo->setCurrentIndex(0); break; case Tactile_TYPE::PiezoresistiveB: type_combo->setCurrentIndex(1); break; case Tactile_TYPE::Hall: type_combo->setCurrentIndex(2); break; } auto* width_spin = new QSpinBox(&dialog); auto* height_spin = new QSpinBox(&dialog); width_spin->setRange(1, 64); height_spin->setRange(1, 64); width_spin->setValue(current.matrix_width); height_spin->setValue(current.matrix_height); auto* left_spin = new QSpinBox(&dialog); auto* right_spin = new QSpinBox(&dialog); left_spin->setRange(-100000, 100000); right_spin->setRange(-100000, 100000); left_spin->setValue(current.range_left); right_spin->setValue(current.range_right); auto* baud_spin = new QSpinBox(&dialog); baud_spin->setRange(1200, 10000000); baud_spin->setValue(current.baud_rate); form->addRow(QStringLiteral("名称"), name_edit); form->addRow(QStringLiteral("类型"), type_combo); form->addRow(QStringLiteral("宽"), width_spin); form->addRow(QStringLiteral("高"), height_spin); form->addRow(QStringLiteral("量程左"), left_spin); form->addRow(QStringLiteral("量程右"), right_spin); form->addRow(QStringLiteral("波特率"), baud_spin); layout->addLayout(form); auto* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &dialog); layout->addWidget(buttons); QObject::connect(buttons, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); QObject::connect(buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); if (dialog.exec() == QDialog::Accepted) { ConfigProfile updated = current; updated.name = name_edit->text(); updated.type = [idx = type_combo->currentIndex()] { switch (idx) { case 0: return Tactile_TYPE::PiezoresistiveA; case 1: return Tactile_TYPE::PiezoresistiveB; default: return Tactile_TYPE::Hall; } }(); updated.matrix_width = width_spin->value(); updated.matrix_height = height_spin->value(); updated.range_left = left_spin->value(); updated.range_right = right_spin->value(); updated.baud_rate = baud_spin->value(); GlobalHelper::instance().remove_profile(current.name); GlobalHelper::instance().add_new_profile(updated); GlobalHelper::instance().reload_profiles(); if (profiles_store) { profiles_store->set(GlobalHelper::instance().get_all_profile()); } RefreshProfilesForView(); if (g_profiles_refresh) { g_profiles_refresh(); } } } static void ShowAddProfileDialog() { QDialog dialog; dialog.setWindowTitle(QStringLiteral("添加配置")); auto* layout = new QVBoxLayout(&dialog); auto* form = new QFormLayout(); auto* name_edit = new QLineEdit(&dialog); auto* type_combo = new QComboBox(&dialog); type_combo->addItem(QStringLiteral("压阻A型")); type_combo->addItem(QStringLiteral("压阻B型")); type_combo->addItem(QStringLiteral("霍尔型")); auto* width_spin = new QSpinBox(&dialog); auto* height_spin = new QSpinBox(&dialog); width_spin->setRange(1, 64); height_spin->setRange(1, 64); auto* left_spin = new QSpinBox(&dialog); auto* right_spin = new QSpinBox(&dialog); left_spin->setRange(-100000, 100000); right_spin->setRange(-100000, 100000); auto* baud_spin = new QSpinBox(&dialog); baud_spin->setRange(1200, 10000000); baud_spin->setValue(115200); form->addRow(QStringLiteral("名称"), name_edit); form->addRow(QStringLiteral("类型"), type_combo); form->addRow(QStringLiteral("宽"), width_spin); form->addRow(QStringLiteral("高"), height_spin); form->addRow(QStringLiteral("量程左"), left_spin); form->addRow(QStringLiteral("量程右"), right_spin); form->addRow(QStringLiteral("波特率"), baud_spin); layout->addLayout(form); auto* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &dialog); layout->addWidget(buttons); QObject::connect(buttons, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); QObject::connect(buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); if (dialog.exec() == QDialog::Accepted) { ConfigProfile profile; profile.name = name_edit->text(); profile.type = [idx = type_combo->currentIndex()] { switch (idx) { case 0: return Tactile_TYPE::PiezoresistiveA; case 1: return Tactile_TYPE::PiezoresistiveB; default: return Tactile_TYPE::Hall; } }(); profile.matrix_width = width_spin->value(); profile.matrix_height = height_spin->value(); profile.range_left = left_spin->value(); profile.range_right = right_spin->value(); profile.baud_rate = baud_spin->value(); GlobalHelper::instance().add_new_profile(profile); GlobalHelper::instance().reload_profiles(); if (auto store = g_profiles_store.lock()) { store->set(GlobalHelper::instance().get_all_profile()); } RefreshProfilesForView(); if (g_profiles_refresh) { g_profiles_refresh(); } } } static auto AddProfileLongItem(creeper::ThemeManager& manager) { return new FilledButton { fbpro::ThemeManager {manager}, fbpro::Text {QStringLiteral("添加配置")}, widget::pro::SizePolicy {QSizePolicy::Fixed, QSizePolicy::Expanding}, widget::pro::MinimumHeight {40}, widget::pro::MinimumWidth {320}, fbpro::Radius {12}, fbpro::Clickable {[]{ ShowAddProfileDialog(); }}, }; } static auto ImportProfileLongItem(creeper::ThemeManager& manager) { return new FilledButton { fbpro::ThemeManager {manager}, fbpro::Text {QStringLiteral("导入配置")}, widget::pro::SizePolicy {QSizePolicy::Fixed, QSizePolicy::Expanding}, widget::pro::MinimumHeight {40}, widget::pro::MinimumWidth {320}, fbpro::Radius {12}, fbpro::Clickable {[]{ const QString file_name = QFileDialog::getOpenFileName( nullptr, QStringLiteral("导入配置"), QString(), QStringLiteral("配置文件 (*.conf);;所有文件 (*.*)")); if (file_name.isEmpty()) { return; } QFile file(file_name); if (!file.open(QIODevice::ReadOnly)) { QMessageBox::warning(nullptr, QStringLiteral("导入配置"), QStringLiteral("无法打开配置文件。")); return; } const QByteArray data = file.readAll(); file.close(); QJsonParseError parse_error {}; const QJsonDocument doc = QJsonDocument::fromJson(data, &parse_error); if (doc.isNull() || parse_error.error != QJsonParseError::NoError || !doc.isArray()) { QMessageBox::warning(nullptr, QStringLiteral("导入配置"), QStringLiteral("配置文件格式不正确。")); return; } const QJsonArray array = doc.array(); std::vector imported_profiles; imported_profiles.reserve(static_cast(array.size())); for (const auto& value : array) { if (!value.isObject()) { continue; } ConfigProfile profile {}; if (ConfigProfileFromJson(value.toObject(), profile)) { imported_profiles.push_back(profile); } } if (imported_profiles.empty()) { QMessageBox::warning(nullptr, QStringLiteral("导入配置"), QStringLiteral("配置文件中没有有效的配置。")); return; } auto& helper = GlobalHelper::instance(); // 清空现有配置 const auto existing = helper.get_all_profile(); for (const auto& p : existing) { helper.remove_profile(p.name); } // 写入新配置到 ini for (const auto& p : imported_profiles) { helper.add_new_profile(p); } helper.reload_profiles(); if (auto store = g_profiles_store.lock()) { store->set(helper.get_all_profile()); } RefreshProfilesForView(); if (g_profiles_refresh) { g_profiles_refresh(); } }}, }; } static auto ExportProfileLongItem(creeper::ThemeManager& manager) { return new FilledButton { fbpro::ThemeManager {manager}, fbpro::Text {QStringLiteral("导出配置")}, widget::pro::SizePolicy {QSizePolicy::Fixed, QSizePolicy::Expanding}, widget::pro::MinimumHeight {40}, widget::pro::MinimumWidth {320}, fbpro::Radius {12}, fbpro::Clickable {[]{ QString file_name = QFileDialog::getSaveFileName( nullptr, QStringLiteral("导出配置"), QString(), QStringLiteral("配置文件 (*.conf);;所有文件 (*.*)")); if (file_name.isEmpty()) { return; } if (!file_name.endsWith(QStringLiteral(".conf"), Qt::CaseInsensitive)) { file_name.append(QStringLiteral(".conf")); } auto& helper = GlobalHelper::instance(); helper.reload_profiles(); const auto& profiles = helper.get_all_profile(); QJsonArray array; // array.reserve(static_cast(profiles.size())); for (const auto& p : profiles) { array.append(ConfigProfileToJson(p)); } const QJsonDocument doc(array); QFile file(file_name); if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { QMessageBox::warning(nullptr, QStringLiteral("导出配置"), QStringLiteral("无法写入配置文件。")); return; } file.write(doc.toJson(QJsonDocument::Indented)); file.close(); QMessageBox::information(nullptr, QStringLiteral("导出配置"), QStringLiteral("配置导出完成。")); }}, }; } static auto ProfileItemComponent(creeper::ThemeManager& manager, ConfigProfile& profile, const std::shared_ptr>>& profiles_store) { QString matrix_size = "规格:" + QString{ "%1 * %2" }.arg(profile.matrix_width).arg(profile.matrix_height); QString value_range = "量程:" + QString{ "%1 ~ %2" }.arg(profile.range_left).arg(profile.range_right); QString tactile_type = [profile]() -> QString { switch (profile.type) { case Tactile_TYPE::PiezoresistiveA: return "类型:压阻A型"; case Tactile_TYPE::PiezoresistiveB: return "类型:压阻B型"; case Tactile_TYPE::Hall: return "类型:霍尔型"; } return "错误"; }(); QString baud_rate = "波特率:" + QString::number(profile.baud_rate); return new FilledCard{ card::pro::SizePolicy { QSizePolicy::Expanding, }, card::pro::MinimumSize { 300, 50 }, // card::pro::FixedSize { // 200, 100 // }, card::pro::ThemeManager{ manager }, card::pro::SizePolicy { QSizePolicy::Expanding }, card::pro::LevelLow, card::pro::Layout{ lnpro::Item{ // row::pro::Stretch {1}, row::pro::Item{ text::pro::AdjustSize {}, text::pro::Text{ QString{ profile.name } }, text::pro::Apply{ [&manager](Text& self) { manager.append_handler(&self, [&](const ThemeManager& manager) { const auto scheme = manager.color_scheme(); self.set_color(scheme.primary); }); } }, }, row::pro::Item{ ibpro::ThemeManager{ manager }, ibpro::FixedSize{ 30, 30 }, ibpro::Font{ material::kRegularExtraSmallFont }, icon_button::pro::FontIcon{ material::icon::kBorderColor }, ibpro::Clickable{ [profiles_store, current = profile] { ShowEditProfileDialog(current, profiles_store); } }, }, row::pro::Item{ ibpro::ThemeManager{ manager }, ibpro::FixedSize{ 30, 30 }, ibpro::Font{ material::kRegularExtraSmallFont }, icon_button::pro::FontIcon{ material::icon::kDelete }, ibpro::Clickable{ [profiles_store, name = profile.name, &manager] { GlobalHelper::instance().remove_profile(name); GlobalHelper::instance().reload_profiles(); if (profiles_store) { profiles_store->set(GlobalHelper::instance().get_all_profile()); } RefreshProfilesForView(); manager.apply_theme(); } }, } }, lnpro::Item{ text::pro::Text{ tactile_type }, text::pro::Apply{ [&manager](Text& self) { manager.append_handler(&self, [&](const ThemeManager& manager) { const auto scheme = manager.color_scheme(); self.set_color(scheme.primary); }); } } }, lnpro::Item{ text::pro::AdjustSize {}, text::pro::Text{ matrix_size }, text::pro::Apply{ [&manager](Text& self) { manager.append_handler(&self, [&](const ThemeManager& manager) { const auto scheme = manager.color_scheme(); self.set_color(scheme.primary); }); } } }, lnpro::Item{ text::pro::Text{ value_range }, text::pro::Apply{ [&manager](Text& self) { manager.append_handler(&self, [&](const ThemeManager& manager) { const auto scheme = manager.color_scheme(); self.set_color(scheme.primary); }); } } }, lnpro::Item{ text::pro::Text{ baud_rate }, text::pro::Apply{ [&manager](Text& self) { manager.append_handler(&self, [&](const ThemeManager& manager) { const auto scheme = manager.color_scheme(); self.set_color(scheme.primary); }); } } }, }, }; } static void PopulateProfiles(Flow& flow, creeper::ThemeManager& manager, const std::vector& profiles, const std::shared_ptr>>& profiles_store) { while (auto item = flow.takeAt(0)) { if (auto* w = item->widget()) { w->deleteLater(); } delete item; } using namespace repest_literals; profiles.size() * [&](auto i) { flow.addWidget(ProfileItemComponent(manager, const_cast(profiles[i]), profiles_store)); }; flow.update(); } static void AttachProfilesObserver(const std::shared_ptr>>& store, Flow& flow, creeper::ThemeManager& manager) { struct Functor : creeper::MutableValue>::Functor { Flow& flow; creeper::ThemeManager& manager; std::weak_ptr>> store_ptr; Functor(Flow& f, creeper::ThemeManager& m, std::weak_ptr>> s) noexcept : flow(f) , manager(m) , store_ptr(std::move(s)) { } void update(const std::vector& value) override { PopulateProfiles(flow, manager, value, store_ptr.lock()); manager.apply_theme(); } }; auto functor = std::make_unique(flow, manager, store); store->callbacks[&flow] = std::move(functor); auto alive = std::weak_ptr { store->alive }; QObject::connect(&flow, &QObject::destroyed, [store, alive](auto* key) { if (alive.lock()) store->callbacks.erase(key); }); } auto SettingComponent(SettingComponentState& state) noexcept -> raw_pointer { auto profiles_ctx = std::make_shared>>( GlobalHelper::instance().get_all_profile() ); g_profiles_store = profiles_ctx; g_profiles_refresh = [profiles_ctx]() { if (auto store = profiles_ctx) { store->set(GlobalHelper::instance().get_all_profile()); } }; auto* profiles_flow = new Flow { flow::pro::RowSpacing{ 10 }, flow::pro::ColSpacing{ 10 }, flow::pro::RowLimit{ 10 }, }; PopulateProfiles(*profiles_flow, state.manager, profiles_ctx->get(), profiles_ctx); AttachProfilesObserver(profiles_ctx, *profiles_flow, state.manager); return new FilledCard { capro::ThemeManager{ state.manager }, capro::SizePolicy{ QSizePolicy::Expanding }, capro::Layout{ lnpro::Alignment {Qt::AlignTop}, lnpro::Margin {10}, lnpro::Spacing {10}, lnpro::Item { lnpro::Item{ AddProfileLongItem(state.manager) }, lnpro::Item { ImportProfileLongItem(state.manager) }, lnpro::Item { ExportProfileLongItem(state.manager) } }, col::pro::Item{ scroll::pro::ThemeManager { state.manager }, scroll::pro::HorizontalScrollBarPolicy { Qt::ScrollBarAlwaysOff }, scroll::pro::Item { profiles_flow }, }, }, }; }