Files
ts-qt/components/setting.cc

438 lines
16 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// 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 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{
AddProfileLongItem(state.manager)
},
col::pro::Item<ScrollArea>{
scroll::pro::ThemeManager { state.manager },
scroll::pro::HorizontalScrollBarPolicy { Qt::ScrollBarAlwaysOff },
scroll::pro::Item {
profiles_flow
},
},
},
};
}