summaryrefslogtreecommitdiff
path: root/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Dock/DockAppButton.qml
diff options
context:
space:
mode:
authornippy <you@example.com>2026-04-18 13:49:56 +0200
committernippy <you@example.com>2026-04-18 13:49:56 +0200
commit5d94c0a7d44a2255b81815a52a7056a94a39842d (patch)
tree759bdea9645c6a62f9e1e4c001f7d81cccd120d2 /raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Dock/DockAppButton.qml
parente79cdf210b267f21a186255ce1a4d50029439d54 (diff)
downloadRaveOS-PKGBUILD-5d94c0a7d44a2255b81815a52a7056a94a39842d.tar.gz
RaveOS-PKGBUILD-5d94c0a7d44a2255b81815a52a7056a94a39842d.zip
update Raveos themes
Diffstat (limited to 'raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Dock/DockAppButton.qml')
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Dock/DockAppButton.qml670
1 files changed, 670 insertions, 0 deletions
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Dock/DockAppButton.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Dock/DockAppButton.qml
new file mode 100644
index 0000000..1d9fb75
--- /dev/null
+++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Dock/DockAppButton.qml
@@ -0,0 +1,670 @@
+import QtQuick
+import QtQuick.Effects
+import Quickshell
+import Quickshell.Hyprland
+import Quickshell.Wayland
+import Quickshell.Widgets
+import qs.Common
+import qs.Services
+import qs.Widgets
+
+Item {
+ id: root
+
+ clip: false
+ property var appData
+ property var contextMenu: null
+ property var dockApps: null
+ property int index: -1
+ property var parentDockScreen: null
+ property bool longPressing: false
+ property bool dragging: false
+ property point dragStartPos: Qt.point(0, 0)
+ property real dragAxisOffset: 0
+ property int targetIndex: -1
+ property int originalIndex: -1
+ property bool isVertical: SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right
+ property bool showWindowTitle: false
+ property string windowTitle: ""
+ property bool isHovered: mouseArea.containsMouse && !dragging
+ property bool showTooltip: mouseArea.containsMouse && !dragging
+ property var cachedDesktopEntry: null
+ property real actualIconSize: 40
+ property bool shouldShowIndicator: {
+ if (!appData)
+ return false;
+ if (appData.type === "window")
+ return true;
+ if (appData.type === "grouped")
+ return appData.windowCount > 0;
+ return appData.isRunning;
+ }
+ readonly property string coreIconColorOverride: SettingsData.dockLauncherLogoColorOverride
+ readonly property bool coreIconHasCustomColor: coreIconColorOverride !== "" && coreIconColorOverride !== "primary" && coreIconColorOverride !== "surface"
+ readonly property color effectiveCoreIconColor: {
+ if (coreIconColorOverride === "primary")
+ return Theme.primary;
+ if (coreIconColorOverride === "surface")
+ return Theme.surfaceText;
+ if (coreIconColorOverride !== "")
+ return coreIconColorOverride;
+ return Theme.surfaceText;
+ }
+ readonly property real effectiveCoreIconBrightness: coreIconHasCustomColor ? SettingsData.dockLauncherLogoBrightness : 0.0
+ readonly property real effectiveCoreIconContrast: coreIconHasCustomColor ? SettingsData.dockLauncherLogoContrast : 0.0
+
+ function updateDesktopEntry() {
+ if (!appData || appData.appId === "__SEPARATOR__") {
+ cachedDesktopEntry = null;
+ return;
+ }
+ if (appData.isCoreApp) {
+ cachedDesktopEntry = null;
+ return;
+ }
+ const moddedId = Paths.moddedAppId(appData.appId);
+ cachedDesktopEntry = DesktopEntries.heuristicLookup(moddedId);
+ }
+
+ Component.onCompleted: updateDesktopEntry()
+
+ onAppDataChanged: updateDesktopEntry()
+
+ Connections {
+ target: DesktopEntries
+ function onApplicationsChanged() {
+ updateDesktopEntry();
+ }
+ }
+
+ Connections {
+ target: SettingsData
+ function onAppIdSubstitutionsChanged() {
+ updateDesktopEntry();
+ }
+ }
+ property bool isWindowFocused: {
+ if (!appData) {
+ return false;
+ }
+
+ if (appData.type === "window") {
+ const toplevel = getToplevelObject();
+ if (!toplevel) {
+ return false;
+ }
+ return toplevel.activated;
+ } else if (appData.type === "grouped") {
+ // For grouped apps, check if any window is focused
+ const allToplevels = ToplevelManager.toplevels.values;
+ for (let i = 0; i < allToplevels.length; i++) {
+ const toplevel = allToplevels[i];
+ if (toplevel.appId === appData.appId && toplevel.activated) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+ property string tooltipText: {
+ if (!appData || !appData.appId) {
+ return "";
+ }
+
+ let appName;
+ if (appData.isCoreApp && appData.coreAppData) {
+ appName = appData.coreAppData.name || appData.appId;
+ } else {
+ appName = Paths.getAppName(appData.appId, cachedDesktopEntry);
+ }
+
+ if ((appData.type === "window" && showWindowTitle) || (appData.type === "grouped" && appData.windowTitle)) {
+ const title = appData.type === "window" ? windowTitle : appData.windowTitle;
+ return appName + (title ? " • " + title : "");
+ }
+
+ return appName;
+ }
+
+ function getToplevelObject() {
+ return appData?.toplevel || null;
+ }
+
+ function getGroupedToplevels() {
+ return appData?.allWindows?.map(w => w.toplevel).filter(t => t !== null) || [];
+ }
+
+ function getHyprToplevelForWayland(waylandToplevel) {
+ if (!waylandToplevel || !CompositorService.isHyprland || !Hyprland.toplevels)
+ return null;
+ const hyprToplevels = Array.from(Hyprland.toplevels.values);
+ for (let i = 0; i < hyprToplevels.length; i++) {
+ if (hyprToplevels[i].wayland === waylandToplevel)
+ return hyprToplevels[i];
+ }
+ return null;
+ }
+
+ function getSpecialWorkspaceName(waylandToplevel) {
+ const hyprToplevel = getHyprToplevelForWayland(waylandToplevel);
+ if (!hyprToplevel)
+ return "";
+ const wsName = String(hyprToplevel.lastIpcObject?.workspace?.name || hyprToplevel.workspace?.name || "");
+ if (!wsName.startsWith("special:"))
+ return "";
+ return wsName.slice("special:".length);
+ }
+
+ function restoreSpecialWorkspaceWindow(waylandToplevel) {
+ if (!SettingsData.dockRestoreSpecialWorkspaceOnClick || !CompositorService.isHyprland || !waylandToplevel)
+ return false;
+
+ const specialName = getSpecialWorkspaceName(waylandToplevel);
+ if (!specialName)
+ return false;
+
+ Hyprland.dispatch("togglespecialworkspace " + specialName);
+ Qt.callLater(() => waylandToplevel.activate());
+ return true;
+ }
+ onIsHoveredChanged: {
+ if (mouseArea.pressed || dragging)
+ return;
+ if (isHovered) {
+ exitAnimation.stop();
+ if (!bounceAnimation.running) {
+ bounceAnimation.restart();
+ }
+ } else {
+ bounceAnimation.stop();
+ exitAnimation.restart();
+ }
+ }
+
+ readonly property bool animateX: SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right
+ readonly property real animationDistance: actualIconSize
+ readonly property real animationDirection: {
+ if (SettingsData.dockPosition === SettingsData.Position.Bottom)
+ return -1;
+ if (SettingsData.dockPosition === SettingsData.Position.Top)
+ return 1;
+ if (SettingsData.dockPosition === SettingsData.Position.Right)
+ return -1;
+ if (SettingsData.dockPosition === SettingsData.Position.Left)
+ return 1;
+ return -1;
+ }
+
+ SequentialAnimation {
+ id: bounceAnimation
+
+ running: false
+
+ NumberAnimation {
+ target: root
+ property: "hoverAnimOffset"
+ to: animationDirection * animationDistance * 0.25
+ duration: Anims.durShort
+ easing.type: Easing.BezierSpline
+ easing.bezierCurve: Anims.emphasizedAccel
+ }
+
+ NumberAnimation {
+ target: root
+ property: "hoverAnimOffset"
+ to: animationDirection * animationDistance * 0.2
+ duration: Anims.durShort
+ easing.type: Easing.BezierSpline
+ easing.bezierCurve: Anims.emphasizedDecel
+ }
+ }
+
+ NumberAnimation {
+ id: exitAnimation
+
+ running: false
+ target: root
+ property: "hoverAnimOffset"
+ to: 0
+ duration: Anims.durShort
+ easing.type: Easing.BezierSpline
+ easing.bezierCurve: Anims.emphasizedDecel
+ }
+
+ Timer {
+ id: longPressTimer
+
+ interval: 500
+ repeat: false
+ onTriggered: {
+ if (appData && appData.isPinned) {
+ longPressing = true;
+ }
+ }
+ }
+
+ MouseArea {
+ id: mouseArea
+
+ anchors.fill: parent
+ hoverEnabled: true
+ enabled: true
+ preventStealing: dragging || longPressing
+ cursorShape: longPressing ? Qt.DragMoveCursor : Qt.PointingHandCursor
+ acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
+ onPressed: mouse => {
+ if (mouse.button === Qt.LeftButton && appData && appData.isPinned) {
+ dragStartPos = Qt.point(mouse.x, mouse.y);
+ longPressTimer.start();
+ }
+ }
+ onReleased: mouse => {
+ longPressTimer.stop();
+
+ const wasDragging = dragging;
+ const didReorder = wasDragging && targetIndex >= 0 && targetIndex !== originalIndex && dockApps;
+
+ if (didReorder)
+ dockApps.movePinnedApp(originalIndex, targetIndex);
+
+ longPressing = false;
+ dragging = false;
+ dragAxisOffset = 0;
+ targetIndex = -1;
+ originalIndex = -1;
+
+ if (dockApps) {
+ dockApps.draggedIndex = -1;
+ dockApps.dropTargetIndex = -1;
+ }
+
+ if (wasDragging || mouse.button !== Qt.LeftButton)
+ return;
+
+ handleLeftClick();
+ }
+
+ function handleLeftClick() {
+ if (!appData)
+ return;
+
+ switch (appData.type) {
+ case "pinned":
+ if (!appData.appId)
+ return;
+ if (appData.isCoreApp && appData.coreAppData) {
+ AppSearchService.executeCoreApp(appData.coreAppData);
+ return;
+ }
+ const pinnedEntry = cachedDesktopEntry;
+ if (pinnedEntry) {
+ AppUsageHistoryData.addAppUsage({
+ "id": appData.appId,
+ "name": pinnedEntry.name || appData.appId,
+ "icon": pinnedEntry.icon ? String(pinnedEntry.icon) : "",
+ "exec": pinnedEntry.exec || "",
+ "comment": pinnedEntry.comment || ""
+ });
+ }
+ SessionService.launchDesktopEntry(pinnedEntry);
+ break;
+ case "window":
+ const windowToplevel = getToplevelObject();
+ if (windowToplevel) {
+ if (restoreSpecialWorkspaceWindow(windowToplevel))
+ return;
+ windowToplevel.activate();
+ }
+ break;
+ case "grouped":
+ if (appData.windowCount === 0) {
+ if (!appData.appId)
+ return;
+ if (appData.isCoreApp && appData.coreAppData) {
+ AppSearchService.executeCoreApp(appData.coreAppData);
+ return;
+ }
+ const groupedEntry = cachedDesktopEntry;
+ if (groupedEntry) {
+ AppUsageHistoryData.addAppUsage({
+ "id": appData.appId,
+ "name": groupedEntry.name || appData.appId,
+ "icon": groupedEntry.icon ? String(groupedEntry.icon) : "",
+ "exec": groupedEntry.exec || "",
+ "comment": groupedEntry.comment || ""
+ });
+ }
+ SessionService.launchDesktopEntry(groupedEntry);
+ } else if (appData.windowCount === 1) {
+ const groupedToplevel = getToplevelObject();
+ if (groupedToplevel) {
+ if (restoreSpecialWorkspaceWindow(groupedToplevel))
+ return;
+ groupedToplevel.activate();
+ }
+ } else if (contextMenu) {
+ const shouldHidePin = appData.appId === "org.quickshell" || appData.appId === "com.danklinux.dms";
+ contextMenu.showForButton(root, appData, root.height + 25, shouldHidePin, cachedDesktopEntry, parentDockScreen, dockApps);
+ }
+ break;
+ }
+ }
+ onPositionChanged: mouse => {
+ if (longPressing && !dragging) {
+ const distance = Math.sqrt(Math.pow(mouse.x - dragStartPos.x, 2) + Math.pow(mouse.y - dragStartPos.y, 2));
+ if (distance > 5) {
+ dragging = true;
+ targetIndex = index;
+ originalIndex = index;
+ if (dockApps) {
+ dockApps.draggedIndex = index;
+ dockApps.dropTargetIndex = index;
+ }
+ }
+ }
+
+ if (!dragging || !dockApps)
+ return;
+
+ const axisOffset = isVertical ? (mouse.y - dragStartPos.y) : (mouse.x - dragStartPos.x);
+ dragAxisOffset = axisOffset;
+
+ const spacing = Math.min(8, Math.max(4, actualIconSize * 0.08));
+ const itemSize = actualIconSize * 1.2 + spacing;
+ const slotOffset = Math.round(axisOffset / itemSize);
+ const newTargetIndex = Math.max(0, Math.min(dockApps.pinnedAppCount - 1, originalIndex + slotOffset));
+
+ if (newTargetIndex !== targetIndex) {
+ targetIndex = newTargetIndex;
+ dockApps.dropTargetIndex = newTargetIndex;
+ }
+ }
+ onClicked: mouse => {
+ if (!appData)
+ return;
+
+ if (mouse.button === Qt.MiddleButton) {
+ switch (appData.type) {
+ case "window":
+ appData.toplevel?.close();
+ break;
+ case "grouped":
+ if (contextMenu) {
+ const shouldHidePin = appData.appId === "org.quickshell" || appData.appId === "com.danklinux.dms";
+ contextMenu.showForButton(root, appData, root.height, shouldHidePin, cachedDesktopEntry, parentDockScreen, dockApps);
+ }
+ break;
+ default:
+ if (!appData.appId)
+ return;
+ const desktopEntry = cachedDesktopEntry;
+ if (desktopEntry) {
+ AppUsageHistoryData.addAppUsage({
+ "id": appData.appId,
+ "name": desktopEntry.name || appData.appId,
+ "icon": desktopEntry.icon ? String(desktopEntry.icon) : "",
+ "exec": desktopEntry.exec || "",
+ "comment": desktopEntry.comment || ""
+ });
+ }
+ SessionService.launchDesktopEntry(desktopEntry);
+ break;
+ }
+ } else if (mouse.button === Qt.RightButton) {
+ if (!contextMenu)
+ return;
+ const shouldHidePin = appData.appId === "org.quickshell" || appData.appId === "com.danklinux.dms";
+ contextMenu.showForButton(root, appData, root.height, shouldHidePin, cachedDesktopEntry, parentDockScreen, dockApps);
+ }
+ }
+ }
+
+ property real hoverAnimOffset: 0
+
+ Item {
+ id: visualContent
+ anchors.fill: parent
+
+ transform: Translate {
+ id: iconTransform
+ x: {
+ if (dragging && !isVertical)
+ return dragAxisOffset;
+ if (!dragging && isVertical)
+ return hoverAnimOffset;
+ return 0;
+ }
+ y: {
+ if (dragging && isVertical)
+ return dragAxisOffset;
+ if (!dragging && !isVertical)
+ return hoverAnimOffset;
+ return 0;
+ }
+ }
+
+ Rectangle {
+ anchors.fill: parent
+ radius: Theme.cornerRadius
+ color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3)
+ border.width: 2
+ border.color: Theme.primary
+ visible: dragging
+ z: -1
+ }
+
+ AppIconRenderer {
+ id: coreIcon
+
+ anchors.centerIn: parent
+ iconSize: actualIconSize
+ iconValue: appData && appData.isCoreApp && appData.coreAppData ? (appData.coreAppData.icon || "") : ""
+ colorOverride: effectiveCoreIconColor
+ brightnessOverride: effectiveCoreIconBrightness
+ contrastOverride: effectiveCoreIconContrast
+ fallbackText: "?"
+ visible: iconValue !== ""
+ }
+
+ IconImage {
+ id: iconImg
+
+ anchors.centerIn: parent
+ implicitSize: appData && (appData.appId === "org.quickshell" || appData.appId === "com.danklinux.dms") ? actualIconSize * 0.85 : actualIconSize
+ source: {
+ if (!appData || appData.appId === "__SEPARATOR__") {
+ return "";
+ }
+ if (appData.isCoreApp && appData.coreAppData) {
+ return "";
+ }
+ return Paths.getAppIcon(appData.appId, cachedDesktopEntry);
+ }
+ mipmap: true
+ smooth: true
+ asynchronous: true
+ visible: status === Image.Ready && !coreIcon.visible
+ layer.enabled: appData && (appData.appId === "org.quickshell" || appData.appId === "com.danklinux.dms")
+ layer.smooth: true
+ layer.mipmap: true
+ layer.effect: MultiEffect {
+ saturation: 0
+ colorization: 1
+ colorizationColor: Theme.primary
+ }
+ }
+
+ Rectangle {
+ width: actualIconSize
+ height: actualIconSize
+ anchors.centerIn: parent
+ visible: !coreIcon.visible && iconImg.status !== Image.Ready && appData && appData.appId && !Paths.isSteamApp(appData.appId)
+ color: Theme.surfaceLight
+ radius: Theme.cornerRadius
+ border.width: 1
+ border.color: Theme.primarySelected
+
+ Text {
+ anchors.centerIn: parent
+ text: {
+ if (!appData || !appData.appId) {
+ return "?";
+ }
+
+ let appName;
+ if (appData.isCoreApp && appData.coreAppData) {
+ appName = appData.coreAppData.name || appData.appId;
+ } else {
+ appName = Paths.getAppName(appData.appId, cachedDesktopEntry);
+ }
+ return appName.charAt(0).toUpperCase();
+ }
+ font.pixelSize: Math.max(8, parent.width * 0.35)
+ color: Theme.primary
+ font.weight: Font.Bold
+ }
+ }
+
+ DankIcon {
+ anchors.centerIn: parent
+ size: actualIconSize
+ name: "sports_esports"
+ color: Theme.surfaceText
+ visible: !coreIcon.visible && iconImg.status !== Image.Ready && appData && appData.appId && Paths.isSteamApp(appData.appId)
+ }
+
+ Loader {
+ anchors.horizontalCenter: SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right ? undefined : parent.horizontalCenter
+ anchors.verticalCenter: SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right ? parent.verticalCenter : undefined
+ anchors.bottom: SettingsData.dockPosition === SettingsData.Position.Bottom ? parent.bottom : undefined
+ anchors.top: SettingsData.dockPosition === SettingsData.Position.Top ? parent.top : undefined
+ anchors.left: SettingsData.dockPosition === SettingsData.Position.Left ? parent.left : undefined
+ anchors.right: SettingsData.dockPosition === SettingsData.Position.Right ? parent.right : undefined
+ anchors.bottomMargin: SettingsData.dockPosition === SettingsData.Position.Bottom ? -(SettingsData.dockSpacing / 2 + 1.4) : 0
+ anchors.topMargin: SettingsData.dockPosition === SettingsData.Position.Top ? -(SettingsData.dockSpacing / 2 + 1.4) : 0
+ anchors.leftMargin: SettingsData.dockPosition === SettingsData.Position.Left ? -(SettingsData.dockSpacing / 2 + 1.4) : 0
+ anchors.rightMargin: SettingsData.dockPosition === SettingsData.Position.Right ? -(SettingsData.dockSpacing / 2 + 1.4) : 0
+
+ sourceComponent: SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right ? columnIndicator : rowIndicator
+
+ visible: root.shouldShowIndicator
+ }
+ }
+
+ Component {
+ id: rowIndicator
+
+ Row {
+ spacing: 2
+
+ Repeater {
+ model: {
+ if (!appData)
+ return 0;
+ if (appData.type === "grouped") {
+ return Math.min(appData.windowCount, 4);
+ } else if (appData.type === "window" || appData.isRunning) {
+ return 1;
+ }
+ return 0;
+ }
+
+ Rectangle {
+ width: {
+ if (SettingsData.dockIndicatorStyle === "circle") {
+ return Math.max(4, actualIconSize * 0.1);
+ }
+ return appData && appData.type === "grouped" && appData.windowCount > 1 ? Math.max(3, actualIconSize * 0.1) : Math.max(6, actualIconSize * 0.2);
+ }
+ height: {
+ if (SettingsData.dockIndicatorStyle === "circle") {
+ return Math.max(4, actualIconSize * 0.1);
+ }
+ return Math.max(2, actualIconSize * 0.05);
+ }
+ radius: SettingsData.dockIndicatorStyle === "circle" ? width / 2 : Theme.cornerRadius
+ color: {
+ if (!appData) {
+ return "transparent";
+ }
+
+ if (appData.type !== "grouped" || appData.windowCount === 1) {
+ if (isWindowFocused) {
+ return Theme.primary;
+ }
+ return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6);
+ }
+
+ if (appData.type === "grouped" && appData.windowCount > 1) {
+ const groupToplevels = getGroupedToplevels();
+ if (index < groupToplevels.length && groupToplevels[index].activated) {
+ return Theme.primary;
+ }
+ }
+
+ return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6);
+ }
+ }
+ }
+ }
+ }
+
+ Component {
+ id: columnIndicator
+
+ Column {
+ spacing: 2
+
+ Repeater {
+ model: {
+ if (!appData)
+ return 0;
+ if (appData.type === "grouped") {
+ return Math.min(appData.windowCount, 4);
+ } else if (appData.type === "window" || appData.isRunning) {
+ return 1;
+ }
+ return 0;
+ }
+
+ Rectangle {
+ width: {
+ if (SettingsData.dockIndicatorStyle === "circle") {
+ return Math.max(4, actualIconSize * 0.1);
+ }
+ return Math.max(2, actualIconSize * 0.05);
+ }
+ height: {
+ if (SettingsData.dockIndicatorStyle === "circle") {
+ return Math.max(4, actualIconSize * 0.1);
+ }
+ return appData && appData.type === "grouped" && appData.windowCount > 1 ? Math.max(3, actualIconSize * 0.1) : Math.max(6, actualIconSize * 0.2);
+ }
+ radius: SettingsData.dockIndicatorStyle === "circle" ? width / 2 : Theme.cornerRadius
+ color: {
+ if (!appData) {
+ return "transparent";
+ }
+
+ if (appData.type !== "grouped" || appData.windowCount === 1) {
+ if (isWindowFocused) {
+ return Theme.primary;
+ }
+ return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6);
+ }
+
+ if (appData.type === "grouped" && appData.windowCount > 1) {
+ const groupToplevels = getGroupedToplevels();
+ if (index < groupToplevels.length && groupToplevels[index].activated) {
+ return Theme.primary;
+ }
+ }
+
+ return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6);
+ }
+ }
+ }
+ }
+ }
+}