Spatial 透视视图
SpatialView 空间视图
让现有控件以透视方式显示,并随鼠标倾斜。
示例

用法
调整场景透视
调整镜头角度和缩放,再把鼠标移到卡片上。需要输入文字或弹出窗口时,请切换到平面模式。
#include <FluentQt/Spatial.h>
using namespace fluent::spatial;
using namespace fluent;
// The two cards keep their own poses while the view changes the whole scene.
class SceneView final : public spatial::SpatialView {
public:
using SpatialView::SpatialView;
void arrange()
{
if (itemCount() != 2)
return;
const float spread = width() < 650 ? 24 : 100;
items().first()->setPosition(QVector3D(-spread, -44, -160));
items().last()->setPosition(QVector3D(spread, 44, 160));
}
protected:
void resizeEvent(QResizeEvent* event) override
{
SpatialView::resizeEvent(event);
arrange();
}
};
auto* panel = new SpatialPreviewPanel(parent);
auto* column = new QVBoxLayout(panel);
column->setContentsMargins(0, 0, 0, 0);
column->setSpacing(16);
auto* session = new spatial::SpatialSession(panel, panel);
const bool active = session->activate();
auto* view = new SceneView(panel);
if (!active || !session->attachView(view)) {
view->setSpatialEnabled(false);
view->setRenderMode(spatial::SpatialView::RenderMode::Raster);
}
view->setObjectName("spatialPreviewView");
view->setFixedHeight(400);
view->setCameraDistance(1200);
view->setZoom(0.9);
view->setMaximumTilt(QPointF(6, 12));
// Response time (140 ms), animation limit (60 fps), cache and Auto renderer use UILib defaults.
// See SpatialView.h for the full parameter contract.
// Both layers use the same size and data, making perspective easy to compare.
const auto makeLayer = [](const QString& title, const QString& caption) {
auto* card = new layout::Card;
card->setFixedSize(220, 150);
auto* content = new QVBoxLayout(card);
content->setContentsMargins(16, 12, 16, 12);
content->setSpacing(6);
auto* heading = new textfields::Label(title, card);
heading->setFluentTypography(Typography::FontRole::Subtitle);
heading->setTextColorRole(textfields::Label::TextColorRole::Primary);
content->addWidget(heading);
auto* chart = new charts::Sparkline(card);
chart->setAccessibleName(title + " sample chart");
auto* model = new charts::ChartModel(card);
model->setPoints({{0, 12}, {1, 24}, {2, 18}, {3, 38}, {4, 32}, {5, 54}});
chart->setModel(model);
content->addWidget(chart, 1);
auto* note = new textfields::Label(caption, card);
note->setTextColorRole(textfields::Label::TextColorRole::Secondary);
content->addWidget(note);
return card;
};
spatial::SpatialItem* back = view->addWidget(
makeLayer("Back layer", "Z -160 · sample data"), WidgetOwnership::Owned);
spatial::SpatialItem* front = view->addWidget(
makeLayer("Front layer", "Z +160 · sample data"), WidgetOwnership::Owned);
back->setRotation(QVector3D(6, 16, 0));
front->setRotation(QVector3D(-6, -16, 0));
back->setSurfaceIntensity(0.75);
front->setSurfaceIntensity(0.75);
view->arrange();
auto* toolbar = new QHBoxLayout;
auto* follow = new basicinput::ToggleSwitch(panel);
follow->setObjectName("spatialViewFollow");
follow->setAccessibleName("Follow pointer");
follow->setOnContent("Follow pointer");
follow->setOffContent("Follow pointer");
follow->setIsOn(true);
toolbar->addWidget(follow);
toolbar->addStretch();
column->addLayout(toolbar);
column->addWidget(view);
// Keep view-wide controls outside the projection so their drag targets stay still.
auto* controls = new QWidget(panel);
controls->setObjectName("spatialViewControls");
auto* grid = new QGridLayout(controls);
grid->setContentsMargins(0, 0, 0, 0);
grid->setHorizontalSpacing(24);
grid->setVerticalSpacing(12);
grid->setColumnStretch(0, 1);
grid->setColumnStretch(1, 1);
const auto addSlider = [controls, grid](const QString& name, const QString& label,
const QString& unit, int minimum, int maximum,
int initial, int row, int col) {
auto* group = new QWidget(controls);
auto* layout = new QVBoxLayout(group);
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(4);
auto* text = new textfields::Label(group);
text->setTextColorRole(textfields::Label::TextColorRole::Primary);
auto* slider = new basicinput::Slider(group);
slider->setObjectName(name);
slider->setAccessibleName(label);
slider->setRange(minimum, maximum);
slider->setValue(initial);
const auto updateText = [text, label, unit](int value) {
text->setText(QString("%1: %2%3").arg(label).arg(value).arg(unit));
};
updateText(initial);
QObject::connect(slider, &basicinput::Slider::valueChanged, text, updateText);
layout->addWidget(text);
layout->addWidget(slider);
grid->addWidget(group, row, col);
return slider;
};
auto* distance = addSlider("spatialViewDistance", "Camera distance", " px",
650, 2400, 1200, 0, 0);
auto* zoom = addSlider("spatialViewZoom", "Scene zoom", "%", 50, 110, 90, 0, 1);
QObject::connect(distance, &basicinput::Slider::valueChanged, view,
[view](int value) { view->setCameraDistance(value); });
QObject::connect(zoom, &basicinput::Slider::valueChanged, view,
[view](int value) { view->setZoom(value / 100.0); });
column->addWidget(controls);
QObject::connect(follow, &basicinput::ToggleSwitch::toggled,
view, &spatial::SpatialView::setPointerTrackingEnabled);
QObject::connect(view, &spatial::SpatialView::pointerTrackingEnabledChanged,
follow, &basicinput::ToggleSwitch::setIsOn);
const auto updateAvailability = [view, controls, follow] {
controls->setEnabled(view->isSpatialEnabled());
follow->setEnabled(view->isSpatialEnabled());
};
QObject::connect(view, &spatial::SpatialView::spatialEnabledChanged, controls, updateAvailability);
updateAvailability();import fluentqt
from PySide6.QtCore import QEvent, QPointF
from PySide6.QtGui import QVector3D
from PySide6.QtWidgets import QGridLayout, QHBoxLayout, QVBoxLayout, QWidget
from fluentqt.spatial import SpatialSession, SpatialView
class SpatialPreviewPanel(QWidget):
def stop_spatial(self):
session = self.findChild(SpatialSession)
if session is not None:
session.deactivate()
def event(self, event):
# Stop before deleteLater dismantles this panel's widgets.
# Also call stop_spatial before disposing an enclosing window.
if event.type() == QEvent.DeferredDelete:
self.stop_spatial()
return super().event(event)
class SceneView(SpatialView):
def arrange(self):
if self.itemCount() == 2:
spread = 24 if self.width() < 650 else 100
self.items()[0].setPosition(QVector3D(-spread, -44, -160))
self.items()[-1].setPosition(QVector3D(spread, 44, 160))
def resizeEvent(self, event):
super().resizeEvent(event)
self.arrange()
panel = SpatialPreviewPanel()
column = QVBoxLayout(panel)
column.setContentsMargins(0, 0, 0, 0)
column.setSpacing(12)
session = SpatialSession(panel, panel)
spatial_attached = session.activate()
view = SceneView(panel)
spatial_attached = spatial_attached and session.attachView(view)
if not spatial_attached:
view.setRenderMode(SpatialView.RenderMode.Raster)
view.setSpatialEnabled(False)
view.setObjectName("spatialPreviewView")
view.setFixedHeight(400)
view.setCameraDistance(1200)
view.setMaximumTilt(QPointF(4, 6))
column.addWidget(view)
def add_slider(parent, layout, name, label, unit, minimum, maximum, initial):
in_grid = isinstance(layout, QGridLayout)
group = QWidget(parent) if in_grid else parent
column = QVBoxLayout(group) if in_grid else QVBoxLayout()
column.setContentsMargins(0, 0, 0, 0)
column.setSpacing(4)
text = fluentqt.Label(f"{label}: {initial}{unit}", group)
slider = fluentqt.Slider(group)
slider.setObjectName(name)
slider.setAccessibleName(label)
slider.setRange(minimum, maximum)
slider.setValue(initial)
slider.valueChanged.connect(
lambda value: text.setText(f"{label}: {value}{unit}")
)
column.addWidget(text)
column.addWidget(slider)
if isinstance(layout, QGridLayout):
layout.addWidget(group, 0, layout.count())
else:
layout.addLayout(column)
return slider
column.setSpacing(16)
view.setZoom(0.9)
view.setMaximumTilt(QPointF(6, 12))
def make_layer(title, caption):
card = fluentqt.Card()
card.setFixedSize(220, 150)
content = QVBoxLayout(card)
content.setContentsMargins(16, 12, 16, 12)
content.setSpacing(6)
heading = fluentqt.Label(title, card)
heading.setFluentTypography(fluentqt.FontRole.Subtitle)
content.addWidget(heading)
chart = fluentqt.Sparkline(card)
chart.setAccessibleName(title + " sample chart")
model = fluentqt.ChartModel(card)
model.setPoints([QPointF(x, y) for x, y in enumerate([12, 24, 18, 38, 32, 54])])
chart.setModel(model)
content.addWidget(chart, 1)
note = fluentqt.Label(caption, card)
note.setTextColorRole(fluentqt.Label.TextColorRole.Secondary)
content.addWidget(note)
card._model = model
return card
back = view.addOwnedWidget(make_layer("Back layer", "Z -160 · sample data"))
front = view.addOwnedWidget(make_layer("Front layer", "Z +160 · sample data"))
back.setRotation(QVector3D(6, 16, 0))
front.setRotation(QVector3D(-6, -16, 0))
back.setSurfaceIntensity(0.75)
front.setSurfaceIntensity(0.75)
view.arrange()
toolbar = QHBoxLayout()
follow = fluentqt.ToggleSwitch(panel)
follow.setObjectName("spatialViewFollow")
follow.setAccessibleName("Follow pointer")
follow.setOnContent("Follow pointer")
follow.setOffContent("Follow pointer")
follow.setIsOn(True)
toolbar.addWidget(follow)
toolbar.addStretch()
column.removeWidget(view)
column.addLayout(toolbar)
column.addWidget(view)
controls = QWidget(panel)
controls.setObjectName("spatialViewControls")
sliders = QGridLayout(controls)
sliders.setContentsMargins(0, 0, 0, 0)
sliders.setHorizontalSpacing(24)
sliders.setVerticalSpacing(12)
sliders.setColumnStretch(0, 1)
sliders.setColumnStretch(1, 1)
distance = add_slider(
controls,
sliders,
"spatialViewDistance",
"Camera distance",
" px",
650,
2400,
1200
)
zoom = add_slider(
controls,
sliders,
"spatialViewZoom",
"Scene zoom",
"%",
50,
110,
90
)
distance.valueChanged.connect(view.setCameraDistance)
zoom.valueChanged.connect(lambda value: view.setZoom(value / 100.0))
column.addWidget(controls)
follow.toggled.connect(view.setPointerTrackingEnabled)
view.pointerTrackingEnabledChanged.connect(follow.setIsOn)
def update_availability():
controls.setEnabled(view.isSpatialEnabled())
follow.setEnabled(view.isSpatialEnabled())
view.spatialEnabledChanged.connect(update_availability)
update_availability()API
查看声明没有匹配的 API 成员。
fluent::spatial::SpatialView
继承自 QWidget · FluentElement · QMLPlus
使用 OpenGL 承载少量具有透视变换的真实控件。
构造函数
属性
bool spatialEnabled是否启用透视;动效和主题策略允许时默认 true。 关闭后将同一批控件放回原生纵向布局,保留原有状态。
读取isSpatialEnabled()设置setSpatialEnabled()通知spatialEnabledChanged()qreal cameraDistance透视距离,逻辑像素范围 [100, 10000],默认 1000;越小,近大远小越明显。
读取cameraDistance()设置setCameraDistance()通知cameraDistanceChanged()qreal zoom整个场景的显示倍率 [0.1, 4],默认 1;不改变条目的位置和姿态。
读取zoom()设置setZoom()通知zoomChanged()bool pointerTrackingEnabled是否让整个场景随鼠标倾斜,默认 true,仅在 3D 中生效。
读取isPointerTrackingEnabled()设置setPointerTrackingEnabled()通知pointerTrackingEnabledChanged()QPointF maximumTilt鼠标跟随的俯仰/偏航上限,各为 [0, 45] 度,默认 (10, 16)。
读取maximumTilt()设置setMaximumTilt()通知maximumTiltChanged()int responseTime指针跟随时间常数 [0, 1000] 毫秒,默认 140;越大越柔和,0 表示立即跟随。 此值是指数响应的时间常数,不是固定动画时长。
读取responseTime()设置setResponseTime()通知responseTimeChanged()int maximumFrameRate鼠标动画更新上限 [15, 120] 帧/秒,默认 60,不代表显示器实际帧率。
读取maximumFrameRate()设置setMaximumFrameRate()通知maximumFrameRateChanged()bool cacheEnabled是否缓存控件表面,默认 true;内容发生变化时仍会重绘。
读取isCacheEnabled()设置setCacheEnabled()通知cacheEnabledChanged()RenderMode renderMode请求的后端:Auto(默认)、Raster 或 OpenGL;OpenGL 不可用时回退。 切换等待当前鼠标操作结束;使用 activeBackend 查询实际后端。隐藏或完全裁出时释放 GPU 视口;祖先绘制特效启用时使用 Raster 供外层捕获,保留请求的渲染模式。
读取renderMode()设置setRenderMode()通知renderModeChanged()Backend activeBackend实际渲染后端 Raster 或 OpenGL;只读,在视图显示并完成初始化前为 Raster。
读取activeBackend()通知rendererChanged()QString rendererName当前 OpenGL 设备名称;只读,Raster 或尚未初始化时为空。
读取rendererName()通知rendererChanged()QString fallbackReason自动回退后端的原因;只读,没有回退原因时为空。
读取fallbackReason()通知rendererChanged()int itemCount承载的表面数量,包含隐藏条目;只读,初始为 0。 外部销毁控件时立即移除条目,销毁完成后合并通知,允许回调销毁宿主。
读取itemCount()通知itemCountChanged()
方法
SpatialItem* addWidget(QWidget* widget, WidgetOwnership ownership = WidgetOwnership::Borrowed)承载可支持的控件;拒绝空指针、不支持的控件或已在其他代理中的内容。
QWidget* takeWidget(SpatialItem* item)分离内容并删除条目,将控件所有权转交调用方。
void releaseItem(SpatialItem* item)删除条目并按记录的所有权策略释放控件。
int itemCount() const承载的表面数量,包含隐藏条目;只读,初始为 0。 外部销毁控件时立即移除条目,销毁完成后合并通知,允许回调销毁宿主。
bool isSpatialEnabled() const是否启用透视;动效和主题策略允许时默认 true。 关闭后将同一批控件放回原生纵向布局,保留原有状态。
void setSpatialEnabled(bool enabled)是否启用透视;动效和主题策略允许时默认 true。 关闭后将同一批控件放回原生纵向布局,保留原有状态。
qreal cameraDistance() const透视距离,逻辑像素范围 [100, 10000],默认 1000;越小,近大远小越明显。
void setCameraDistance(qreal distance)透视距离,逻辑像素范围 [100, 10000],默认 1000;越小,近大远小越明显。
qreal zoom() const整个场景的显示倍率 [0.1, 4],默认 1;不改变条目的位置和姿态。
void setZoom(qreal zoom)整个场景的显示倍率 [0.1, 4],默认 1;不改变条目的位置和姿态。
bool isPointerTrackingEnabled() const是否让整个场景随鼠标倾斜,默认 true,仅在 3D 中生效。
void setPointerTrackingEnabled(bool enabled)是否让整个场景随鼠标倾斜,默认 true,仅在 3D 中生效。
QPointF maximumTilt() const鼠标跟随的俯仰/偏航上限,各为 [0, 45] 度,默认 (10, 16)。
void setMaximumTilt(const QPointF& degrees)鼠标跟随的俯仰/偏航上限,各为 [0, 45] 度,默认 (10, 16)。
int responseTime() const指针跟随时间常数 [0, 1000] 毫秒,默认 140;越大越柔和,0 表示立即跟随。 此值是指数响应的时间常数,不是固定动画时长。
void setResponseTime(int milliseconds)指针跟随时间常数 [0, 1000] 毫秒,默认 140;越大越柔和,0 表示立即跟随。 此值是指数响应的时间常数,不是固定动画时长。
int maximumFrameRate() const鼠标动画更新上限 [15, 120] 帧/秒,默认 60,不代表显示器实际帧率。
void setMaximumFrameRate(int framesPerSecond)鼠标动画更新上限 [15, 120] 帧/秒,默认 60,不代表显示器实际帧率。
bool isCacheEnabled() const是否缓存控件表面,默认 true;内容发生变化时仍会重绘。
void setCacheEnabled(bool enabled)是否缓存控件表面,默认 true;内容发生变化时仍会重绘。
RenderMode renderMode() const请求的后端:Auto(默认)、Raster 或 OpenGL;OpenGL 不可用时回退。 切换等待当前鼠标操作结束;使用 activeBackend 查询实际后端。隐藏或完全裁出时释放 GPU 视口;祖先绘制特效启用时使用 Raster 供外层捕获,保留请求的渲染模式。
void setRenderMode(RenderMode mode)请求的后端:Auto(默认)、Raster 或 OpenGL;OpenGL 不可用时回退。 切换等待当前鼠标操作结束;使用 activeBackend 查询实际后端。隐藏或完全裁出时释放 GPU 视口;祖先绘制特效启用时使用 Raster 供外层捕获,保留请求的渲染模式。
Backend activeBackend() const实际渲染后端 Raster 或 OpenGL;只读,在视图显示并完成初始化前为 Raster。
QString rendererName() const当前 OpenGL 设备名称;只读,Raster 或尚未初始化时为空。
QString fallbackReason() const自动回退后端的原因;只读,没有回退原因时为空。
信号
void spatialEnabledChanged(bool enabled)是否启用透视;动效和主题策略允许时默认 true。 关闭后将同一批控件放回原生纵向布局,保留原有状态。
void cameraDistanceChanged(qreal distance)透视距离,逻辑像素范围 [100, 10000],默认 1000;越小,近大远小越明显。
void zoomChanged(qreal zoom)整个场景的显示倍率 [0.1, 4],默认 1;不改变条目的位置和姿态。
void pointerTrackingEnabledChanged(bool enabled)是否让整个场景随鼠标倾斜,默认 true,仅在 3D 中生效。
void maximumTiltChanged(const QPointF& degrees)鼠标跟随的俯仰/偏航上限,各为 [0, 45] 度,默认 (10, 16)。
void responseTimeChanged(int milliseconds)指针跟随时间常数 [0, 1000] 毫秒,默认 140;越大越柔和,0 表示立即跟随。 此值是指数响应的时间常数,不是固定动画时长。
void maximumFrameRateChanged(int framesPerSecond)鼠标动画更新上限 [15, 120] 帧/秒,默认 60,不代表显示器实际帧率。
void cacheEnabledChanged(bool enabled)是否缓存控件表面,默认 true;内容发生变化时仍会重绘。
void renderModeChanged(RenderMode mode)请求的后端:Auto(默认)、Raster 或 OpenGL;OpenGL 不可用时回退。 切换等待当前鼠标操作结束;使用 activeBackend 查询实际后端。隐藏或完全裁出时释放 GPU 视口;祖先绘制特效启用时使用 Raster 供外层捕获,保留请求的渲染模式。
void rendererChanged()实际渲染后端 Raster 或 OpenGL;只读,在视图显示并完成初始化前为 Raster。
void itemCountChanged(int count)承载的表面数量,包含隐藏条目;只读,初始为 0。 外部销毁控件时立即移除条目,销毁完成后合并通知,允许回调销毁宿主。
枚举
enum class RenderModeAutoRasterOpenGL
enum class BackendRasterOpenGL
析构函数
PySide6 扩展
addOwnedWidget(widget)Transfer content to the view; releasing its item destroys it.
addReparentedWidget(widget)Restore content to its original QWidget parent on release.
子类扩展接口
方法
继承自 fluent::FluentElement
方法
static void setTheme(Theme theme)更新全局主题,并通知所有活跃的 FluentElement 实例。
static void setThemeDeferred(Theme theme)以可见元素优先的批次调度主题刷新,避免阻塞当前交互。
static Theme currentTheme()返回当前生效的全局主题。
static bool themeUsesDarkAppearance(Theme theme)返回指定主题是否使用暗色底的 chrome 与材质 token。
static void refreshTheme()在设计 token 变化后重新广播当前主题以触发重绘。
static int themeGeneration()每次主题变化自增的单调计数器,用于过期判断。
Theme effectiveTheme() const解析此元素实际使用的主题,会优先读取 QWidget 父链上的局部主题覆盖。
bool effectiveThemeUsesDarkAppearance() const返回此元素的实际主题是否使用暗色底 chrome。
bool setThemeOverrides(const QJsonObject& overrides)供组件侧访问的设计 token 接口。
QJsonObject themeOverrides() const返回当前元素的覆盖配置。
void clearThemeOverrides()恢复全部 token 跟随全局主题。
const Colors& themeColorsRef() const对当前色板的零拷贝访问(用于绘制热路径)。
Material::AcrylicToken themeAcrylic() const材质、阴影和自适应布局 token。
QColor themeBackdrop(bool active) const给定激活状态下的窗口 chrome 背景色。
virtual void onThemeUpdated()全局主题变化后调用。
枚举
enum Theme解析设计 token 时使用的全局视觉主题。
Light = 0Dark = 1HighContrast = 2
类型与别名
struct Colors返回给组件使用的设计 token 快照。
继承自 fluent::QMLPlus
方法
AnchorLayout::Anchors* anchors()返回 QWidget 宿主的可变锚点配置。
void setState(const QString& name)应用具名 state;名称为空时恢复默认状态。
void addState(const QMLState& state)注册或替换一个具名 state 定义。
void evaluateStates()重新计算条件;按注册顺序选择首个成立的条件。
QMLPlusRuntime* runtime()返回按需创建、由宿主持有的状态观察对象。
void setAnchors(const AnchorLayout::Anchors& anchors)替换宿主的全部锚点,并使父布局失效。
void resetAnchors()清除宿主全部锚点,并使父布局失效。
void bind(const char* targetProp, QObject* source, const char* sourceProp, PropertyBinder::Direction dir = PropertyBinder::OneWay)将 source 属性绑定到 QWidget 宿主上的 target 属性。
BindingHandle bindManaged(const char* targetProp, QObject* source, const char* sourceProp, PropertyBinder::Direction direction = PropertyBinder::OneWay, BindingOptions options = {})在当前 QWidget 宿主上安装可管理的直接绑定。
BindingHandle bindComputed(const char* targetProp, const QVector<BindingDependency>& dependencies, std::function<QVariant()> evaluate, BindingOptions options = {})在当前 QWidget 宿主上安装使用显式依赖的 C++ 表达式绑定。
void setBehavior(const char* propertyName, const PropertyAnimation& animation)设置宿主受管理属性写入时使用的默认动画。
void clearBehavior(const char* propertyName)移除后续受管理写入使用的默认动画。
bool writeProperty(const char* propertyName, const QVariant& value)通过 Behavior 写入宿主属性,且不移除该属性的绑定。
Qt 基类完整成员:QWidget