11pragma ComponentBehavior : Bound
22
33import QtQuick
4+ import QtQuick.Controls
45import QtQuick.Layouts
56import qs.components
67import qs.services
@@ -16,6 +17,11 @@ Item {
1617 readonly property var tabs: activeConfig ? activeConfig .tabs : []
1718
1819 property int activeTabIndex: 0
20+ property string _prevCategory: " "
21+ property var _prevConfig: null
22+ property var _prevTabs: []
23+ property bool _categoryTransitioning: false
24+ property real _slideOffset: 0
1925
2026 function updateTabIndicator () {
2127 const item = tabRepeater .itemAt (activeTabIndex);
@@ -42,11 +48,39 @@ Item {
4248 }
4349
4450 onActiveConfigChanged: {
45- activeTabIndex = 0 ;
51+ if (session .activeCategory === " " ) {
52+ _prevCategory = " " ;
53+ _prevConfig = null ;
54+ _prevTabs = [];
55+ activeTabIndex = 0 ;
56+ tabSwipeView .currentIndex = 0 ;
57+ contentContainer .opacity = 0 ;
58+ _slideOffset = 0 ;
59+ _categoryTransitioning = false ;
60+ } else if (_prevCategory === " " ) {
61+ _prevCategory = session .activeCategory ;
62+ _prevConfig = activeConfig;
63+ _prevTabs = tabs;
64+ activeTabIndex = 0 ;
65+ tabSwipeView .currentIndex = 0 ;
66+ contentFadeOut .stop ();
67+ contentContainer .opacity = 0 ;
68+ _slideOffset = contentContainer .height * 0.15 ;
69+ contentFadeIn .restart ();
70+ _categoryTransitioning = false ;
71+ } else {
72+ activeTabIndex = 0 ;
73+ tabSwipeView .currentIndex = 0 ;
74+ _categoryTransitioning = true ;
75+ contentFadeOut .start ();
76+ }
4677 tabIndicatorUpdate .restart ();
4778 }
4879
4980 onActiveTabIndexChanged: {
81+ if (! _categoryTransitioning && _prevCategory === session .activeCategory && _prevCategory !== " " ) {
82+ tabSwipeView .currentIndex = activeTabIndex;
83+ }
5084 tabIndicatorUpdate .restart ();
5185 }
5286
@@ -65,13 +99,77 @@ Item {
6599 target: root .session
66100 }
67101
102+ ParallelAnimation {
103+ id: contentFadeOut
104+
105+ onFinished: {
106+ root ._prevCategory = root .session .activeCategory ;
107+ root ._prevConfig = root .activeConfig ;
108+ root ._prevTabs = root .tabs ;
109+ tabSwipeView .currentIndex = 0 ;
110+ root ._slideOffset = contentContainer .height * 0.15 ;
111+ contentFadeIn .start ();
112+ }
113+
114+ NumberAnimation {
115+ target: contentContainer
116+ property: " opacity"
117+ from: 1
118+ to: 0
119+ duration: 150
120+ easing .type : Easing .InOutQuad
121+ }
122+
123+ NumberAnimation {
124+ target: root
125+ property: " _slideOffset"
126+ from: 0
127+ to: - contentContainer .height * 0.15
128+ duration: 150
129+ easing .type : Easing .InOutQuad
130+ }
131+ }
132+
133+ ParallelAnimation {
134+ id: contentFadeIn
135+
136+ onFinished: {
137+ root ._categoryTransitioning = false ;
138+ }
139+
140+ NumberAnimation {
141+ target: contentContainer
142+ property: " opacity"
143+ from: 0
144+ to: 1
145+ duration: 250
146+ easing .type : Easing .InOutQuad
147+ }
148+
149+ NumberAnimation {
150+ target: root
151+ property: " _slideOffset"
152+ from: contentContainer .height * 0.15
153+ to: 0
154+ duration: 250
155+ easing .type : Easing .InOutQuad
156+ }
157+ }
158+
68159 ColumnLayout {
160+ id: contentContainer
161+
69162 anchors .fill : parent
70163 anchors .rightMargin : Appearance .padding .large * 2
71164 anchors .leftMargin : Appearance .padding .large * 2
72165 anchors .topMargin : Appearance .padding .large
73166 anchors .bottomMargin : Appearance .padding .large
74167 spacing: Appearance .spacing .normal
168+ opacity: 1
169+
170+ transform: Translate {
171+ y: root ._slideOffset // qmllint disable Quick.layout-positioning
172+ }
75173
76174 // Header: title + description
77175 ColumnLayout {
@@ -81,17 +179,17 @@ Item {
81179 spacing: Appearance .spacing .small / 2
82180
83181 StyledText {
84- Layout . fillWidth : true
85- text : root . activeConfig ? root . activeConfig . title : " "
86- font .pointSize : Appearance . font . size . extraLarge
87- font . weight : Font . Medium
182+ text : root . _prevConfig ? . label ?? " "
183+ font . pointSize : Appearance . font . size . larger + 4
184+ font .weight : Font . DemiBold
185+ color : Colours . palette . m3onSurface
88186 }
89187
90188 StyledText {
91- Layout .fillWidth : true
92- text: root .activeConfig ? root .activeConfig .description : " "
189+ text: root ._prevConfig ? .description ?? " "
93190 font .pointSize : Appearance .font .size .normal
94- color: Qt .alpha (Colours .palette .m3onSurface , 0.6 )
191+ color: Qt .alpha (Colours .palette .m3onSurface , 0.7 )
192+ visible: root ._prevConfig && root ._prevConfig .description
95193 }
96194 }
97195
@@ -101,7 +199,7 @@ Item {
101199 Layout .fillWidth : true
102200 Layout .topMargin : Appearance .spacing .smaller
103201 Layout .preferredHeight : 48
104- visible: root .tabs .length > 0
202+ visible: root ._prevTabs .length > 0
105203
106204 // Track line
107205 Rectangle {
@@ -123,7 +221,7 @@ Item {
123221 Repeater {
124222 id: tabRepeater
125223
126- model: root .tabs
224+ model: root ._prevTabs
127225
128226 delegate: Rectangle {
129227 id: tabItem
@@ -178,7 +276,7 @@ Item {
178276 height: 3
179277 radius: 1.5
180278 color: Colours .palette .m3primary
181- visible: root .tabs .length > 0
279+ visible: root ._prevTabs .length > 0
182280
183281 x: targetX
184282 width: targetWidth
@@ -201,46 +299,60 @@ Item {
201299 }
202300
203301 // Panel content
204- Item {
302+ SwipeView {
303+ id: tabSwipeView
304+
205305 Layout .fillWidth : true
206306 Layout .fillHeight : true
207307 Layout .topMargin : Appearance .spacing .normal
208308
209- Loader {
210- id: panelLoader
309+ interactive: false
310+ currentIndex: root .activeTabIndex
311+ clip: true
211312
212- readonly property string targetSource : root . activeConfig ? " panels/ " + root . activeConfig . id . charAt ( 0 ). toUpperCase () + root . activeConfig . id . slice ( 1 ) + " Panel.qml " : " "
213- property string resolvedSource : targetSource
313+ Repeater {
314+ model : root . _prevTabs . length > 0 ? root . _prevTabs . length : 1
214315
215- anchors .fill : parent
216- asynchronous: true
217- source: resolvedSource
316+ delegate: Item {
317+ required property int index
218318
219- onTargetSourceChanged: resolvedSource = targetSource
319+ Loader {
320+ id: tabPanelLoader
220321
221- onStatusChanged: {
222- if (status === Loader .Error && resolvedSource !== " panels/PlaceholderPanel.qml" ) {
223- Qt .callLater (() => {
224- resolvedSource = " panels/PlaceholderPanel.qml" ;
225- });
226- }
227- }
322+ readonly property string targetSource: {
323+ if (! root ._prevConfig )
324+ return " " ;
325+ if (root ._prevTabs .length === 0 ) {
326+ // e.g panels/AppearancePanel.qml, uses lowercase def from registry and matches to Pascal cased file
327+ return " panels/" + root ._prevConfig .id .charAt (0 ).toUpperCase () + root ._prevConfig .id .slice (1 ) + " Panel.qml" ;
328+ }
329+ return " panels/" + root ._prevConfig .id .charAt (0 ).toUpperCase () + root ._prevConfig .id .slice (1 ) + " Panel.qml" ;
330+ }
331+ property string resolvedSource: targetSource
228332
229- onLoaded: {
230- if (item && item .hasOwnProperty (" activeTabIndex" )) {
231- item .activeTabIndex = root .activeTabIndex ;
232- }
233- }
234- }
333+ anchors .centerIn : parent
334+ width: parent .width
335+ height: parent .height
336+ asynchronous: true
337+ source: resolvedSource
338+
339+ onTargetSourceChanged: resolvedSource = targetSource
235340
236- Connections {
237- function onActiveTabIndexChanged () {
238- if (panelLoader .item && panelLoader .item .hasOwnProperty (" activeTabIndex" )) {
239- panelLoader .item .activeTabIndex = root .activeTabIndex ;
341+ onStatusChanged: {
342+ if (status === Loader .Error && resolvedSource !== " panels/PlaceholderPanel.qml" ) {
343+ Qt .callLater (() => {
344+ resolvedSource = " panels/PlaceholderPanel.qml" ;
345+ });
346+ }
347+ }
348+
349+ onLoaded: {
350+ if (item && item .hasOwnProperty (" activeTabIndex" )) {
351+ item .activeTabIndex = parent .index ;
352+ }
353+ }
240354 }
241355 }
242-
243- target: root
244356 }
245357 }
246358 }
0 commit comments