470 lines
17 KiB
C++
470 lines
17 KiB
C++
//
|
||
// 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 <concepts>
|
||
#include <creeper-qt/utility/material-icon.hh>
|
||
#include <creeper-qt/utility/wrapper/mutable-value.hh>
|
||
#include <creeper-qt/widget/buttons/icon-button.hh>
|
||
#include <creeper-qt/widget/cards/filled-card.hh>
|
||
#include <creeper-qt/widget/cards/outlined-card.hh>
|
||
#include <creeper-qt/widget/dropdown-menu.hh>
|
||
#include <creeper-qt/widget/image.hh>
|
||
#include <creeper-qt/widget/shape/wave-circle.hh>
|
||
#include <creeper-qt/widget/sliders.hh>
|
||
#include <creeper-qt/widget/switch.hh>
|
||
#include <creeper-qt/widget/text-fields.hh>
|
||
#include <creeper-qt/widget/text.hh>
|
||
#include <creeper-qt/widget/buttons/icon-button.hh>
|
||
#include <cstddef>
|
||
#include <iterator>
|
||
#include <qcontainerfwd.h>
|
||
#include <qlogging.h>
|
||
#include <qnamespace.h>
|
||
#include <ranges>
|
||
#include <utility>
|
||
#include "globalhelper.hh"
|
||
#include <functional>
|
||
#include <memory>
|
||
#include <QComboBox>
|
||
#include <QDialog>
|
||
#include <QDialogButtonBox>
|
||
#include <QFormLayout>
|
||
#include <QLineEdit>
|
||
#include <QSpinBox>
|
||
#include <QVBoxLayout>
|
||
#include <sys/stat.h>
|
||
|
||
namespace repest_literals {
|
||
template<class F>
|
||
concept IndexInvocable = std::invocable<F, std::size_t>;
|
||
template<IndexInvocable F>
|
||
void operator*(F&& f, std::size_t n) {
|
||
std::ranges::for_each(std::views::iota(std::size_t{ 0 }, n), std::forward<F>(f));
|
||
}
|
||
template<IndexInvocable F>
|
||
void operator*(std::size_t n, F&& f) {
|
||
std::ranges::for_each(std::views::iota(std::size_t{ 0 }, n), std::forward<F>(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<MutableValue<std::vector<ConfigProfile>>> g_profiles_store;
|
||
static std::function<void()> g_profiles_refresh;
|
||
|
||
static void ShowEditProfileDialog(
|
||
const ConfigProfile& current,
|
||
const std::shared_ptr<MutableValue<std::vector<ConfigProfile>>>& 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 {[]{ qDebug() << "ImportProfileLongItem"; }},
|
||
};
|
||
}
|
||
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 {[]{ qDebug() << "ExportProfileLongItem"; }},
|
||
};
|
||
}
|
||
static auto ProfileItemComponent(creeper::ThemeManager& manager, ConfigProfile& profile,
|
||
const std::shared_ptr<MutableValue<std::vector<ConfigProfile>>>& 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<Col>{
|
||
lnpro::Item<Row>{
|
||
// row::pro::Stretch {1},
|
||
row::pro::Item<Text>{
|
||
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<IconButton>{
|
||
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<IconButton>{
|
||
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>{
|
||
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>{
|
||
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>{
|
||
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>{
|
||
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<ConfigProfile>& profiles,
|
||
const std::shared_ptr<MutableValue<std::vector<ConfigProfile>>>& 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<ConfigProfile&>(profiles[i]), profiles_store));
|
||
};
|
||
flow.update();
|
||
}
|
||
|
||
static void AttachProfilesObserver(const std::shared_ptr<MutableValue<std::vector<ConfigProfile>>>& store,
|
||
Flow& flow, creeper::ThemeManager& manager) {
|
||
struct Functor : creeper::MutableValue<std::vector<ConfigProfile>>::Functor {
|
||
Flow& flow;
|
||
creeper::ThemeManager& manager;
|
||
std::weak_ptr<MutableValue<std::vector<ConfigProfile>>> store_ptr;
|
||
Functor(Flow& f, creeper::ThemeManager& m,
|
||
std::weak_ptr<MutableValue<std::vector<ConfigProfile>>> s) noexcept
|
||
: flow(f)
|
||
, manager(m)
|
||
, store_ptr(std::move(s)) { }
|
||
void update(const std::vector<ConfigProfile>& value) override {
|
||
PopulateProfiles(flow, manager, value, store_ptr.lock());
|
||
manager.apply_theme();
|
||
}
|
||
};
|
||
auto functor = std::make_unique<Functor>(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<QWidget> {
|
||
auto profiles_ctx = std::make_shared<MutableValue<std::vector<ConfigProfile>>>(
|
||
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<Col>{
|
||
lnpro::Alignment {Qt::AlignTop},
|
||
lnpro::Margin {10},
|
||
lnpro::Spacing {10},
|
||
lnpro::Item<Row> {
|
||
lnpro::Item{
|
||
AddProfileLongItem(state.manager)
|
||
},
|
||
lnpro::Item {
|
||
ImportProfileLongItem(state.manager)
|
||
},
|
||
lnpro::Item {
|
||
ExportProfileLongItem(state.manager)
|
||
}
|
||
},
|
||
|
||
|
||
col::pro::Item<ScrollArea>{
|
||
scroll::pro::ThemeManager { state.manager },
|
||
scroll::pro::HorizontalScrollBarPolicy { Qt::ScrollBarAlwaysOff },
|
||
scroll::pro::Item {
|
||
profiles_flow
|
||
},
|
||
},
|
||
|
||
},
|
||
};
|
||
}
|