201 lines
8.1 KiB
C++
201 lines
8.1 KiB
C++
#include "AxesSettingsPanel.hpp"
|
||
|
||
#include <algorithm>
|
||
|
||
#include <QCheckBox>
|
||
#include <QComboBox>
|
||
#include <QDoubleSpinBox>
|
||
#include <QHBoxLayout>
|
||
#include <QLabel>
|
||
#include <QMouseEvent>
|
||
#include <QPushButton>
|
||
#include <QSlider>
|
||
#include <QStyle>
|
||
#include <QStyleOptionSlider>
|
||
#include <QVBoxLayout>
|
||
|
||
#include "Theme.hpp"
|
||
|
||
namespace geopro::app {
|
||
|
||
namespace {
|
||
// 点击轨道直接定位到点击处(默认 QSlider 点轨道只按 pageStep 步进;用户要求点哪跳哪)。
|
||
class ClickJumpSlider : public QSlider {
|
||
public:
|
||
using QSlider::QSlider;
|
||
|
||
protected:
|
||
void mousePressEvent(QMouseEvent* e) override {
|
||
if (e->button() == Qt::LeftButton && orientation() == Qt::Horizontal) {
|
||
QStyleOptionSlider opt;
|
||
initStyleOption(&opt);
|
||
const QRect handle =
|
||
style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, this);
|
||
if (!handle.contains(e->pos())) { // 点在轨道(非手柄) → 跳到点击位置
|
||
const int span = width() - handle.width();
|
||
const int x = e->pos().x() - handle.width() / 2;
|
||
setValue(QStyle::sliderValueFromPosition(minimum(), maximum(), x, span));
|
||
e->accept();
|
||
return;
|
||
}
|
||
}
|
||
QSlider::mousePressEvent(e); // 点手柄 → 正常拖动
|
||
}
|
||
};
|
||
} // namespace
|
||
|
||
AxesSettingsPanel::AxesSettingsPanel(QWidget* parent) : QFrame(parent) {
|
||
setFrameShape(QFrame::StyledPanel);
|
||
applyTokenizedStyleSheet(
|
||
this, QStringLiteral("QFrame{background:{{bg/panel}};border:1px solid {{border/default}};"
|
||
"border-radius:8px;}")); // radius/lg=8(规范§3.2 画布浮窗)
|
||
setFixedWidth(320);
|
||
|
||
auto* v = new QVBoxLayout(this);
|
||
v->setContentsMargins(space::kMd, space::kMd, space::kMd, space::kMd);
|
||
v->setSpacing(space::kSm);
|
||
|
||
// 标题行:坐标轴设置 + × 关闭。
|
||
auto* titleRow = new QHBoxLayout();
|
||
auto* title = new QLabel(QStringLiteral("坐标轴设置"), this);
|
||
title->setStyleSheet(QStringLiteral("font-weight:600;font-size:15px;border:none;"));
|
||
auto* close = new QPushButton(QStringLiteral("✕"), this);
|
||
close->setFixedSize(24, 24);
|
||
close->setCursor(Qt::PointingHandCursor);
|
||
// 显式覆盖全局 QPushButton 的 padding(6px 14px)/border——否则 24×24 容不下 padding,× 被挤出不可见。
|
||
// 颜色走 token(避免深色模式失效)。
|
||
applyTokenizedStyleSheet(
|
||
close, QStringLiteral(
|
||
"QPushButton{border:none;background:transparent;padding:0;margin:0;font-size:16px;"
|
||
"color:{{text/secondary}};}"
|
||
"QPushButton:hover{color:{{accent/primary}};}"));
|
||
connect(close, &QPushButton::clicked, this, &AxesSettingsPanel::closed);
|
||
titleRow->addWidget(title);
|
||
titleRow->addStretch(1);
|
||
titleRow->addWidget(close);
|
||
v->addLayout(titleRow);
|
||
|
||
// 坐标轴单位。
|
||
auto* unitLbl = new QLabel(QStringLiteral("坐标轴单位"), this);
|
||
unitLbl->setStyleSheet(QStringLiteral("border:none;"));
|
||
v->addWidget(unitLbl);
|
||
unit_ = new QComboBox(this);
|
||
unit_->addItems({QStringLiteral("米"), QStringLiteral("英尺")});
|
||
v->addWidget(unit_);
|
||
|
||
// X / Y / Z 三轴(各:显示开关 + 最小值/最大值)。与上方单位组留间距。
|
||
v->addSpacing(space::kSm);
|
||
x_ = addAxisRow(v, QStringLiteral("X轴"), {true, -500, 500});
|
||
y_ = addAxisRow(v, QStringLiteral("Y轴"), {true, -500, 500});
|
||
z_ = addAxisRow(v, QStringLiteral("Z轴"), {true, 0, 200});
|
||
|
||
// 放大系数(=垂直夸张):滑块 1~10×。仅改面板内数值/标签,点「应用」才统一生效(不再实时)。
|
||
// 与上方坐标轴组留出间距(用户反馈:滑块离上面项目太近)。
|
||
v->addSpacing(space::kMd);
|
||
auto* scaleRow = new QHBoxLayout();
|
||
auto* scaleLbl = new QLabel(QStringLiteral("放大系数"), this);
|
||
scaleLbl->setStyleSheet(QStringLiteral("border:none;"));
|
||
scaleRow->addWidget(scaleLbl);
|
||
scaleSlider_ = new ClickJumpSlider(Qt::Horizontal, this);
|
||
scaleSlider_->setMinimum(1);
|
||
scaleSlider_->setMaximum(10);
|
||
scaleSlider_->setValue(1);
|
||
scaleSlider_->setSingleStep(1);
|
||
scaleSlider_->setPageStep(1); // 点击轨道按 1 步移动(默认 pageStep=10 → 点一下直接跳到 10 的 bug)
|
||
scaleLabel_ = new QLabel(QStringLiteral("1.0×"), this);
|
||
applyTokenizedStyleSheet(scaleLabel_,
|
||
QStringLiteral("border:none;color:{{text/secondary}};min-width:36px;"));
|
||
connect(scaleSlider_, &QSlider::valueChanged, this,
|
||
[this](int v) { scaleLabel_->setText(QStringLiteral("%1.0×").arg(v)); });
|
||
scaleRow->addWidget(scaleSlider_, 1);
|
||
scaleRow->addWidget(scaleLabel_);
|
||
v->addLayout(scaleRow);
|
||
|
||
// 取消 / 应用。
|
||
auto* btns = new QHBoxLayout();
|
||
auto* cancel = new QPushButton(QStringLiteral("取消"), this);
|
||
auto* apply = new QPushButton(QStringLiteral("应用"), this);
|
||
apply->setDefault(true); // 主按钮:走全局 QPushButton:default 样式(accent/primary,随主题),不再硬编码蓝
|
||
connect(cancel, &QPushButton::clicked, this, &AxesSettingsPanel::closed);
|
||
connect(apply, &QPushButton::clicked, this, [this] {
|
||
auto rd = [](const Row& r) {
|
||
return AxisRange{r.show->isChecked(), r.lo->value(), r.hi->value()};
|
||
};
|
||
emit applied(rd(x_), rd(y_), rd(z_), unit_->currentIndex(),
|
||
static_cast<double>(scaleSlider_->value()));
|
||
});
|
||
btns->addStretch(1);
|
||
btns->addWidget(cancel);
|
||
btns->addWidget(apply);
|
||
v->addLayout(btns);
|
||
}
|
||
|
||
AxesSettingsPanel::Row AxesSettingsPanel::addAxisRow(QVBoxLayout* col, const QString& title,
|
||
const AxisRange& def) {
|
||
Row r;
|
||
auto* head = new QHBoxLayout();
|
||
auto* lbl = new QLabel(title, this);
|
||
lbl->setStyleSheet(QStringLiteral("font-weight:600;border:none;"));
|
||
head->addWidget(lbl);
|
||
head->addStretch(1);
|
||
auto* showLbl = new QLabel(QStringLiteral("显示"), this);
|
||
showLbl->setStyleSheet(QStringLiteral("border:none;"));
|
||
head->addWidget(showLbl);
|
||
r.show = new QCheckBox(this);
|
||
r.show->setChecked(def.show);
|
||
head->addWidget(r.show);
|
||
col->addLayout(head);
|
||
|
||
auto* range = new QHBoxLayout();
|
||
auto* loCol = new QVBoxLayout();
|
||
auto* loLbl = new QLabel(QStringLiteral("最小值"), this);
|
||
applyTokenizedStyleSheet(loLbl, QStringLiteral("border:none;color:{{text/tertiary}};"));
|
||
loCol->addWidget(loLbl);
|
||
r.lo = new QDoubleSpinBox(this);
|
||
r.lo->setRange(-1e6, 1e6);
|
||
r.lo->setValue(def.min);
|
||
loCol->addWidget(r.lo);
|
||
range->addLayout(loCol);
|
||
auto* hiCol = new QVBoxLayout();
|
||
auto* hiLbl = new QLabel(QStringLiteral("最大值"), this);
|
||
applyTokenizedStyleSheet(hiLbl, QStringLiteral("border:none;color:{{text/tertiary}};"));
|
||
hiCol->addWidget(hiLbl);
|
||
r.hi = new QDoubleSpinBox(this);
|
||
r.hi->setRange(-1e6, 1e6);
|
||
r.hi->setValue(def.max);
|
||
hiCol->addWidget(r.hi);
|
||
range->addLayout(hiCol);
|
||
col->addLayout(range);
|
||
|
||
// 取消「显示」时禁用该轴的最小/最大值编辑(隐藏的轴改范围无意义)。
|
||
auto* lo = r.lo;
|
||
auto* hi = r.hi;
|
||
connect(r.show, &QCheckBox::toggled, this, [lo, hi](bool on) {
|
||
lo->setEnabled(on);
|
||
hi->setEnabled(on);
|
||
});
|
||
r.lo->setEnabled(def.show);
|
||
r.hi->setEnabled(def.show);
|
||
return r;
|
||
}
|
||
|
||
void AxesSettingsPanel::setValues(AxisRange x, AxisRange y, AxisRange z, int unitIdx, double scale) {
|
||
auto wr = [](const Row& r, const AxisRange& a) {
|
||
r.show->setChecked(a.show);
|
||
r.lo->setValue(a.min);
|
||
r.hi->setValue(a.max);
|
||
r.lo->setEnabled(a.show); // 回灌时同步禁用态(隐藏轴 → 禁编辑)
|
||
r.hi->setEnabled(a.show);
|
||
};
|
||
wr(x_, x);
|
||
wr(y_, y);
|
||
wr(z_, z);
|
||
unit_->setCurrentIndex(unitIdx);
|
||
const int v = std::max(1, std::min(10, static_cast<int>(scale + 0.5)));
|
||
QSignalBlocker block(scaleSlider_); // 回灌不触发 sliderReleased
|
||
scaleSlider_->setValue(v);
|
||
scaleLabel_->setText(QStringLiteral("%1.0×").arg(v));
|
||
}
|
||
|
||
} // namespace geopro::app
|