Spatial
SpatialItem
Position, rotate and scale a widget inside SpatialView.
Example

Usage
Card finish and pose
Adjust one card while the controls keep their own position.
#include <FluentQt/Spatial.h>
using namespace fluent::spatial;
using namespace fluent;
// Keep the control card independent of the card it adjusts.
class WorkbenchView final : public spatial::SpatialView {
public:
using SpatialView::SpatialView;
void arrange()
{
if (itemCount() != 2)
return;
const bool stacked = width() < 720;
setFixedHeight(!isSpatialEnabled() ? 520 : (stacked ? 560 : 360));
auto* preview = items().first();
preview->setPosition(QVector3D(stacked ? 0 : -164, stacked ? -140 : 0,
preview->position().z()));
items().last()->setPosition(QVector3D(stacked ? 0 : 164, stacked ? 110 : 0, -50));
}
protected:
void resizeEvent(QResizeEvent* event) override
{
SpatialView::resizeEvent(event);
// Update the responsive height after the current layout pass.
QTimer::singleShot(0, this, [this] { arrange(); });
}
};
auto* panel = new SpatialPreviewPanel(parent);
auto* column = new QVBoxLayout(panel);
column->setContentsMargins(0, 0, 0, 0);
column->setSpacing(12);
auto* session = new spatial::SpatialSession(panel, panel);
const bool active = session->activate();
auto* view = new WorkbenchView(panel);
if (!active || !session->attachView(view)) {
view->setSpatialEnabled(false);
view->setRenderMode(spatial::SpatialView::RenderMode::Raster);
}
view->setObjectName("spatialPreviewView");
view->setCameraDistance(1400);
view->setZoom(0.92);
view->setMaximumTilt(QPointF(4, 6));
column->addWidget(view);
auto* card = new layout::Card;
card->setObjectName("spatialDemoCard");
card->setFixedSize(280, 180);
auto* content = new QVBoxLayout(card);
content->setContentsMargins(20, 16, 20, 16);
content->setSpacing(8);
auto* title = new textfields::Label("Activity", card);
title->setFluentTypography(Typography::FontRole::Subtitle);
title->setTextColorRole(textfields::Label::TextColorRole::Primary);
content->addWidget(title);
auto* chart = new charts::Sparkline(card);
chart->setAccessibleName("Activity trend, demo data");
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* caption = new textfields::Label("Last 6 days · demo data", card);
caption->setTextColorRole(textfields::Label::TextColorRole::Secondary);
content->addWidget(caption);
spatial::SpatialItem* item = view->addWidget(card, WidgetOwnership::Owned);
item->setSurfaceIntensity(0.75);
item->setHoverLift(5);
item->setRotation(QVector3D(0, -18, 0));
// Scale, pivot and visibility retain their defaults; see SpatialItem.h.
auto* settings = new layout::Card;
settings->setObjectName("spatialParameterCard");
settings->setFixedSize(280, 260);
auto* controls = new QVBoxLayout(settings);
controls->setContentsMargins(20, 16, 20, 16);
controls->setSpacing(8);
auto* heading = new textfields::Label("Card", settings);
heading->setFluentTypography(Typography::FontRole::Subtitle);
heading->setTextColorRole(textfields::Label::TextColorRole::Primary);
controls->addWidget(heading);
const auto addSlider = [settings, controls](const QString& name, const QString& label,
const QString& unit, int minimum, int maximum,
int initial) {
auto* group = new QVBoxLayout;
group->setSpacing(4);
auto* text = new textfields::Label(settings);
text->setTextColorRole(textfields::Label::TextColorRole::Primary);
auto* slider = new basicinput::Slider(settings);
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);
group->addWidget(text);
group->addWidget(slider);
controls->addLayout(group);
return slider;
};
auto* finish = addSlider("spatialItemSurfaceIntensity", "Surface", "%", 0, 100, 75);
auto* angle = addSlider("spatialItemRotation", "Rotation", "°", -35, 35, -18);
auto* depth = addSlider("spatialItemDepth", "Depth", " px", -120, 120, 0);
QObject::connect(finish, &basicinput::Slider::valueChanged, item,
[item](int value) { item->setSurfaceIntensity(value / 100.0); });
QObject::connect(angle, &basicinput::Slider::valueChanged, item,
[item](int value) { item->setRotation(QVector3D(0, value, 0)); });
QObject::connect(depth, &basicinput::Slider::valueChanged, item, [item](int value) {
auto position = item->position();
position.setZ(value);
item->setPosition(position);
});
auto* controlItem = view->addWidget(settings, WidgetOwnership::Owned);
controlItem->setSurfaceIntensity(0.75);
controlItem->setRotation(QVector3D(0, -6, 0));
// No hover lift for the parameter card: keep its drag targets steady.
QObject::connect(view, &spatial::SpatialView::spatialEnabledChanged,
view, [view] { view->arrange(); });
view->arrange();import fluentqt
from PySide6.QtCore import QEvent, QPointF, QTimer
from PySide6.QtGui import QVector3D
from PySide6.QtWidgets import QGridLayout, 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 WorkbenchView(SpatialView):
def __init__(self, parent=None):
super().__init__(parent)
self.arrange_timer = QTimer(self)
self.arrange_timer.setSingleShot(True)
self.arrange_timer.timeout.connect(self.arrange)
def arrange(self):
if self.itemCount() != 2:
return
stacked = self.width() < 720
self.setFixedHeight(
520 if not self.isSpatialEnabled() else (560 if stacked else 360)
)
item = self.items()[0]
item.setPosition(
QVector3D(
0 if stacked else -164,
-140 if stacked else 0,
item.position().z()
)
)
self.items()[-1].setPosition(
QVector3D(0 if stacked else 164, 110 if stacked else 0, -50)
)
def resizeEvent(self, event):
super().resizeEvent(event)
self.arrange_timer.start(0)
panel = SpatialPreviewPanel()
column = QVBoxLayout(panel)
column.setContentsMargins(0, 0, 0, 0)
column.setSpacing(12)
session = SpatialSession(panel, panel)
spatial_attached = session.activate()
view = WorkbenchView(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(360)
view.setCameraDistance(1400)
view.setMaximumTilt(QPointF(4, 6))
column.addWidget(view)
card = fluentqt.Card()
card.setFixedSize(280, 180)
content = QVBoxLayout(card)
content.setContentsMargins(20, 16, 20, 16)
content.setSpacing(8)
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
view.setZoom(0.92)
card.setObjectName("spatialDemoCard")
title = fluentqt.Label("Activity", card)
title.setFluentTypography(fluentqt.FontRole.Subtitle)
title.setTextColorRole(fluentqt.Label.TextColorRole.Primary)
content.addWidget(title)
chart = fluentqt.Sparkline(card)
chart.setAccessibleName("Activity trend, demo data")
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)
caption = fluentqt.Label("Last 6 days · demo data", card)
caption.setTextColorRole(fluentqt.Label.TextColorRole.Secondary)
content.addWidget(caption)
item = view.addOwnedWidget(card)
item.setSurfaceIntensity(0.75)
item.setHoverLift(5)
item.setRotation(QVector3D(0, -18, 0))
settings = fluentqt.Card()
settings.setObjectName("spatialParameterCard")
settings.setFixedSize(280, 260)
controls = QVBoxLayout(settings)
controls.setContentsMargins(20, 16, 20, 16)
controls.setSpacing(8)
heading = fluentqt.Label("Card", settings)
heading.setFluentTypography(fluentqt.FontRole.Subtitle)
controls.addWidget(heading)
finish = add_slider(
settings,
controls,
"spatialItemSurfaceIntensity",
"Surface",
"%",
0,
100,
75
)
angle = add_slider(
settings,
controls,
"spatialItemRotation",
"Rotation",
"°",
-35,
35,
-18
)
depth = add_slider(
settings,
controls,
"spatialItemDepth",
"Depth",
" px",
-120,
120,
0
)
finish.valueChanged.connect(lambda value: item.setSurfaceIntensity(value / 100.0))
angle.valueChanged.connect(lambda value: item.setRotation(QVector3D(0, value, 0)))
def change_depth(value):
position = item.position()
position.setZ(value)
item.setPosition(position)
depth.valueChanged.connect(change_depth)
control_item = view.addOwnedWidget(settings)
control_item.setSurfaceIntensity(0.75)
control_item.setRotation(QVector3D(0, -6, 0))
view.spatialEnabledChanged.connect(view.arrange)
view.arrange()API
DeclarationNo matching API members.
fluent::spatial::SpatialItem
Inherits QObject
One live widget surface positioned in a SpatialView.
Properties
QVector3D positionPivot position relative to view center in logical pixels; default (0, 0, 0). X points right, Y down and positive Z toward the camera.
Readposition()WritesetPosition()NotifypositionChanged()QVector3D rotationRotation about the pivot in X/Y/Z degrees, applied in order; default (0, 0, 0).
Readrotation()WritesetRotation()NotifyrotationChanged()qreal scaleUniform size multiplier for this surface [0.05, 8]; default 1.
Readscale()WritesetScale()NotifyscaleChanged()QPointF pivotNormalized anchor and rotation origin, each axis [0, 1]; default center (0.5, 0.5).
Readpivot()WritesetPivot()NotifypivotChanged()bool visibleVisibility in both spatial and native layouts; default true. Use this property instead of hiding the hosted root widget directly.
ReadisVisible()WritesetVisible()NotifyvisibleChanged()qreal surfaceIntensityStrength of the rounded surface shadow and rim, clamped to [0, 1]; default 0. Intended for an intact rectangular Card; decoration does not change content or hit areas. Only drawn in spatial mode.
ReadsurfaceIntensity()WritesetSurfaceIntensity()NotifysurfaceIntensityChanged()qreal hoverLiftTemporary upward pointer-hover offset in logical pixels [0, 16]; default 0. Motion freezes during pointer input. Native mode removes it.
ReadhoverLift()WritesetHoverLift()NotifyhoverLiftChanged()
Methods
QVector3D position() constPivot position relative to view center in logical pixels; default (0, 0, 0). X points right, Y down and positive Z toward the camera.
void setPosition(const QVector3D& position)Pivot position relative to view center in logical pixels; default (0, 0, 0). X points right, Y down and positive Z toward the camera.
QVector3D rotation() constRotation about the pivot in X/Y/Z degrees, applied in order; default (0, 0, 0).
void setRotation(const QVector3D& rotation)Rotation about the pivot in X/Y/Z degrees, applied in order; default (0, 0, 0).
qreal scale() constUniform size multiplier for this surface [0.05, 8]; default 1.
void setScale(qreal scale)Uniform size multiplier for this surface [0.05, 8]; default 1.
QPointF pivot() constNormalized anchor and rotation origin, each axis [0, 1]; default center (0.5, 0.5).
void setPivot(const QPointF& pivot)Normalized anchor and rotation origin, each axis [0, 1]; default center (0.5, 0.5).
bool isVisible() constVisibility in both spatial and native layouts; default true. Use this property instead of hiding the hosted root widget directly.
void setVisible(bool visible)Visibility in both spatial and native layouts; default true. Use this property instead of hiding the hosted root widget directly.
qreal surfaceIntensity() constStrength of the rounded surface shadow and rim, clamped to [0, 1]; default 0. Intended for an intact rectangular Card; decoration does not change content or hit areas. Only drawn in spatial mode.
void setSurfaceIntensity(qreal intensity)Strength of the rounded surface shadow and rim, clamped to [0, 1]; default 0. Intended for an intact rectangular Card; decoration does not change content or hit areas. Only drawn in spatial mode.
qreal hoverLift() constTemporary upward pointer-hover offset in logical pixels [0, 16]; default 0. Motion freezes during pointer input. Native mode removes it.
void setHoverLift(qreal pixels)Temporary upward pointer-hover offset in logical pixels [0, 16]; default 0. Motion freezes during pointer input. Native mode removes it.
QPolygonF projectedPolygon() constCurrent projected corners in SpatialView coordinates; empty when not projectable.
Signals
void positionChanged(const QVector3D& position)Pivot position relative to view center in logical pixels; default (0, 0, 0). X points right, Y down and positive Z toward the camera.
void rotationChanged(const QVector3D& rotation)Rotation about the pivot in X/Y/Z degrees, applied in order; default (0, 0, 0).
void scaleChanged(qreal scale)Uniform size multiplier for this surface [0.05, 8]; default 1.
void pivotChanged(const QPointF& pivot)Normalized anchor and rotation origin, each axis [0, 1]; default center (0.5, 0.5).
void visibleChanged(bool visible)Visibility in both spatial and native layouts; default true. Use this property instead of hiding the hosted root widget directly.
void surfaceIntensityChanged(qreal intensity)Strength of the rounded surface shadow and rim, clamped to [0, 1]; default 0. Intended for an intact rectangular Card; decoration does not change content or hit areas. Only drawn in spatial mode.
void hoverLiftChanged(qreal pixels)Temporary upward pointer-hover offset in logical pixels [0, 16]; default 0. Motion freezes during pointer input. Native mode removes it.
Destructors
All inherited Qt members:QObject