refactor: SplitPaneLayout now component

This commit is contained in:
ATMDA 2025-11-19 12:39:45 -05:00
parent 50e29f8be3
commit ad4213d45c
6 changed files with 1317 additions and 1436 deletions

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,7 @@
pragma ComponentBehavior: Bound
import ".."
import "../components"
import qs.components
import qs.components.controls
import qs.components.effects
@ -11,52 +12,17 @@ import Quickshell.Widgets
import QtQuick
import QtQuick.Layouts
RowLayout {
Item {
id: root
required property Session session
anchors.fill: parent
spacing: 0
Item {
id: leftAudioItem
Layout.preferredWidth: Math.floor(parent.width * 0.4)
Layout.minimumWidth: 420
Layout.fillHeight: true
ClippingRectangle {
id: leftAudioClippingRect
SplitPaneLayout {
anchors.fill: parent
anchors.margins: Appearance.padding.normal
anchors.leftMargin: 0
anchors.rightMargin: Appearance.padding.normal / 2
radius: leftAudioBorder.innerRadius
color: "transparent"
Loader {
id: leftAudioLoader
anchors.fill: parent
anchors.margins: Appearance.padding.large + Appearance.padding.normal
anchors.leftMargin: Appearance.padding.large
anchors.rightMargin: Appearance.padding.large + Appearance.padding.normal / 2
asynchronous: true
sourceComponent: audioLeftContentComponent
}
}
InnerBorder {
id: leftAudioBorder
leftThickness: 0
rightThickness: Appearance.padding.normal / 2
}
Component {
id: audioLeftContentComponent
leftContent: Component {
StyledFlickable {
id: leftAudioFlickable
@ -248,47 +214,18 @@ RowLayout {
}
}
}
}
rightContent: Component {
Item {
id: rightAudioItem
Layout.fillWidth: true
Layout.fillHeight: true
ClippingRectangle {
id: rightAudioClippingRect
anchors.fill: parent
anchors.margins: Appearance.padding.normal
anchors.leftMargin: 0
anchors.rightMargin: Appearance.padding.normal / 2
radius: rightAudioBorder.innerRadius
color: "transparent"
Loader {
id: rightAudioLoader
anchors.fill: parent
anchors.topMargin: Appearance.padding.large * 2
anchors.bottomMargin: Appearance.padding.large * 2
anchors.leftMargin: 0
anchors.rightMargin: 0
asynchronous: true
sourceComponent: audioRightContentComponent
}
}
InnerBorder {
id: rightAudioBorder
leftThickness: Appearance.padding.normal / 2
}
Component {
id: audioRightContentComponent
StyledFlickable {
id: rightAudioFlickable
anchors.fill: parent
flickableDirection: Flickable.VerticalFlick
contentHeight: contentLayout.height
@ -592,3 +529,4 @@ RowLayout {
}
}
}
}

View file

@ -1,6 +1,7 @@
pragma ComponentBehavior: Bound
import ".."
import "../components"
import qs.components.controls
import qs.components.effects
import qs.components.containers
@ -10,95 +11,50 @@ import Quickshell.Bluetooth
import QtQuick
import QtQuick.Layouts
RowLayout {
Item {
id: root
required property Session session
anchors.fill: parent
spacing: 0
Item {
id: leftBtItem
Layout.preferredWidth: Math.floor(parent.width * 0.4)
Layout.minimumWidth: 420
Layout.fillHeight: true
ClippingRectangle {
id: leftBtClippingRect
SplitPaneLayout {
anchors.fill: parent
anchors.margins: Appearance.padding.normal
anchors.leftMargin: 0
anchors.rightMargin: Appearance.padding.normal / 2
radius: leftBtBorder.innerRadius
color: "transparent"
Loader {
id: leftBtLoader
anchors.fill: parent
anchors.margins: Appearance.padding.large + Appearance.padding.normal
anchors.leftMargin: Appearance.padding.large
anchors.rightMargin: Appearance.padding.large + Appearance.padding.normal / 2
asynchronous: true
sourceComponent: btDeviceListComponent
}
}
InnerBorder {
id: leftBtBorder
leftThickness: 0
rightThickness: Appearance.padding.normal / 2
}
Component {
id: btDeviceListComponent
leftContent: Component {
DeviceList {
anchors.fill: parent
session: root.session
}
}
}
rightContent: Component {
Item {
id: rightBtItem
Layout.fillWidth: true
Layout.fillHeight: true
ClippingRectangle {
id: btClippingRect
anchors.fill: parent
anchors.margins: Appearance.padding.normal
anchors.leftMargin: 0
anchors.rightMargin: Appearance.padding.normal / 2
radius: rightBorder.innerRadius
color: "transparent"
Loader {
id: loader
id: rightBtPane
property BluetoothDevice pane: root.session.bt.active
Loader {
id: rightLoader
anchors.fill: parent
anchors.margins: Appearance.padding.large * 2
asynchronous: true
sourceComponent: pane ? details : settings
sourceComponent: rightBtPane.pane ? details : settings
}
Behavior on pane {
SequentialAnimation {
ParallelAnimation {
Anim {
target: rightLoader
property: "opacity"
to: 0
easing.bezierCurve: Appearance.anim.curves.standardAccel
}
Anim {
target: rightLoader
property: "scale"
to: 0.8
easing.bezierCurve: Appearance.anim.curves.standardAccel
@ -107,11 +63,13 @@ RowLayout {
PropertyAction {}
ParallelAnimation {
Anim {
target: rightLoader
property: "opacity"
to: 1
easing.bezierCurve: Appearance.anim.curves.standardDecel
}
Anim {
target: rightLoader
property: "scale"
to: 1
easing.bezierCurve: Appearance.anim.curves.standardDecel
@ -119,13 +77,15 @@ RowLayout {
}
}
}
Connections {
target: root.session.bt
function onActiveChanged() {
rightBtPane.pane = root.session.bt.active;
}
}
}
}
InnerBorder {
id: rightBorder
leftThickness: Appearance.padding.normal / 2
}
Component {
@ -158,10 +118,8 @@ RowLayout {
session: root.session
}
}
}
component Anim: NumberAnimation {
target: loader
duration: Appearance.anim.durations.normal / 2
easing.type: Easing.BezierSpline
}

View file

@ -0,0 +1,120 @@
pragma ComponentBehavior: Bound
import qs.components
import qs.components.effects
import qs.config
import Quickshell.Widgets
import QtQuick
import QtQuick.Layouts
RowLayout {
id: root
spacing: 0
property Component leftContent: null
property Component rightContent: null
// Left pane configuration
property real leftWidthRatio: 0.4
property int leftMinimumWidth: 420
property var leftLoaderProperties: ({})
// Right pane configuration
property var rightLoaderProperties: ({})
// Expose loaders for customization (access via splitLayout.leftLoader or splitLayout.rightLoader)
property alias leftLoader: leftLoader
property alias rightLoader: rightLoader
// Left pane
Item {
id: leftPane
Layout.preferredWidth: Math.floor(parent.width * root.leftWidthRatio)
Layout.minimumWidth: root.leftMinimumWidth
Layout.fillHeight: true
ClippingRectangle {
id: leftClippingRect
anchors.fill: parent
anchors.margins: Appearance.padding.normal
anchors.leftMargin: 0
anchors.rightMargin: Appearance.padding.normal / 2
radius: leftBorder.innerRadius
color: "transparent"
Loader {
id: leftLoader
anchors.fill: parent
anchors.margins: Appearance.padding.large + Appearance.padding.normal
anchors.leftMargin: Appearance.padding.large
anchors.rightMargin: Appearance.padding.large + Appearance.padding.normal / 2
asynchronous: true
sourceComponent: root.leftContent
// Apply any additional properties from leftLoaderProperties
Component.onCompleted: {
for (const key in root.leftLoaderProperties) {
leftLoader[key] = root.leftLoaderProperties[key];
}
}
}
}
InnerBorder {
id: leftBorder
leftThickness: 0
rightThickness: Appearance.padding.normal / 2
}
}
// Right pane
Item {
id: rightPane
Layout.fillWidth: true
Layout.fillHeight: true
ClippingRectangle {
id: rightClippingRect
anchors.fill: parent
anchors.margins: Appearance.padding.normal
anchors.leftMargin: 0
anchors.rightMargin: Appearance.padding.normal / 2
radius: rightBorder.innerRadius
color: "transparent"
Loader {
id: rightLoader
anchors.fill: parent
anchors.margins: Appearance.padding.large * 2
asynchronous: true
sourceComponent: root.rightContent
// Apply any additional properties from rightLoaderProperties
Component.onCompleted: {
for (const key in root.rightLoaderProperties) {
rightLoader[key] = root.rightLoaderProperties[key];
}
}
}
}
InnerBorder {
id: rightBorder
leftThickness: Appearance.padding.normal / 2
}
}
}

View file

@ -1,6 +1,7 @@
pragma ComponentBehavior: Bound
import ".."
import "../components"
import "../../launcher/services"
import qs.components
import qs.components.controls
@ -16,7 +17,7 @@ import QtQuick
import QtQuick.Layouts
import "../../../utils/scripts/fuzzysort.js" as Fuzzy
RowLayout {
Item {
id: root
required property Session session
@ -26,8 +27,6 @@ RowLayout {
anchors.fill: parent
spacing: 0
onSelectedAppChanged: {
root.session.launcher.active = root.selectedApp;
updateToggleState();
@ -156,43 +155,10 @@ RowLayout {
}
}
Item {
id: leftLauncherItem
Layout.preferredWidth: Math.floor(parent.width * 0.4)
Layout.minimumWidth: 420
Layout.fillHeight: true
ClippingRectangle {
id: leftLauncherClippingRect
SplitPaneLayout {
anchors.fill: parent
anchors.margins: Appearance.padding.normal
anchors.leftMargin: 0
anchors.rightMargin: Appearance.padding.normal / 2
radius: leftLauncherBorder.innerRadius
color: "transparent"
Loader {
id: leftLauncherLoader
anchors.fill: parent
anchors.margins: Appearance.padding.large + Appearance.padding.normal
anchors.leftMargin: Appearance.padding.large
anchors.rightMargin: Appearance.padding.large + Appearance.padding.normal / 2
asynchronous: true
sourceComponent: leftContentComponent
}
}
InnerBorder {
id: leftLauncherBorder
leftThickness: 0
rightThickness: Appearance.padding.normal / 2
}
Component {
id: leftContentComponent
leftContent: Component {
ColumnLayout {
id: leftLauncherLayout
@ -336,7 +302,8 @@ RowLayout {
// Lazy load: activate when left pane is loaded
// The ListView will load asynchronously, and search will work because filteredApps
// is updated regardless of whether the ListView is loaded
return leftLauncherLoader.item !== null;
// Access loader through parent - this will be set when component loads
return true;
}
sourceComponent: StyledListView {
@ -412,25 +379,10 @@ RowLayout {
}
}
}
}
rightContent: Component {
Item {
id: rightLauncherItem
Layout.fillWidth: true
Layout.fillHeight: true
ClippingRectangle {
id: rightLauncherClippingRect
anchors.fill: parent
anchors.margins: Appearance.padding.normal
anchors.leftMargin: 0
anchors.rightMargin: Appearance.padding.normal / 2
radius: rightLauncherBorder.innerRadius
color: "transparent"
Loader {
id: rightLauncherLoader
id: rightLauncherPane
property var pane: root.session.launcher.active
property string paneId: pane ? (pane.id || pane.entry?.id || "") : ""
@ -442,8 +394,16 @@ RowLayout {
return pane ? appDetails : settings;
}
Component.onCompleted: {
displayedApp = pane;
targetComponent = getComponentForPane();
nextComponent = targetComponent;
}
Loader {
id: rightLauncherLoader
anchors.fill: parent
anchors.margins: Appearance.padding.large * 2
opacity: 1
scale: 1
@ -451,19 +411,17 @@ RowLayout {
clip: false
asynchronous: true
sourceComponent: rightLauncherLoader.targetComponent
sourceComponent: rightLauncherPane.targetComponent
active: true
Component.onCompleted: {
displayedApp = pane;
targetComponent = getComponentForPane();
nextComponent = targetComponent;
}
// Expose displayedApp to loaded components
property var displayedApp: rightLauncherPane.displayedApp
onItemChanged: {
// Ensure displayedApp is set when item is created (for async loading)
if (item && pane && displayedApp !== pane) {
displayedApp = pane;
if (item && rightLauncherPane.pane && rightLauncherPane.displayedApp !== rightLauncherPane.pane) {
rightLauncherPane.displayedApp = rightLauncherPane.pane;
}
}
}
@ -484,9 +442,9 @@ RowLayout {
}
}
PropertyAction {
target: rightLauncherLoader
target: rightLauncherPane
property: "displayedApp"
value: rightLauncherLoader.pane
value: rightLauncherPane.pane
}
PropertyAction {
target: rightLauncherLoader
@ -494,9 +452,9 @@ RowLayout {
value: false
}
PropertyAction {
target: rightLauncherLoader
target: rightLauncherPane
property: "targetComponent"
value: rightLauncherLoader.nextComponent
value: rightLauncherPane.nextComponent
}
PropertyAction {
target: rightLauncherLoader
@ -539,11 +497,6 @@ RowLayout {
}
}
}
InnerBorder {
id: rightLauncherBorder
leftThickness: Appearance.padding.normal / 2
}
Component {
@ -573,8 +526,12 @@ RowLayout {
id: appDetails
ColumnLayout {
id: appDetailsLayout
anchors.fill: parent
// Get displayedApp from parent Loader (the Loader has displayedApp property we set)
readonly property var displayedApp: parent && parent.displayedApp !== undefined ? parent.displayedApp : null
spacing: Appearance.spacing.normal
Item {
@ -587,7 +544,7 @@ RowLayout {
Loader {
id: iconLoader
sourceComponent: rightLauncherLoader.displayedApp ? appIconComponent : defaultIconComponent
sourceComponent: parent.parent.displayedApp ? appIconComponent : defaultIconComponent
}
Component {
@ -595,8 +552,9 @@ RowLayout {
IconImage {
implicitSize: Appearance.font.size.extraLarge * 3 * 2
source: {
if (!rightLauncherLoader.displayedApp) return "image-missing";
const entry = rightLauncherLoader.displayedApp.entry;
const app = iconLoader.parent.parent.displayedApp;
if (!app) return "image-missing";
const entry = app.entry;
if (entry && entry.icon) {
return Quickshell.iconPath(entry.icon, "image-missing");
}
@ -619,7 +577,7 @@ RowLayout {
Layout.alignment: Qt.AlignHCenter
Layout.leftMargin: Appearance.padding.large * 2
Layout.rightMargin: Appearance.padding.large * 2
text: rightLauncherLoader.displayedApp ? (rightLauncherLoader.displayedApp.name || rightLauncherLoader.displayedApp.entry?.name || qsTr("Application Details")) : qsTr("Launcher Applications")
text: displayedApp ? (displayedApp.name || displayedApp.entry?.name || qsTr("Application Details")) : qsTr("Launcher Applications")
font.pointSize: Appearance.font.size.large
font.bold: true
}
@ -650,14 +608,15 @@ RowLayout {
SwitchRow {
Layout.topMargin: Appearance.spacing.normal
visible: rightLauncherLoader.displayedApp !== null
visible: appDetailsLayout.displayedApp !== null
label: qsTr("Hide from launcher")
checked: root.hideFromLauncherChecked
enabled: rightLauncherLoader.displayedApp !== null
enabled: appDetailsLayout.displayedApp !== null
onToggled: checked => {
root.hideFromLauncherChecked = checked;
if (rightLauncherLoader.displayedApp) {
const appId = rightLauncherLoader.displayedApp.id || rightLauncherLoader.displayedApp.entry?.id;
const app = appDetailsLayout.displayedApp;
if (app) {
const appId = app.id || app.entry?.id;
const hiddenApps = Config.launcher.hiddenApps ? [...Config.launcher.hiddenApps] : [];
if (checked) {
if (!hiddenApps.includes(appId)) {
@ -680,7 +639,6 @@ RowLayout {
}
}
}
}
component Anim: NumberAnimation {
target: rightLauncherLoader

View file

@ -1,6 +1,7 @@
pragma ComponentBehavior: Bound
import ".."
import "../components"
import "."
import qs.components
import qs.components.controls
@ -21,49 +22,12 @@ Item {
anchors.fill: parent
RowLayout {
id: contentLayout
SplitPaneLayout {
id: splitLayout
anchors.fill: parent
spacing: 0
Item {
id: leftNetworkItem
Layout.preferredWidth: Math.floor(parent.width * 0.4)
Layout.minimumWidth: 420
Layout.fillHeight: true
ClippingRectangle {
id: leftNetworkClippingRect
anchors.fill: parent
anchors.margins: Appearance.padding.normal
anchors.leftMargin: 0
anchors.rightMargin: Appearance.padding.normal / 2
radius: leftNetworkBorder.innerRadius
color: "transparent"
Loader {
id: leftNetworkLoader
anchors.fill: parent
anchors.margins: Appearance.padding.large + Appearance.padding.normal
anchors.leftMargin: Appearance.padding.large
anchors.rightMargin: Appearance.padding.large + Appearance.padding.normal / 2
asynchronous: true
sourceComponent: networkListComponent
}
}
InnerBorder {
id: leftNetworkBorder
leftThickness: 0
rightThickness: Appearance.padding.normal / 2
}
Component {
id: networkListComponent
leftContent: Component {
StyledFlickable {
id: leftFlickable
@ -474,27 +438,12 @@ Item {
}
}
}
}
rightContent: Component {
Item {
id: rightNetworkItem
Layout.fillWidth: true
Layout.fillHeight: true
ClippingRectangle {
id: networkClippingRect
anchors.fill: parent
anchors.margins: Appearance.padding.normal
anchors.leftMargin: 0
anchors.rightMargin: Appearance.padding.normal / 2
radius: rightBorder.innerRadius
color: "transparent"
id: rightPaneItem
// Right pane - networking details/settings
Loader {
id: loader
property var ethernetPane: root.session.ethernet.active
property var wirelessPane: root.session.network.active
property var pane: ethernetPane || wirelessPane
@ -506,6 +455,30 @@ Item {
return pane ? (ethernetPane ? ethernetDetails : wirelessDetails) : settings;
}
Component.onCompleted: {
targetComponent = getComponentForPane();
nextComponent = targetComponent;
}
Connections {
target: root.session.ethernet
function onActiveChanged() {
nextComponent = getComponentForPane();
paneId = ethernetPane ? (ethernetPane.interface || "") : (wirelessPane ? (wirelessPane.ssid || wirelessPane.bssid || "") : "");
}
}
Connections {
target: root.session.network
function onActiveChanged() {
nextComponent = getComponentForPane();
paneId = ethernetPane ? (ethernetPane.interface || "") : (wirelessPane ? (wirelessPane.ssid || wirelessPane.bssid || "") : "");
}
}
Loader {
id: rightLoader
anchors.fill: parent
anchors.margins: Appearance.padding.large * 2
@ -515,43 +488,46 @@ Item {
clip: false
asynchronous: true
sourceComponent: loader.targetComponent
sourceComponent: rightPaneItem.targetComponent
Component.onCompleted: {
targetComponent = getComponentForPane();
nextComponent = targetComponent;
Connections {
target: rightPaneItem
function onPaneIdChanged() {
rightPaneItem.targetComponent = rightPaneItem.nextComponent;
}
}
}
Behavior on paneId {
SequentialAnimation {
ParallelAnimation {
Anim {
target: loader
target: rightLoader
property: "opacity"
to: 0
easing.bezierCurve: Appearance.anim.curves.standardAccel
}
Anim {
target: loader
target: rightLoader
property: "scale"
to: 0.8
easing.bezierCurve: Appearance.anim.curves.standardAccel
}
}
PropertyAction {
target: loader
target: rightPaneItem
property: "targetComponent"
value: loader.nextComponent
value: rightPaneItem.nextComponent
}
ParallelAnimation {
Anim {
target: loader
target: rightLoader
property: "opacity"
to: 1
easing.bezierCurve: Appearance.anim.curves.standardDecel
}
Anim {
target: loader
target: rightLoader
property: "scale"
to: 1
easing.bezierCurve: Appearance.anim.curves.standardDecel
@ -560,17 +536,14 @@ Item {
}
}
onPaneChanged: {
nextComponent = getComponentForPane();
paneId = ethernetPane ? (ethernetPane.interface || "") : (wirelessPane ? (wirelessPane.ssid || wirelessPane.bssid || "") : "");
Connections {
target: rightPaneItem
function onPaneIdChanged() {
rightPaneItem.targetComponent = rightPaneItem.nextComponent;
}
}
}
}
InnerBorder {
id: rightBorder
leftThickness: Appearance.padding.normal / 2
}
Component {
@ -641,8 +614,6 @@ Item {
}
}
}
}
}
WirelessPasswordDialog {
anchors.fill: parent
@ -651,7 +622,6 @@ Item {
}
component Anim: NumberAnimation {
target: loader
duration: Appearance.anim.durations.normal / 2
easing.type: Easing.BezierSpline
}