feat/vtk-3d-view #7

Merged
gaozheng merged 301 commits from feat/vtk-3d-view into main 2026-06-27 18:43:52 +08:00
5 changed files with 53 additions and 7 deletions
Showing only changes of commit 09a48d846b - Show all commits

View File

@ -7,14 +7,43 @@
#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(
@ -64,7 +93,7 @@ AxesSettingsPanel::AxesSettingsPanel(QWidget* parent) : QFrame(parent) {
auto* scaleLbl = new QLabel(QStringLiteral("放大系数"), this);
scaleLbl->setStyleSheet(QStringLiteral("border:none;"));
scaleRow->addWidget(scaleLbl);
scaleSlider_ = new QSlider(Qt::Horizontal, this);
scaleSlider_ = new ClickJumpSlider(Qt::Horizontal, this);
scaleSlider_->setMinimum(1);
scaleSlider_->setMaximum(10);
scaleSlider_->setValue(1);

View File

@ -84,10 +84,11 @@ public:
// 可勾选项:左侧画复选框(用当前 style 的指示器),文本整体右移。容器节点(项目/GS/TM)不画
// 复选框、名称紧跟展开图标(左留白小,与对象树容器一致;勿为对齐子级而预留空复选框列,
// 否则容器名与展开图标间出现大段空白,见用户 #2 反馈)。
const int box = 16;
int textLeftPad = 6;
const bool checkable = (idx.flags() & Qt::ItemIsUserCheckable);
const bool isContainer = idx.data(kDsDdCodeRole).toString() == QStringLiteral("container");
if (checkable) {
const int box = 16;
QRect checkRect(r.left() + 12, r.top() + (r.height() - box) / 2, box, box);
const auto cs = static_cast<Qt::CheckState>(idx.data(Qt::CheckStateRole).toInt());
QStyleOptionViewItem o(opt);
@ -98,6 +99,11 @@ public:
QStyle* st = w ? w->style() : QApplication::style();
st->drawPrimitive(QStyle::PE_IndicatorItemViewItemCheck, &o, p, w);
textLeftPad = 12 + box + 8; // 复选框右侧留白后再放文本
} else if (isContainer) {
// 容器文本左缘对齐子级复选框的左缘(r.left()+12)——使「容器→带框子级」的视觉缩进 = 一个树级
// (14px),与「带框父→带框子」一致,消除带框子级相对容器缩进过大(用户 #6)。只 +12(非整列宽)
// 故名称仍紧邻展开图标、无 #2 的大留白。
textLeftPad = 12;
}
QString title = disp, meta;

View File

@ -86,8 +86,8 @@ public:
const int count = idx.data(kRoleChildCount).toInt();
if (count > 0) {
QFont f = opt.font;
f.setPointSizeF(-1);
f.setPixelSize(scaledPx(type::kCaption));
f.setPixelSize(scaledPx(type::kCaption)); // setPixelSize 直接生效,无需先 setPointSizeF(-1)
// -1 会触发 QFont::setPointSizeF 警告刷屏日志)
f.setWeight(QFont::Normal);
painter->setFont(f);
painter->setPen(tokenColor("text/tertiary"));

View File

@ -169,6 +169,13 @@ bool CategorySection::passesFilters(const DsRow& row) const {
}
void CategorySection::rebuildList() {
// 增量保留:记住当前已勾选的 ds重建后复原仍存在的项保持勾选。否则每次刷新(勾选对象/建体/
// 存切片/建异常都会触发)清空全部勾选 → 渲染被重置,体验极差(用户反馈:必须增量更新)。
std::set<std::string> wasChecked;
for (QTreeWidgetItemIterator it(list_); *it; ++it)
if ((*it)->checkState(0) == Qt::Checked)
wasChecked.insert((*it)->data(0, kDsIdRole).toString().toStdString());
std::vector<DsRow> filtered;
filtered.reserve(rows_.size());
for (const auto& r : rows_)
@ -219,7 +226,8 @@ void CategorySection::rebuildList() {
continue;
}
(*it)->setFlags((*it)->flags() | Qt::ItemIsUserCheckable);
(*it)->setCheckState(0, Qt::Unchecked);
const std::string id = (*it)->data(0, kDsIdRole).toString().toStdString();
(*it)->setCheckState(0, wasChecked.count(id) ? Qt::Checked : Qt::Unchecked); // 复原勾选
}
}
list_->expandAll(); // 展开容器层级(项目根/GS/TM让体/切片/异常可见
@ -230,7 +238,7 @@ void CategorySection::rebuildList() {
if (!(*hit)->isHidden())
contentH += (*hit)->text(0).contains(QLatin1Char('\n')) ? 52 : 30;
list_->setMinimumHeight(contentH);
emitChecked(); // 重建后必为空选,清掉上次渲染勾选
emitChecked(); // 上抛复原后的勾选集(保持渲染,不再清空 → 控制器据 diff 增量保留已渲染图元)
}
QStringList CategorySection::checkedDsIds() const {

View File

@ -24,6 +24,7 @@ ColumnDrawer::ColumnDrawer(QWidget* parent, geopro::data::DatasetFieldDictionary
body_ = tabs;
tabs->addTab(analysisTab_, QStringLiteral("三维分析"));
tabs->addTab(col2D_, QStringLiteral("二维分析"));
tabs->tabBar()->setUsesScrollButtons(false); // 永不出左右滚动箭头(两 tab 必能平铺)
// 折叠按钮:固定宽 18px垂直拉伸。
// 用 SVG 图标(makeGlyph)而非 ◀/▶ 文字——三角符(U+25C0/25B6)不在 YaHei作按钮文字会触发
@ -61,7 +62,9 @@ void ColumnDrawer::resizeEvent(QResizeEvent* e)
if (auto* tabs = qobject_cast<QTabWidget*>(body_)) {
const int n = tabs->count();
if (n > 0 && tabs->width() > 0) {
const int w = std::max(40, tabs->width() / n - 6); // 减 margin-right(4)余量
// 每 tab 内容宽 = 总宽/n - 每 tab 非内容开销(全局 QSS padding 8+16+16=… 约 32 + margin 4)。
// 稍欠一点宽避免溢出(溢出会触发滚动箭头)setUsesScrollButtons(false) 再兜底。
const int w = std::max(40, tabs->width() / n - 42);
tabs->tabBar()->setStyleSheet(QStringLiteral("QTabBar::tab{width:%1px;}").arg(w));
}
}