feat: dashboard show on hover, media and weather tabs added
This commit is contained in:
+6
-5
@@ -4,6 +4,7 @@ import Quickshell
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import ZShell.Config
|
||||
import qs.Components
|
||||
import qs.Modules.Bar.Components
|
||||
import qs.Modules.Bar.Popouts
|
||||
|
||||
@@ -17,7 +18,7 @@ GridLayout {
|
||||
required property ClipWrapper popoutsWrapper
|
||||
required property ShellScreen screen
|
||||
readonly property int vPadding: 6
|
||||
required property PersistentProperties visibilities
|
||||
required property ScreenState screenState
|
||||
|
||||
function axisCenterOf(item: Item): real {
|
||||
const c = item.mapToItem(root, item.implicitWidth / 2, item.implicitHeight / 2);
|
||||
@@ -34,7 +35,7 @@ GridLayout {
|
||||
return;
|
||||
}
|
||||
|
||||
if (visibilities.sidebar || visibilities.dashboard || visibilities.resources || visibilities.settings)
|
||||
if (screenState.sidebar || screenState.dashboard || screenState.resources || screenState.settings)
|
||||
return;
|
||||
|
||||
if (id === "statusIcons" && Config.bar.popouts.statusIcons) {
|
||||
@@ -149,7 +150,7 @@ GridLayout {
|
||||
delegate: EntryWrapper {
|
||||
Resources {
|
||||
horizontal: root.horizontal
|
||||
visibilities: root.visibilities
|
||||
screenState: root.screenState
|
||||
visible: !root.fullscreen || root.isHovered
|
||||
}
|
||||
}
|
||||
@@ -173,7 +174,7 @@ GridLayout {
|
||||
NotifBell {
|
||||
horizontal: root.horizontal
|
||||
popouts: root.popouts
|
||||
visibilities: root.visibilities
|
||||
screenState: root.screenState
|
||||
visible: !root.fullscreen || root.isHovered
|
||||
}
|
||||
}
|
||||
@@ -187,7 +188,7 @@ GridLayout {
|
||||
horizontal: root.horizontal
|
||||
loader: root
|
||||
popouts: root.popouts
|
||||
visibilities: root.visibilities
|
||||
screenState: root.screenState
|
||||
visible: !root.fullscreen || root.isHovered
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,9 +21,9 @@ Item {
|
||||
required property ClipWrapper popoutsWrapper
|
||||
required property string position
|
||||
required property ShellScreen screen
|
||||
readonly property bool shouldBeVisible: (!fullscreen && (!Config.bar.autoHide || visibilities.bar)) || isHovered
|
||||
readonly property bool shouldBeVisible: (!fullscreen && (!Config.bar.autoHide || screenState.bar)) || isHovered
|
||||
readonly property int vPadding: 6
|
||||
required property PersistentProperties visibilities
|
||||
required property ScreenState screenState
|
||||
|
||||
function checkPopout(pos: real): void {
|
||||
(content.item as Bar)?.checkPopout(pos);
|
||||
@@ -85,7 +85,7 @@ Item {
|
||||
popouts: root.popouts
|
||||
popoutsWrapper: root.popoutsWrapper
|
||||
screen: root.screen
|
||||
visibilities: root.visibilities
|
||||
screenState: root.screenState
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ Item {
|
||||
id: root
|
||||
|
||||
required property Item bar
|
||||
required property PersistentProperties visibilities
|
||||
required property ScreenState screenState
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import qs.Services
|
||||
WidgetBase {
|
||||
id: root
|
||||
|
||||
readonly property color contentColor: visibilities.dashboard ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
|
||||
readonly property color contentColor: screenState.dashboard ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
|
||||
readonly property string effectiveFormat: {
|
||||
let fmt = Config.general.dateFormat;
|
||||
if (!root.horizontal)
|
||||
@@ -44,10 +44,10 @@ WidgetBase {
|
||||
}
|
||||
required property GridLayout loader
|
||||
required property Wrapper popouts
|
||||
readonly property real size: horizontal ? timeText.contentWidth + Tokens.padding.small * 2 : verticalColumn.implicitHeight + Tokens.padding.small * 2
|
||||
required property PersistentProperties visibilities
|
||||
readonly property real size: horizontal ? timeText.contentWidth + Tokens.padding.medium * 2 : verticalColumn.implicitHeight + Tokens.padding.medium * 2
|
||||
required property ScreenState screenState
|
||||
|
||||
color: visibilities.dashboard ? Colors.palette.m3primary : Colors.tPalette.m3surfaceContainer
|
||||
color: screenState.dashboard ? Colors.palette.m3primary : Colors.tPalette.m3surfaceContainer
|
||||
implicitHeight: horizontal ? baseSize : size
|
||||
implicitWidth: horizontal ? size : baseSize
|
||||
radius: Tokens.rounding.full
|
||||
@@ -151,7 +151,7 @@ WidgetBase {
|
||||
color: root.contentColor
|
||||
|
||||
onClicked: {
|
||||
root.visibilities.dashboard = !root.visibilities.dashboard;
|
||||
root.screenState.dashboard = !root.screenState.dashboard;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,17 +11,17 @@ WidgetBase {
|
||||
id: root
|
||||
|
||||
required property Wrapper popouts
|
||||
required property PersistentProperties visibilities
|
||||
required property ScreenState screenState
|
||||
|
||||
color: visibilities.sidebar ? Colors.palette.m3primary : Colors.tPalette.m3surfaceContainer
|
||||
color: screenState.sidebar ? Colors.palette.m3primary : Colors.tPalette.m3surfaceContainer
|
||||
radius: Tokens.rounding.full
|
||||
|
||||
MaterialIcon {
|
||||
id: notificationCenterIcon
|
||||
|
||||
anchors.centerIn: parent
|
||||
color: root.visibilities.sidebar ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
|
||||
fill: root.visibilities.sidebar ? 1 : 0
|
||||
color: root.screenState.sidebar ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
|
||||
fill: root.screenState.sidebar ? 1 : 0
|
||||
font.pointSize: Tokens.font.size.larger
|
||||
text: NotifServer.list.length ? "\uf4fe" : "\ue7f4"
|
||||
|
||||
@@ -36,10 +36,10 @@ WidgetBase {
|
||||
}
|
||||
|
||||
StateLayer {
|
||||
color: root.visibilities.sidebar ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
|
||||
color: root.screenState.sidebar ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
|
||||
|
||||
onClicked: {
|
||||
root.visibilities.sidebar = !root.visibilities.sidebar;
|
||||
root.screenState.sidebar = !root.screenState.sidebar;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,18 +12,18 @@ import qs.Components
|
||||
WidgetBase {
|
||||
id: root
|
||||
|
||||
required property PersistentProperties visibilities
|
||||
required property ScreenState screenState
|
||||
|
||||
clip: true
|
||||
color: visibilities.resources ? Colors.palette.m3primary : Colors.tPalette.m3surfaceContainer
|
||||
color: screenState.resources ? Colors.palette.m3primary : Colors.tPalette.m3surfaceContainer
|
||||
implicitHeight: horizontal ? baseSize : gridLayout.implicitHeight + Tokens.padding.medium * 2
|
||||
implicitWidth: horizontal ? gridLayout.implicitWidth + Tokens.padding.medium * 2 : baseSize
|
||||
radius: Tokens.rounding.full
|
||||
|
||||
StateLayer {
|
||||
color: root.visibilities.resources ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
|
||||
color: root.screenState.resources ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
|
||||
|
||||
onClicked: root.visibilities.resources = !root.visibilities.resources
|
||||
onClicked: root.screenState.resources = !root.screenState.resources
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
@@ -53,8 +53,8 @@ WidgetBase {
|
||||
Layout.fillWidth: !root.horizontal
|
||||
horizontal: root.horizontal
|
||||
icon: "memory"
|
||||
iconColor: root.visibilities.resources ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
|
||||
mainColor: root.visibilities.resources ? Colors.palette.m3onPrimary : Colors.palette.m3primary
|
||||
iconColor: root.screenState.resources ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
|
||||
mainColor: root.screenState.resources ? Colors.palette.m3onPrimary : Colors.palette.m3primary
|
||||
percentage: Cpu.percentage
|
||||
warningThreshold: 95
|
||||
}
|
||||
@@ -64,8 +64,8 @@ WidgetBase {
|
||||
Layout.fillWidth: !root.horizontal
|
||||
horizontal: root.horizontal
|
||||
icon: "memory_alt"
|
||||
iconColor: root.visibilities.resources ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
|
||||
mainColor: root.visibilities.resources ? Colors.palette.m3onPrimary : Colors.palette.m3secondary
|
||||
iconColor: root.screenState.resources ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
|
||||
mainColor: root.screenState.resources ? Colors.palette.m3onPrimary : Colors.palette.m3secondary
|
||||
percentage: Memory.percentage
|
||||
warningThreshold: 80
|
||||
}
|
||||
@@ -75,8 +75,8 @@ WidgetBase {
|
||||
Layout.fillWidth: !root.horizontal
|
||||
horizontal: root.horizontal
|
||||
icon: "gamepad"
|
||||
iconColor: root.visibilities.resources ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
|
||||
mainColor: root.visibilities.resources ? Colors.palette.m3onPrimary : Colors.palette.m3tertiary
|
||||
iconColor: root.screenState.resources ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
|
||||
mainColor: root.screenState.resources ? Colors.palette.m3onPrimary : Colors.palette.m3tertiary
|
||||
percentage: Gpu.percentage
|
||||
}
|
||||
|
||||
@@ -85,8 +85,8 @@ WidgetBase {
|
||||
Layout.fillWidth: !root.horizontal
|
||||
horizontal: root.horizontal
|
||||
icon: "developer_board"
|
||||
iconColor: root.visibilities.resources ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
|
||||
mainColor: root.visibilities.resources ? Colors.palette.m3onPrimary : Colors.palette.m3primary
|
||||
iconColor: root.screenState.resources ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
|
||||
mainColor: root.screenState.resources ? Colors.palette.m3onPrimary : Colors.palette.m3primary
|
||||
percentage: Gpu.memoryUsed / Gpu.memoryTotal
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,10 +62,10 @@ Item {
|
||||
root.popouts.currentName = `traymenu${root.ind}`;
|
||||
root.popouts.currentCenter = Qt.binding(() => root.mapToItem(root.loader, root.implicitWidth / 2, 0).x);
|
||||
root.popouts.hasCurrent = true;
|
||||
if (visibilities.sidebar || visibilities.dashboard || visibilities.settings) {
|
||||
visibilities.sidebar = false;
|
||||
visibilities.dashboard = false;
|
||||
visibilities.settings = false;
|
||||
if (screenState.sidebar || screenState.dashboard || screenState.settings) {
|
||||
screenState.sidebar = false;
|
||||
screenState.dashboard = false;
|
||||
screenState.settings = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ Item {
|
||||
|
||||
readonly property int itemHeight: Config.clipboard.sizes.itemHeight
|
||||
required property ShellScreen screen
|
||||
required property PersistentProperties visibilities
|
||||
required property ScreenState screenState
|
||||
|
||||
implicitHeight: search.implicitHeight + entries.anchors.topMargin + ((view.spacing + itemHeight) * Config.clipboard.maxEntriesShown) - view.spacing
|
||||
implicitWidth: Config.clipboard.sizes.width + preview.width + preview.anchors.leftMargin
|
||||
@@ -49,7 +49,7 @@ Item {
|
||||
Keys.onUpPressed: view.decrementCurrentIndex()
|
||||
onAccepted: {
|
||||
ClipHistory.copy(view.currentItem.modelData);
|
||||
root.visibilities.clipboard = false;
|
||||
root.screenState.clipboard = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@ Item {
|
||||
property int contentHeight
|
||||
property real offsetScale: shouldBeActive ? 0 : 1
|
||||
required property ShellScreen screen
|
||||
readonly property bool shouldBeActive: visibilities.clipboard && Config.clipboard.enabled
|
||||
required property PersistentProperties visibilities
|
||||
readonly property bool shouldBeActive: screenState.clipboard && Config.clipboard.enabled
|
||||
required property ScreenState screenState
|
||||
|
||||
anchors.bottomMargin: (-implicitHeight - 5) * offsetScale
|
||||
implicitHeight: content.implicitHeight + Tokens.padding.small * 2
|
||||
@@ -35,7 +35,7 @@ Item {
|
||||
|
||||
sourceComponent: Content {
|
||||
screen: root.screen
|
||||
visibilities: root.visibilities
|
||||
screenState: root.screenState
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+137
-38
@@ -5,78 +5,177 @@ import Quickshell.Widgets
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import ZShell.Config
|
||||
import ZShell
|
||||
import qs.Components
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property PersistentProperties dashState
|
||||
readonly property real nonAnimHeight: view.implicitHeight + viewWrapper.anchors.margins * 2
|
||||
readonly property real nonAnimWidth: view.implicitWidth + viewWrapper.anchors.margins * 2
|
||||
required property PersistentProperties visibilities
|
||||
readonly property real nonAnimHeight: tabs.implicitHeight + tabs.anchors.topMargin + view.implicitHeight + viewWrapper.anchors.margins * 2
|
||||
|
||||
required property ScreenState screenState
|
||||
|
||||
readonly property var dashboardTabs: {
|
||||
const allTabs = [
|
||||
{
|
||||
component: dashComponent,
|
||||
iconName: "dashboard",
|
||||
text: qsTr("Dashboard"),
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
component: weatherComponent,
|
||||
iconName: "cloud",
|
||||
text: qsTr("Weather"),
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
component: mediaComponent,
|
||||
iconName: "queue_music",
|
||||
text: qsTr("Media"),
|
||||
enabled: true
|
||||
},
|
||||
// {
|
||||
// component: testingComponent,
|
||||
// iconName: "speed",
|
||||
// text: qsTr("Testing"),
|
||||
// enabled: true
|
||||
// },
|
||||
];
|
||||
return allTabs.filter(tab => tab.enabled);
|
||||
}
|
||||
|
||||
implicitHeight: nonAnimHeight
|
||||
implicitWidth: nonAnimWidth
|
||||
implicitHeight: nonAnimHeight
|
||||
|
||||
Behavior on implicitHeight {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
Behavior on implicitWidth {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
|
||||
Tabs {
|
||||
id: tabs
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: ZUtils.clamp(anchors.margins - Config.bar.border, 0, anchors.margins)
|
||||
anchors.margins: Tokens.padding.large
|
||||
|
||||
nonAnimWidth: root.nonAnimWidth - anchors.margins * 2
|
||||
screenState: root.screenState
|
||||
tabs: root.dashboardTabs
|
||||
}
|
||||
|
||||
ClippingRectangle {
|
||||
id: viewWrapper
|
||||
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.top: tabs.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.margins: Tokens.padding.small
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.margins: Tokens.padding.small
|
||||
color: "transparent"
|
||||
radius: Tokens.rounding.extraLarge - anchors.margins
|
||||
|
||||
Item {
|
||||
Flickable {
|
||||
id: view
|
||||
|
||||
readonly property int currentIndex: root.dashState?.currentTab
|
||||
readonly property Item currentItem: row.children[currentIndex]
|
||||
readonly property int currentIndex: root.screenState.dashboardTab
|
||||
readonly property Item currentItem: {
|
||||
repeater.count;
|
||||
return repeater.itemAt(currentIndex);
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
implicitHeight: currentItem.implicitHeight
|
||||
implicitWidth: currentItem.implicitWidth
|
||||
|
||||
implicitWidth: currentItem?.implicitWidth ?? 0
|
||||
implicitHeight: currentItem?.implicitHeight ?? 0
|
||||
|
||||
contentX: currentItem?.x ?? 0
|
||||
contentWidth: row.implicitWidth
|
||||
contentHeight: row.implicitHeight
|
||||
|
||||
interactive: false
|
||||
|
||||
onContentXChanged: {
|
||||
if (!moving || !currentItem)
|
||||
return;
|
||||
|
||||
const x = contentX - currentItem.x;
|
||||
if (x > currentItem.implicitWidth / 2)
|
||||
root.screenState.dashboardTab = Math.min(root.screenState.dashboardTab + 1, tabs.count - 1);
|
||||
else if (x < -currentItem.implicitWidth / 2)
|
||||
root.screenState.dashboardTab = Math.max(root.screenState.dashboardTab - 1, 0);
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: row
|
||||
|
||||
Pane {
|
||||
index: 0
|
||||
Repeater {
|
||||
id: repeater
|
||||
|
||||
sourceComponent: Dash {
|
||||
dashState: root.dashState
|
||||
visibilities: root.visibilities
|
||||
model: ScriptModel {
|
||||
values: root.dashboardTabs
|
||||
}
|
||||
|
||||
delegate: Loader {
|
||||
id: paneLoader
|
||||
|
||||
required property int index
|
||||
required property var modelData
|
||||
|
||||
Layout.alignment: Qt.AlignTop
|
||||
|
||||
sourceComponent: modelData.component
|
||||
|
||||
Component.onCompleted: active = Qt.binding(() => {
|
||||
if (index === view.currentIndex)
|
||||
return true;
|
||||
const vx = Math.floor(view.visibleArea.xPosition * view.contentWidth);
|
||||
const vex = Math.floor(vx + view.visibleArea.widthRatio * view.contentWidth);
|
||||
return (vx >= x && vx <= x + implicitWidth) || (vex >= x && vex <= x + implicitWidth);
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: dashComponent
|
||||
|
||||
Dash {
|
||||
screenState: root.screenState
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: weatherComponent
|
||||
|
||||
WeatherTab {}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: mediaComponent
|
||||
|
||||
Media {
|
||||
screenState: root.screenState
|
||||
}
|
||||
}
|
||||
|
||||
// Component {
|
||||
// id: testingComponent
|
||||
//
|
||||
// WeatherTest {}
|
||||
// }
|
||||
|
||||
Behavior on contentX {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component Pane: Loader {
|
||||
id: pane
|
||||
|
||||
required property int index
|
||||
|
||||
Layout.alignment: Qt.AlignTop
|
||||
|
||||
Component.onCompleted: active = Qt.binding(() => {
|
||||
// Always keep current tab loaded
|
||||
if (pane.index === view.currentIndex)
|
||||
return true;
|
||||
const vx = Math.floor(view.visibleArea.xPosition * view.contentWidth);
|
||||
const vex = Math.floor(vx + view.visibleArea.widthRatio * view.contentWidth);
|
||||
return (vx >= x && vx <= x + implicitWidth) || (vex >= x && vex <= x + implicitWidth);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,10 +11,9 @@ import qs.Services
|
||||
GridLayout {
|
||||
id: root
|
||||
|
||||
required property PersistentProperties dashState
|
||||
readonly property bool dashboardVisible: visibilities.dashboard
|
||||
readonly property bool dashboardVisible: screenState.dashboard
|
||||
property int radius: Tokens.rounding.medium
|
||||
required property PersistentProperties visibilities
|
||||
required property ScreenState screenState
|
||||
|
||||
columnSpacing: Tokens.spacing.small
|
||||
rowSpacing: Tokens.spacing.small
|
||||
@@ -61,7 +60,7 @@ GridLayout {
|
||||
User {
|
||||
id: user
|
||||
|
||||
dashState: root.dashState
|
||||
screenState: root.screenState
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,8 +71,7 @@ GridLayout {
|
||||
Layout.row: 0
|
||||
radius: root.radius
|
||||
|
||||
Weather {
|
||||
}
|
||||
Weather {}
|
||||
}
|
||||
|
||||
Rect {
|
||||
@@ -87,7 +85,7 @@ GridLayout {
|
||||
Calendar {
|
||||
id: calendar
|
||||
|
||||
dashState: root.dashState
|
||||
screenState: root.screenState
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,20 +102,6 @@ GridLayout {
|
||||
}
|
||||
}
|
||||
|
||||
CustomClippingRect {
|
||||
Layout.column: 0
|
||||
Layout.columnSpan: 5
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: media.implicitHeight
|
||||
Layout.row: 2
|
||||
color: Colors.tPalette.m3surfaceContainer
|
||||
radius: root.radius
|
||||
|
||||
Media {
|
||||
id: media
|
||||
}
|
||||
}
|
||||
|
||||
component Rect: CustomRect {
|
||||
color: Colors.tPalette.m3surfaceContainer
|
||||
}
|
||||
|
||||
@@ -12,12 +12,12 @@ CustomMouseArea {
|
||||
id: root
|
||||
|
||||
property int activeGrid: 1
|
||||
required property PersistentProperties dashState
|
||||
required property ScreenState screenState
|
||||
property bool initialized: false
|
||||
property int month1
|
||||
property int month2
|
||||
readonly property int realCurrMonth: dashState.currentDate.getMonth()
|
||||
readonly property int realCurrYear: dashState.currentDate.getFullYear()
|
||||
readonly property int realCurrMonth: screenState.dashboardDate.getMonth()
|
||||
readonly property int realCurrYear: screenState.dashboardDate.getFullYear()
|
||||
property int year1
|
||||
property int year2
|
||||
|
||||
@@ -43,9 +43,9 @@ CustomMouseArea {
|
||||
|
||||
function onWheel(event: WheelEvent): void {
|
||||
if (event.angleDelta.y > 0)
|
||||
root.dashState.currentDate = new Date(root.realCurrYear, root.realCurrMonth - 1, 1);
|
||||
root.screenState.dashboardDate = new Date(root.realCurrYear, root.realCurrMonth - 1, 1);
|
||||
else if (event.angleDelta.y < 0)
|
||||
root.dashState.currentDate = new Date(root.realCurrYear, root.realCurrMonth + 1, 1);
|
||||
root.screenState.dashboardDate = new Date(root.realCurrYear, root.realCurrMonth + 1, 1);
|
||||
}
|
||||
|
||||
acceptedButtons: Qt.MiddleButton
|
||||
@@ -61,7 +61,7 @@ CustomMouseArea {
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
onClicked: root.dashState.currentDate = new Date()
|
||||
onClicked: root.screenState.dashboardDate = new Date()
|
||||
onRealCurrMonthChanged: handleDateChange()
|
||||
onRealCurrYearChanged: handleDateChange()
|
||||
|
||||
@@ -135,7 +135,7 @@ CustomMouseArea {
|
||||
padding: Tokens.padding.extraSmall
|
||||
type: IconButton.Text
|
||||
|
||||
onClicked: root.dashState.currentDate = new Date(root.realCurrYear, root.realCurrMonth - 1, 1)
|
||||
onClicked: root.screenState.dashboardDate = new Date(root.realCurrYear, root.realCurrMonth - 1, 1)
|
||||
}
|
||||
|
||||
Item {
|
||||
@@ -158,7 +158,7 @@ CustomMouseArea {
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: root.dashState.currentDate = new Date()
|
||||
onClicked: root.screenState.dashboardDate = new Date()
|
||||
}
|
||||
|
||||
Item {
|
||||
@@ -206,7 +206,7 @@ CustomMouseArea {
|
||||
padding: Tokens.padding.extraSmall
|
||||
type: IconButton.Text
|
||||
|
||||
onClicked: root.dashState.currentDate = new Date(root.realCurrYear, root.realCurrMonth + 1, 1)
|
||||
onClicked: root.screenState.dashboardDate = new Date(root.realCurrYear, root.realCurrMonth + 1, 1)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,329 +0,0 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import Quickshell
|
||||
import Quickshell.Services.Mpris
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Shapes
|
||||
import QtQuick.Effects
|
||||
import ZShell.Components
|
||||
import ZShell.Services
|
||||
import ZShell.Config
|
||||
import qs.Modules.Dashboard.Dash.Media
|
||||
import qs.Daemons
|
||||
import qs.Components
|
||||
import qs.Helpers
|
||||
import qs.Services
|
||||
|
||||
Item {
|
||||
id: root
|
||||
readonly property bool hasUnknownLength: (Players.active?.length ?? 0) > 2147483647
|
||||
property real playerProgress: {
|
||||
const active = Players.active;
|
||||
return active?.length ? (active.position % active.length) / active.length : 0;
|
||||
}
|
||||
property int rowHeight: Tokens.padding.large + Config.dashboard.sizes.mediaProgressThickness + Tokens.spacing.small
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
implicitHeight: layout.implicitHeight + layout.anchors.margins * 2
|
||||
function lengthStr(length: int): string {
|
||||
if (length < 0)
|
||||
return "-1:-1";
|
||||
|
||||
const hours = Math.floor(length / 3600);
|
||||
const mins = Math.floor((length % 3600) / 60);
|
||||
const secs = Math.floor(length % 60).toString().padStart(2, "0");
|
||||
|
||||
if (hours > 0)
|
||||
return `${hours}:${mins.toString().padStart(2, "0")}:${secs}`;
|
||||
return `${mins}:${secs}`;
|
||||
}
|
||||
|
||||
Behavior on playerProgress {
|
||||
Anim {
|
||||
duration: Tokens.anim.durations.large
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
running: Players.active?.isPlaying ?? false
|
||||
interval: Config.dashboard.mediaUpdateInterval
|
||||
triggeredOnStart: true
|
||||
repeat: true
|
||||
onTriggered: Players.active?.positionChanged()
|
||||
}
|
||||
|
||||
ServiceRef {
|
||||
service: Audio.cava
|
||||
}
|
||||
|
||||
BackgroundShapes {
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
Shape {
|
||||
id: visualizer
|
||||
|
||||
readonly property int bars: Config.services.visualizerBars
|
||||
property color color: Qt.alpha(Colors.palette.m3primary, 0.5)
|
||||
property color fillColor: Qt.alpha(color, 0.1)
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: -(shape.strokeWidth / 2)
|
||||
layer.enabled: true
|
||||
asynchronous: true
|
||||
preferredRendererType: Shape.CurveRenderer
|
||||
|
||||
ShapePath {
|
||||
id: shape
|
||||
strokeColor: visualizer.color
|
||||
fillColor: visualizer.fillColor
|
||||
strokeWidth: 2
|
||||
|
||||
PathSvg {
|
||||
path: shape.curvePath
|
||||
}
|
||||
|
||||
property string curvePath: {
|
||||
const values = Audio.cava.values;
|
||||
const n = values.length;
|
||||
|
||||
if (n < 2)
|
||||
return "";
|
||||
|
||||
const h = root.height + shape.strokeWidth / 2;
|
||||
const w = root.width + shape.strokeWidth;
|
||||
|
||||
function x(i) {
|
||||
return i * w / (n - 1);
|
||||
}
|
||||
|
||||
function y(i) {
|
||||
return h - (values[i] * Config.dashboard.sizes.mediaVisualizerSize) / 2;
|
||||
}
|
||||
|
||||
let d = `M 0 ${h} `;
|
||||
d += `L ${x(0)} ${y(0)} `;
|
||||
|
||||
for (let i = 0; i < n - 1; ++i) {
|
||||
const x0 = x(i);
|
||||
const y0 = y(i);
|
||||
|
||||
const x1 = x(i + 1);
|
||||
const y1 = y(i + 1);
|
||||
|
||||
const prev = Math.max(0, i - 1);
|
||||
const next = Math.min(n - 1, i + 2);
|
||||
|
||||
const c1x = x0 + (x1 - x(prev)) / 6;
|
||||
const c1y = y0 + (y1 - y(prev)) / 6;
|
||||
|
||||
const c2x = x1 - (x(next) - x0) / 6;
|
||||
const c2y = y1 - (y(next) - y0) / 6;
|
||||
|
||||
d += `C ${c1x} ${c1y}, ${c2x} ${c2y}, ${x1} ${y1} `;
|
||||
}
|
||||
|
||||
d += `L ${w} ${h} `;
|
||||
d += `L 0 ${h} Z`;
|
||||
|
||||
return d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CustomRect {
|
||||
anchors.fill: visualizer
|
||||
|
||||
color: Qt.alpha(Colors.palette.m3shadow, 0.2)
|
||||
layer.enabled: true
|
||||
layer.effect: MultiEffect {
|
||||
maskEnabled: true
|
||||
maskSource: visualizer
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: layout
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Tokens.padding.large
|
||||
anchors.leftMargin: Tokens.padding.extraLarge
|
||||
|
||||
CoverVisualizer {
|
||||
Layout.fillHeight: true
|
||||
implicitWidth: Config.dashboard.sizes.mediaCoverArtSize
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Tokens.spacing.largeIncreased
|
||||
Layout.rightMargin: Tokens.spacing.largeIncreased
|
||||
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
MarqueeText {
|
||||
id: title
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||
color: Colors.palette.m3primary
|
||||
font.pointSize: Tokens.font.size.normal
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
pauseMs: 4000
|
||||
text: (Players.active?.trackTitle ?? qsTr("No media")) || qsTr("Unknown title")
|
||||
implicitWidth: parent.width - Tokens.padding.large * 4
|
||||
}
|
||||
|
||||
MarqueeText {
|
||||
id: album
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||
color: Colors.palette.m3outline
|
||||
font.pointSize: Tokens.font.size.small
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
pauseMs: 4000
|
||||
text: (Players.active?.trackAlbum ?? qsTr("No media")) || qsTr("Unknown album")
|
||||
implicitWidth: parent.width - Tokens.padding.large * 4
|
||||
}
|
||||
|
||||
MarqueeText {
|
||||
id: artist
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||
color: Colors.palette.m3secondary
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
pauseMs: 4000
|
||||
text: (Players.active?.trackArtist ?? qsTr("No media")) || qsTr("Unknown artist")
|
||||
implicitWidth: parent.width - Tokens.padding.large * 4
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: positionSliderLayout
|
||||
Layout.topMargin: Tokens.spacing.large
|
||||
Layout.fillWidth: true
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
TextMetrics {
|
||||
id: timeMetrics
|
||||
|
||||
text: Players.active ? root.lengthStr(Math.max(Players.active.position, root.hasUnknownLength ? 0 : Players.active.length)).replace(/[1-9]/g, "0") : "00:00"
|
||||
}
|
||||
|
||||
CustomText {
|
||||
id: positionLabel
|
||||
|
||||
Layout.preferredWidth: timeMetrics.width
|
||||
text: root.lengthStr(Players.active?.position ?? -1)
|
||||
color: Colors.palette.m3onSurfaceVariant
|
||||
font: timeMetrics.font
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
CustomSlider {
|
||||
id: positionSlider
|
||||
|
||||
Layout.fillWidth: true
|
||||
value: Players.active ? Players.active.position / (Players.active.length || 1) : 0
|
||||
enabled: (Players.active?.canSeek ?? false) && !root.hasUnknownLength
|
||||
wavy: true
|
||||
animateWave: Players.active?.isPlaying ?? false
|
||||
waveFrequency: 5
|
||||
waveDuration: 2000
|
||||
interactionOnMove: false
|
||||
onInteraction: value => {
|
||||
const active = Players.active;
|
||||
if (active?.canSeek && active?.positionSupported)
|
||||
active.position = value * active.length;
|
||||
}
|
||||
Binding {
|
||||
target: positionLabel
|
||||
property: "text"
|
||||
value: root.lengthStr(positionSlider.pos * (Players.active?.length ?? 0))
|
||||
when: positionSlider.dragging
|
||||
}
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.preferredWidth: timeMetrics.width
|
||||
text: root.hasUnknownLength ? "--:--" : root.lengthStr(Players.active?.length ?? -1)
|
||||
color: Colors.palette.m3onSurfaceVariant
|
||||
font: timeMetrics.font
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
ButtonRow {
|
||||
Layout.topMargin: Tokens.spacing.largeIncreased
|
||||
Layout.fillWidth: true
|
||||
spacing: Tokens.spacing.extraSmall
|
||||
Layout.leftMargin: positionSliderLayout.implicitWidth / 4
|
||||
Layout.rightMargin: positionSliderLayout.implicitWidth / 4
|
||||
|
||||
IconButton {
|
||||
type: IconButton.Tonal
|
||||
icon: "shuffle"
|
||||
isRound: true
|
||||
shapeMorph: true
|
||||
checked: Players.active?.shuffle ?? false
|
||||
enabled: Players.active?.shuffleSupported
|
||||
onClicked: Players.active.shuffle = !Players.active?.shuffle
|
||||
}
|
||||
|
||||
IconButton {
|
||||
id: previousBtn
|
||||
|
||||
type: IconButton.Tonal
|
||||
icon: "skip_previous"
|
||||
isRound: true
|
||||
shapeMorph: true
|
||||
enabled: Players.active?.canGoPrevious
|
||||
onClicked: Players.active?.previous()
|
||||
font.pointSize: Tokens.font.size.large
|
||||
}
|
||||
|
||||
IconButton {
|
||||
id: playPauseBtn
|
||||
|
||||
icon: Players.active?.isPlaying ? "pause" : "play_arrow"
|
||||
isRound: true
|
||||
shapeMorph: true
|
||||
fillWidth: true
|
||||
checked: Players.active?.isPlaying ?? false
|
||||
enabled: Players.active?.canTogglePlaying
|
||||
onClicked: Players.active?.togglePlaying()
|
||||
font.pointSize: Tokens.font.size.large
|
||||
}
|
||||
|
||||
IconButton {
|
||||
id: nextBtn
|
||||
|
||||
type: IconButton.Tonal
|
||||
icon: "skip_next"
|
||||
isRound: true
|
||||
shapeMorph: true
|
||||
enabled: Players.active?.canGoNext
|
||||
onClicked: Players.active?.next()
|
||||
font.pointSize: Tokens.font.size.large
|
||||
}
|
||||
|
||||
IconButton {
|
||||
type: IconButton.Tonal
|
||||
icon: Players.active?.loopState === MprisLoopState.Track ? "repeat_one" : "repeat"
|
||||
isRound: true
|
||||
shapeMorph: true
|
||||
checked: Players.active?.loopState === MprisLoopState.Track || Players.active?.loopState === MprisLoopState.Playlist
|
||||
enabled: Players.active?.loopSupported
|
||||
onClicked: {
|
||||
const state = Players.active.loopState;
|
||||
if (state === MprisLoopState.None)
|
||||
Players.active.loopState = MprisLoopState.Track;
|
||||
else if (state === MprisLoopState.Track)
|
||||
Players.active.loopState = MprisLoopState.Playlist;
|
||||
else
|
||||
Players.active.loopState = MprisLoopState.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import qs.Helpers
|
||||
Row {
|
||||
id: root
|
||||
|
||||
required property PersistentProperties dashState
|
||||
required property ScreenState screenState
|
||||
|
||||
padding: 20
|
||||
spacing: 12
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import M3Shapes
|
||||
import ZShell.Config
|
||||
import qs.Modules.Dashboard.Media
|
||||
import qs.Components
|
||||
import qs.Services
|
||||
import qs.Helpers
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property ScreenState screenState
|
||||
|
||||
implicitWidth: 1000
|
||||
implicitHeight: 320
|
||||
|
||||
BackgroundShapes {
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Tokens.padding.large
|
||||
spacing: Tokens.spacing.extraLarge
|
||||
|
||||
CoverVisualizer {
|
||||
Layout.fillHeight: true
|
||||
implicitWidth: 250
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
state: Players.active ? "" : "noMedia"
|
||||
|
||||
states: State {
|
||||
name: "noMedia"
|
||||
|
||||
PropertyChanges {
|
||||
noMedia.opacity: 1
|
||||
content.opacity: 0
|
||||
}
|
||||
}
|
||||
|
||||
transitions: [
|
||||
Transition {
|
||||
from: ""
|
||||
|
||||
SequentialAnimation {
|
||||
Anim {
|
||||
target: content
|
||||
property: "opacity"
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
Anim {
|
||||
target: noMedia
|
||||
property: "opacity"
|
||||
type: Anim.SlowEffects
|
||||
}
|
||||
}
|
||||
},
|
||||
Transition {
|
||||
to: ""
|
||||
|
||||
SequentialAnimation {
|
||||
Anim {
|
||||
target: noMedia
|
||||
property: "opacity"
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
Anim {
|
||||
target: content
|
||||
property: "opacity"
|
||||
type: Anim.SlowEffects
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
Loader {
|
||||
id: noMedia
|
||||
|
||||
anchors.centerIn: parent
|
||||
anchors.horizontalCenterOffset: -Tokens.padding.extraLarge * 2
|
||||
asynchronous: true
|
||||
active: opacity > 0
|
||||
opacity: 0
|
||||
|
||||
sourceComponent: ColumnLayout {
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
MaterialShape {
|
||||
Layout.topMargin: (pathBounds().height - implicitSize) / 2
|
||||
Layout.bottomMargin: (pathBounds().height - implicitSize) / 2 + Tokens.spacing.small
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
color: Colors.palette.m3primaryContainer
|
||||
implicitSize: icon.implicitHeight + Tokens.padding.extraLarge * 2
|
||||
shape: MaterialShape.ClamShell
|
||||
|
||||
Behavior on color {
|
||||
CAnim {}
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
id: icon
|
||||
|
||||
anchors.centerIn: parent
|
||||
text: "queue_music"
|
||||
font.pointSize: Tokens.font.size.large
|
||||
color: Colors.palette.m3onPrimaryContainer
|
||||
}
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: qsTr("Nothing playing")
|
||||
font.pointSize: Tokens.font.size.normal
|
||||
}
|
||||
|
||||
CustomText {
|
||||
text: qsTr("Play something for it to show up here!")
|
||||
color: Colors.palette.m3onSurfaceVariant
|
||||
font.pointSize: Tokens.font.size.large
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: content
|
||||
|
||||
anchors.fill: parent
|
||||
asynchronous: true
|
||||
active: opacity > 0
|
||||
|
||||
sourceComponent: RowLayout {
|
||||
spacing: Tokens.spacing.extraLarge
|
||||
|
||||
Details {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
LyricsAndSelector {
|
||||
Layout.fillHeight: true
|
||||
implicitWidth: 300
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -11,8 +11,8 @@ Item {
|
||||
id: cover
|
||||
|
||||
anchors.centerIn: parent
|
||||
implicitHeight: Config.dashboard.sizes.mediaCoverArtSize
|
||||
implicitWidth: Config.dashboard.sizes.mediaCoverArtSize
|
||||
implicitHeight: 200
|
||||
implicitWidth: 200
|
||||
shape.shape: MaterialShape.Cookie9Sided
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell.Services.Mpris
|
||||
import ZShell.Components
|
||||
import ZShell.Config
|
||||
import qs.Components
|
||||
import qs.Services
|
||||
import qs.Helpers
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
readonly property bool hasUnknownLength: (Players.active?.length ?? 0) > 2147483647
|
||||
|
||||
function lengthStr(length: int): string {
|
||||
if (length < 0)
|
||||
return "-1:-1";
|
||||
|
||||
const hours = Math.floor(length / 3600);
|
||||
const mins = Math.floor((length % 3600) / 60);
|
||||
const secs = Math.floor(length % 60).toString().padStart(2, "0");
|
||||
|
||||
if (hours > 0)
|
||||
return `${hours}:${mins.toString().padStart(2, "0")}:${secs}`;
|
||||
return `${mins}:${secs}`;
|
||||
}
|
||||
|
||||
spacing: Tokens.spacing.extraSmall
|
||||
|
||||
Timer {
|
||||
running: Players.active?.isPlaying ?? false
|
||||
interval: Config.dashboard.mediaUpdateInterval
|
||||
triggeredOnStart: true
|
||||
repeat: true
|
||||
onTriggered: Players.active?.positionChanged()
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
text: Players.active?.trackTitle ?? ""
|
||||
font.pointSize: Tokens.font.size.large
|
||||
elide: Text.ElideRight
|
||||
animate: true
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
text: Players.active?.trackArtist || qsTr("Unknown artist")
|
||||
color: Colors.palette.m3onSurfaceVariant
|
||||
font.pointSize: Tokens.font.size.normal
|
||||
elide: Text.ElideRight
|
||||
animate: true
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
text: Players.active?.trackAlbum || qsTr("Unknown album")
|
||||
color: Colors.palette.m3secondary
|
||||
font.pointSize: Tokens.font.size.normal
|
||||
elide: Text.ElideRight
|
||||
animate: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.topMargin: Tokens.spacing.extraLargeIncreased
|
||||
Layout.fillWidth: true
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
TextMetrics {
|
||||
id: timeMetrics
|
||||
|
||||
text: Players.active ? root.lengthStr(Math.max(Players.active.position, root.hasUnknownLength ? 0 : Players.active.length)).replace(/[1-9]/g, "0") : "00:00"
|
||||
font.pointSize: Tokens.font.size.normal
|
||||
}
|
||||
|
||||
CustomText {
|
||||
id: positionLabel
|
||||
|
||||
Layout.preferredWidth: timeMetrics.width
|
||||
text: root.lengthStr(Players.active?.position ?? -1)
|
||||
color: Colors.palette.m3onSurfaceVariant
|
||||
font: timeMetrics.font
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
CustomSlider {
|
||||
id: positionSlider
|
||||
|
||||
Layout.fillWidth: true
|
||||
value: Players.active ? Players.active.position / (Players.active.length || 1) : 0
|
||||
enabled: (Players.active?.canSeek ?? false) && !root.hasUnknownLength
|
||||
wavy: true
|
||||
animateWave: Players.active?.isPlaying ?? false
|
||||
waveFrequency: 5
|
||||
waveDuration: 2000
|
||||
interactionOnMove: false
|
||||
onInteraction: value => {
|
||||
const active = Players.active;
|
||||
if (active?.canSeek && active?.positionSupported)
|
||||
active.position = value * active.length;
|
||||
}
|
||||
|
||||
Binding {
|
||||
target: positionLabel
|
||||
property: "text"
|
||||
value: root.lengthStr(positionSlider.pos * (Players.active?.length ?? 0))
|
||||
when: positionSlider.dragging
|
||||
}
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.preferredWidth: timeMetrics.width
|
||||
text: root.hasUnknownLength ? "--:--" : root.lengthStr(Players.active?.length ?? -1)
|
||||
color: Colors.palette.m3onSurfaceVariant
|
||||
font: timeMetrics.font
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
|
||||
ButtonRow {
|
||||
Layout.topMargin: Tokens.spacing.largeIncreased
|
||||
Layout.fillWidth: true
|
||||
spacing: Tokens.spacing.extraSmall
|
||||
|
||||
IconButton {
|
||||
type: IconButton.Tonal
|
||||
icon: "shuffle"
|
||||
isRound: true
|
||||
shapeMorph: true
|
||||
checked: Players.active?.shuffle ?? false
|
||||
font.pointSize: Tokens.font.size.large
|
||||
enabled: Players.active?.shuffleSupported
|
||||
onClicked: Players.active.shuffle = !Players.active?.shuffle
|
||||
implicitWidth: Math.round(implicitHeight * 0.9)
|
||||
}
|
||||
|
||||
IconButton {
|
||||
id: previousBtn
|
||||
|
||||
type: IconButton.Tonal
|
||||
icon: "skip_previous"
|
||||
isRound: true
|
||||
shapeMorph: true
|
||||
font.pointSize: Tokens.font.size.extraLarge
|
||||
enabled: Players.active?.canGoPrevious
|
||||
onClicked: Players.active?.previous()
|
||||
}
|
||||
|
||||
IconButton {
|
||||
id: playPauseBtn
|
||||
|
||||
icon: Players.active?.isPlaying ? "pause" : "play_arrow"
|
||||
isRound: true
|
||||
shapeMorph: true
|
||||
fillWidth: true
|
||||
checked: Players.active?.isPlaying ?? false
|
||||
font.pointSize: Tokens.font.size.extraLarge
|
||||
enabled: Players.active?.canTogglePlaying
|
||||
onClicked: Players.active?.togglePlaying()
|
||||
}
|
||||
|
||||
IconButton {
|
||||
id: nextBtn
|
||||
|
||||
type: IconButton.Tonal
|
||||
icon: "skip_next"
|
||||
isRound: true
|
||||
shapeMorph: true
|
||||
font.pointSize: Tokens.font.size.extraLarge
|
||||
enabled: Players.active?.canGoNext
|
||||
onClicked: Players.active?.next()
|
||||
}
|
||||
|
||||
IconButton {
|
||||
type: IconButton.Tonal
|
||||
icon: Players.active?.loopState === MprisLoopState.Track ? "repeat_one" : "repeat"
|
||||
isRound: true
|
||||
shapeMorph: true
|
||||
checked: Players.active?.loopState === MprisLoopState.Track || Players.active?.loopState === MprisLoopState.Playlist
|
||||
font.pointSize: Tokens.font.size.large
|
||||
enabled: Players.active?.loopSupported
|
||||
onClicked: {
|
||||
const state = Players.active.loopState;
|
||||
if (state === MprisLoopState.None)
|
||||
Players.active.loopState = MprisLoopState.Track;
|
||||
else if (state === MprisLoopState.Track)
|
||||
Players.active.loopState = MprisLoopState.Playlist;
|
||||
else
|
||||
Players.active.loopState = MprisLoopState.None;
|
||||
}
|
||||
implicitWidth: Math.round(implicitHeight * 0.9)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,317 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Layouts
|
||||
import ZShell.Config
|
||||
import ZShell.Services
|
||||
import qs.Components
|
||||
import qs.Effects
|
||||
import qs.Services
|
||||
import qs.Helpers
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
// Funny binding hack to make lyrics update
|
||||
readonly property var _: {
|
||||
const p = Players.active;
|
||||
if (p)
|
||||
Lyrics.setTrack(p.trackArtist, p.trackTitle, p.trackAlbum, p.length);
|
||||
else
|
||||
Lyrics.clearTrack();
|
||||
}
|
||||
|
||||
readonly property real fadeAmount: 0.1
|
||||
property list<string> lyricList: Lyrics.lyrics
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: Mask {
|
||||
maskSource: mask
|
||||
|
||||
Rectangle {
|
||||
id: mask
|
||||
|
||||
layer.enabled: true
|
||||
visible: false
|
||||
implicitWidth: root.width
|
||||
implicitHeight: root.height
|
||||
|
||||
gradient: Gradient {
|
||||
orientation: Gradient.Vertical
|
||||
|
||||
GradientStop {
|
||||
color: Qt.alpha("black", 0)
|
||||
position: 0
|
||||
}
|
||||
GradientStop {
|
||||
color: Qt.alpha("black", 1)
|
||||
position: root.fadeAmount
|
||||
}
|
||||
GradientStop {
|
||||
color: Qt.alpha("black", 1)
|
||||
position: 1 - root.fadeAmount
|
||||
}
|
||||
GradientStop {
|
||||
color: Qt.alpha("black", 0)
|
||||
position: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state: {
|
||||
if (Lyrics.hasLyrics)
|
||||
return "hasLyrics";
|
||||
if (Lyrics.loading)
|
||||
return "loading";
|
||||
return "noLyrics";
|
||||
}
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "loading"
|
||||
|
||||
PropertyChanges {
|
||||
loadingIndicator.opacity: 1
|
||||
lyrics.opacity: 0
|
||||
noLyrics.opacity: 0
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "hasLyrics"
|
||||
|
||||
PropertyChanges {
|
||||
loadingIndicator.opacity: 0
|
||||
lyrics.opacity: 1
|
||||
noLyrics.opacity: 0
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "noLyrics"
|
||||
|
||||
PropertyChanges {
|
||||
loadingIndicator.opacity: 0
|
||||
lyrics.opacity: 0
|
||||
noLyrics.opacity: 1
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
transitions: [
|
||||
Transition {
|
||||
from: "loading"
|
||||
|
||||
SequentialAnimation {
|
||||
Anim {
|
||||
target: loadingIndicator
|
||||
property: "opacity"
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
Anim {
|
||||
targets: [lyrics, noLyrics]
|
||||
property: "opacity"
|
||||
type: Anim.SlowEffects
|
||||
}
|
||||
}
|
||||
},
|
||||
Transition {
|
||||
from: "hasLyrics"
|
||||
|
||||
SequentialAnimation {
|
||||
Anim {
|
||||
target: lyrics
|
||||
property: "opacity"
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
Anim {
|
||||
targets: [loadingIndicator, noLyrics]
|
||||
property: "opacity"
|
||||
type: Anim.SlowEffects
|
||||
}
|
||||
}
|
||||
},
|
||||
Transition {
|
||||
from: "noLyrics"
|
||||
|
||||
SequentialAnimation {
|
||||
Anim {
|
||||
target: noLyrics
|
||||
property: "opacity"
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
Anim {
|
||||
targets: [loadingIndicator, lyrics]
|
||||
property: "opacity"
|
||||
type: Anim.SlowEffects
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
Loader {
|
||||
id: loadingIndicator
|
||||
|
||||
anchors.centerIn: parent
|
||||
asynchronous: true
|
||||
active: opacity > 0
|
||||
opacity: 0
|
||||
|
||||
sourceComponent: ColumnLayout {
|
||||
spacing: Tokens.spacing.large
|
||||
|
||||
CustomRect {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
implicitWidth: shape.implicitSize + Tokens.padding.medium * 2
|
||||
implicitHeight: shape.implicitSize + Tokens.padding.medium * 2
|
||||
color: Colors.palette.m3primaryContainer
|
||||
radius: Tokens.rounding.full
|
||||
|
||||
LoadingIndicator {
|
||||
id: shape
|
||||
|
||||
anchors.centerIn: parent
|
||||
implicitSize: Math.round(300 / 5)
|
||||
containsIcon: true // This removes the pentagon, which is not centered
|
||||
}
|
||||
}
|
||||
|
||||
CustomText {
|
||||
text: qsTr("Loading lyrics...")
|
||||
color: Colors.palette.m3onSurfaceVariant
|
||||
font.pointSize: Tokens.font.size.normal
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: noLyrics
|
||||
|
||||
anchors.centerIn: parent
|
||||
asynchronous: true
|
||||
active: opacity > 0
|
||||
opacity: 0
|
||||
|
||||
sourceComponent: ColumnLayout {
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
MaterialIcon {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: "sentiment_sad"
|
||||
font.pointSize: Tokens.font.size.extraLarge
|
||||
color: Colors.palette.m3outline
|
||||
}
|
||||
|
||||
CustomText {
|
||||
text: qsTr("No lyrics found")
|
||||
color: Colors.palette.m3outline
|
||||
font.pointSize: Tokens.font.size.normal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CustomListView {
|
||||
id: lyrics
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: parent.height * root.fadeAmount / 2
|
||||
anchors.bottomMargin: parent.height * root.fadeAmount / 2
|
||||
|
||||
displayMarginBeginning: anchors.topMargin
|
||||
displayMarginEnd: anchors.bottomMargin
|
||||
|
||||
model: root.lyricList
|
||||
Component.onCompleted: {
|
||||
currentIndex = Qt.binding(() => {
|
||||
model; // Force update when lyrics change
|
||||
return Lyrics.indexForTime(Players.active?.position ?? 0);
|
||||
});
|
||||
positionViewAtIndex(currentIndex, ListView.Center);
|
||||
}
|
||||
onModelChanged: Qt.callLater(() => positionViewAtIndex(currentIndex, ListView.Center))
|
||||
|
||||
highlightRangeMode: ListView.ApplyRange
|
||||
highlightMoveDuration: Tokens.anim.durations.large
|
||||
highlightMoveVelocity: -1
|
||||
preferredHighlightBegin: (height - (currentItem?.implicitHeight ?? 0)) / 2
|
||||
preferredHighlightEnd: (height + (currentItem?.implicitHeight ?? 0)) / 2
|
||||
|
||||
spacing: Tokens.spacing.small
|
||||
opacity: 0
|
||||
enabled: opacity > 0
|
||||
|
||||
delegate: CustomText {
|
||||
id: lyric
|
||||
|
||||
required property string modelData
|
||||
required property int index
|
||||
property real effectScale: ListView.isCurrentItem ? 1 : 0
|
||||
|
||||
anchors.left: lyrics.contentItem.left
|
||||
anchors.right: lyrics.contentItem.right
|
||||
|
||||
text: modelData || ". . ."
|
||||
color: ListView.isCurrentItem ? Colors.palette.m3primary : mouse.containsMouse ? Colors.palette.m3onSurface : Colors.palette.m3outline
|
||||
font.pointSize: Tokens.font.size.medium
|
||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||
|
||||
layer.enabled: effectScale > 0
|
||||
layer.effect: MultiEffect {
|
||||
shadowEnabled: true
|
||||
shadowColor: Colors.palette.m3primary
|
||||
shadowOpacity: 0.5 * lyric.effectScale
|
||||
shadowBlur: 0.6 * lyric.effectScale
|
||||
blur: 0.4 * lyric.effectScale
|
||||
}
|
||||
|
||||
Behavior on effectScale {
|
||||
Anim {
|
||||
type: Anim.SlowEffects
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouse
|
||||
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
const p = Players.active;
|
||||
if (p)
|
||||
p.position = Lyrics.timeForIndex(lyric.index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
type: Anim.SlowEffects
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on lyricList {
|
||||
SequentialAnimation {
|
||||
Anim {
|
||||
target: lyrics
|
||||
property: "opacity"
|
||||
to: 0
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
PropertyAction {}
|
||||
Anim {
|
||||
target: lyrics
|
||||
property: "opacity"
|
||||
to: 1
|
||||
type: Anim.SlowEffects
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Services.Mpris
|
||||
import ZShell.Config
|
||||
import qs.Components
|
||||
import qs.Helpers
|
||||
import qs.Services
|
||||
|
||||
Item {
|
||||
ColumnLayout {
|
||||
id: layout
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Tokens.padding.medium
|
||||
spacing: Tokens.spacing.medium
|
||||
|
||||
RowLayout {
|
||||
Layout.bottomMargin: -Tokens.spacing.medium
|
||||
spacing: Tokens.spacing.medium
|
||||
z: 1
|
||||
|
||||
MaterialIcon {
|
||||
Layout.topMargin: Math.round(fontInfo.pointSize * 0.12)
|
||||
text: "lyrics"
|
||||
font.pointSize: Tokens.font.size.large
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Lyrics")
|
||||
font.pointSize: Tokens.font.size.large
|
||||
}
|
||||
|
||||
LyricsInfo {}
|
||||
}
|
||||
|
||||
LyricList {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
|
||||
SplitButton {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
type: SplitButton.Tonal
|
||||
enabled: Players.list.length
|
||||
active: menuItems.find(m => m.modelData === Players.active) ?? menuItems[0] ?? null
|
||||
menu.onItemSelected: item => Players.manualActive = (item as PlayerItem).modelData
|
||||
|
||||
menuItems: playerList.instances
|
||||
fallbackIcon: "music_off"
|
||||
fallbackText: qsTr("No players", "no media players active")
|
||||
|
||||
minLeftWidth: layout.width - expandBtn.implicitWidth - spacing
|
||||
label.Layout.maximumWidth: minLeftWidth - iconLabel.implicitWidth - textRow.spacing - textRow.anchors.horizontalCenterOffset / 2 - horizontalPadding * 2
|
||||
label.elide: Text.ElideRight
|
||||
|
||||
stateLayer.enabled: true
|
||||
menuOnTop: true
|
||||
|
||||
Variants {
|
||||
id: playerList
|
||||
|
||||
model: Players.list
|
||||
|
||||
PlayerItem {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component PlayerItem: MenuItem {
|
||||
required property MprisPlayer modelData
|
||||
|
||||
icon: modelData === Players.active ? "check" : ""
|
||||
text: Players.getIdentity(modelData)
|
||||
activeIcon: "animated_images"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import ZShell
|
||||
import ZShell.Blobs
|
||||
import ZShell.Config
|
||||
import ZShell.Services
|
||||
import qs.Components
|
||||
import qs.Services
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property bool open
|
||||
readonly property real padding: Tokens.padding.large
|
||||
|
||||
implicitWidth: btn.implicitWidth * 0.9
|
||||
implicitHeight: btn.implicitHeight * 0.9
|
||||
|
||||
BlobGroup {
|
||||
id: blobGroup
|
||||
|
||||
color: Colors.palette.m3surfaceContainerHighest
|
||||
smoothing: Tokens.rounding.medium
|
||||
cornerFill: false
|
||||
|
||||
Behavior on color {
|
||||
CAnim {}
|
||||
}
|
||||
}
|
||||
|
||||
BlobRect {
|
||||
id: btnRect
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: !btn.pressed && btn.containsMouse ? -Tokens.padding.extraSmall : 0
|
||||
group: blobGroup
|
||||
radius: Tokens.rounding.medium
|
||||
|
||||
Behavior on anchors.margins {
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
|
||||
BlobRect {
|
||||
id: rect
|
||||
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
|
||||
implicitWidth: parent.width
|
||||
implicitHeight: parent.height
|
||||
|
||||
group: blobGroup
|
||||
radius: Tokens.rounding.medium
|
||||
deformScale: 0.00001
|
||||
|
||||
states: State {
|
||||
name: "open"
|
||||
when: root.open
|
||||
|
||||
PropertyChanges {
|
||||
rect.anchors.rightMargin: root.width - Tokens.spacing.small
|
||||
rect.anchors.topMargin: -Tokens.padding.medium
|
||||
rect.implicitWidth: Math.max(layout.implicitWidth, placeholder.implicitWidth) + root.padding * 2
|
||||
rect.implicitHeight: Math.max(layout.implicitHeight, placeholder.implicitHeight) + root.padding * 2
|
||||
content.opacity: 1
|
||||
}
|
||||
}
|
||||
|
||||
transitions: Transition {
|
||||
Anim {
|
||||
properties: "rightMargin,implicitWidth"
|
||||
}
|
||||
Anim {
|
||||
properties: "topMargin,implicitHeight"
|
||||
easing: Tokens.anim.expressiveFastSpatial
|
||||
}
|
||||
Anim {
|
||||
property: "opacity"
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on implicitWidth {
|
||||
Anim {}
|
||||
}
|
||||
|
||||
Behavior on implicitHeight {
|
||||
Anim {}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: content
|
||||
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
opacity: 0
|
||||
state: Lyrics.loading || !Lyrics.hasLyrics ? "" : "hasLyrics"
|
||||
|
||||
states: State {
|
||||
name: "hasLyrics"
|
||||
|
||||
PropertyChanges {
|
||||
layout.opacity: 1
|
||||
placeholder.opacity: 0
|
||||
}
|
||||
}
|
||||
|
||||
transitions: [
|
||||
Transition {
|
||||
from: "hasLyrics"
|
||||
|
||||
SequentialAnimation {
|
||||
Anim {
|
||||
target: layout
|
||||
property: "opacity"
|
||||
type: Anim.FastEffects
|
||||
}
|
||||
Anim {
|
||||
target: placeholder
|
||||
property: "opacity"
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
},
|
||||
Transition {
|
||||
to: "hasLyrics"
|
||||
|
||||
SequentialAnimation {
|
||||
Anim {
|
||||
target: placeholder
|
||||
property: "opacity"
|
||||
type: Anim.FastEffects
|
||||
}
|
||||
Anim {
|
||||
target: layout
|
||||
property: "opacity"
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
ColumnLayout {
|
||||
id: layout
|
||||
|
||||
anchors.centerIn: parent
|
||||
spacing: Tokens.spacing.extraSmall
|
||||
opacity: 0
|
||||
|
||||
CustomText {
|
||||
// TRANSLATORS: %1 = lyrics backend name, e.g. LRCLIB
|
||||
text: qsTr("Backend: %1").arg(ZUtils.enumToString(Lyrics, "backend"))
|
||||
color: Colors.palette.m3onSurfaceVariant
|
||||
animate: true
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.maximumWidth: 1000 / 2
|
||||
// TRANSLATORS: %1/%2/%3 = matched track title, artist and album
|
||||
text: qsTr("Selected candidate: %1 | %2 | %3").arg(Lyrics.selectedCandidate.title).arg(Lyrics.selectedCandidate.artist).arg(Lyrics.selectedCandidate.album)
|
||||
color: Colors.palette.m3onSurfaceVariant
|
||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||
animate: true
|
||||
}
|
||||
|
||||
CustomText {
|
||||
// TRANSLATORS: %1 = lyric timing offset; ms is the millisecond unit, leave untranslated
|
||||
text: qsTr("Offset: %1 ms").arg(Lyrics.offset)
|
||||
color: Colors.palette.m3onSurfaceVariant
|
||||
animate: true
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: placeholder
|
||||
|
||||
anchors.centerIn: parent
|
||||
implicitWidth: placeholderText.implicitWidth
|
||||
implicitHeight: placeholderText.implicitHeight
|
||||
|
||||
CustomText {
|
||||
id: placeholderText
|
||||
|
||||
text: Lyrics.loading ? qsTr("Loading...") : qsTr("No lyrics found")
|
||||
color: Colors.palette.m3onSurfaceVariant
|
||||
font.pointSize: Tokens.font.size.normal
|
||||
animate: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: btn
|
||||
|
||||
anchors.centerIn: parent
|
||||
implicitWidth: implicitHeight
|
||||
implicitHeight: icon.implicitHeight + Tokens.padding.extraSmall * 2
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
hoverEnabled: true
|
||||
onClicked: root.open = !root.open
|
||||
|
||||
MaterialIcon {
|
||||
id: icon
|
||||
|
||||
anchors.centerIn: parent
|
||||
text: "more_vert"
|
||||
font.pointSize: Tokens.font.size.large
|
||||
}
|
||||
}
|
||||
}
|
||||
+90
-144
@@ -1,23 +1,24 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import qs.Components
|
||||
import ZShell.Config
|
||||
import qs.Helpers
|
||||
import qs.Modules
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Templates
|
||||
import Quickshell
|
||||
import ZShell.Config
|
||||
import qs.Components
|
||||
import qs.Services
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
readonly property alias count: bar.count
|
||||
required property real nonAnimWidth
|
||||
required property PersistentProperties state
|
||||
required property ScreenState screenState
|
||||
required property var tabs
|
||||
|
||||
implicitHeight: bar.implicitHeight + indicator.implicitHeight + indicator.anchors.topMargin + separator.implicitHeight
|
||||
readonly property alias count: bar.count
|
||||
readonly property int tabIndicatorSpacing: Tokens.spacing.small
|
||||
|
||||
implicitHeight: bar.implicitHeight + bar.anchors.topMargin + indicator.implicitHeight + indicator.anchors.topMargin + separator.implicitHeight
|
||||
|
||||
TabBar {
|
||||
id: bar
|
||||
@@ -25,85 +26,99 @@ Item {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: root.tabIndicatorSpacing
|
||||
|
||||
currentIndex: root.screenState.dashboardTab
|
||||
onCurrentIndexChanged: root.screenState.dashboardTab = currentIndex
|
||||
|
||||
implicitHeight: contentHeight
|
||||
background: null
|
||||
currentIndex: root.state.currentTab
|
||||
contentItem: RowLayout {
|
||||
spacing: 0
|
||||
|
||||
onCurrentIndexChanged: root.state.currentTab = currentIndex
|
||||
|
||||
Tab {
|
||||
iconName: "dashboard"
|
||||
text: qsTr("Dashboard")
|
||||
Repeater {
|
||||
model: bar.contentModel
|
||||
}
|
||||
}
|
||||
|
||||
Tab {
|
||||
iconName: "queue_music"
|
||||
text: qsTr("Media")
|
||||
}
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
values: root.tabs
|
||||
}
|
||||
|
||||
Tab {
|
||||
iconName: "speed"
|
||||
text: qsTr("Performance")
|
||||
}
|
||||
delegate: Tab {
|
||||
required property var modelData
|
||||
|
||||
Tab {
|
||||
iconName: "cloud"
|
||||
text: qsTr("Weather")
|
||||
iconName: modelData.iconName
|
||||
text: modelData.text
|
||||
}
|
||||
}
|
||||
|
||||
// Tab {
|
||||
// iconName: "workspaces"
|
||||
// text: qsTr("Workspaces")
|
||||
// }
|
||||
}
|
||||
|
||||
Item {
|
||||
id: indicator
|
||||
|
||||
anchors.top: bar.bottom
|
||||
clip: true
|
||||
implicitHeight: 40
|
||||
implicitWidth: bar.currentItem.implicitWidth
|
||||
anchors.topMargin: root.tabIndicatorSpacing
|
||||
|
||||
implicitWidth: {
|
||||
const tab = bar.currentItem;
|
||||
if (tab)
|
||||
return tab.implicitWidth;
|
||||
const width = (root.nonAnimWidth - bar.spacing * (bar.count - 1)) / bar.count;
|
||||
return width;
|
||||
}
|
||||
implicitHeight: 3
|
||||
|
||||
x: {
|
||||
const tab = bar.currentItem;
|
||||
const width = (root.nonAnimWidth - bar.spacing * (bar.count - 1)) / bar.count;
|
||||
return width * tab.TabBar.index + (width - tab.implicitWidth) / 2;
|
||||
const tabWidth = tab?.implicitWidth ?? width;
|
||||
return width * bar.currentIndex + (width - tabWidth) / 2;
|
||||
}
|
||||
|
||||
clip: true
|
||||
|
||||
CustomRect {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
implicitHeight: parent.implicitHeight * 2
|
||||
|
||||
color: Colors.palette.m3primary
|
||||
radius: Tokens.rounding.full
|
||||
}
|
||||
|
||||
Behavior on x {
|
||||
Anim {}
|
||||
}
|
||||
|
||||
Behavior on implicitWidth {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
Behavior on x {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
|
||||
CustomRect {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
color: Colors.palette.m3primary
|
||||
implicitHeight: parent.implicitHeight * 2
|
||||
radius: Tokens.rounding.full
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
|
||||
CustomRect {
|
||||
id: separator
|
||||
|
||||
anchors.top: indicator.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: indicator.bottom
|
||||
color: Colors.palette.m3outlineVariant
|
||||
|
||||
implicitHeight: 1
|
||||
color: Colors.palette.m3outlineVariant
|
||||
}
|
||||
|
||||
component Tab: TabButton {
|
||||
id: tab
|
||||
|
||||
readonly property bool current: TabBar.tabBar.currentItem === this
|
||||
required property string iconName
|
||||
readonly property bool current: TabBar.tabBar.currentItem === this
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredWidth: 1 // Uniform width across all tabs
|
||||
implicitWidth: implicitContentWidth
|
||||
implicitHeight: implicitContentHeight
|
||||
background: null
|
||||
|
||||
contentItem: CustomMouseArea {
|
||||
@@ -111,117 +126,47 @@ Item {
|
||||
|
||||
function onWheel(event: WheelEvent): void {
|
||||
if (event.angleDelta.y < 0)
|
||||
root.state.currentTab = Math.min(root.state.currentTab + 1, bar.count - 1);
|
||||
root.screenState.dashboardTab = Math.min(root.screenState.dashboardTab + 1, bar.count - 1);
|
||||
else if (event.angleDelta.y > 0)
|
||||
root.state.currentTab = Math.max(root.state.currentTab - 1, 0);
|
||||
root.screenState.dashboardTab = Math.max(root.screenState.dashboardTab - 1, 0);
|
||||
}
|
||||
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
implicitHeight: icon.height + label.height
|
||||
implicitWidth: Math.max(icon.width, label.width)
|
||||
implicitHeight: icon.height + label.height
|
||||
|
||||
onPressed: event => {
|
||||
root.state.currentTab = tab.TabBar.index;
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
const stateY = stateWrapper.y;
|
||||
rippleAnim.x = event.x;
|
||||
rippleAnim.y = event.y - stateY;
|
||||
onPressed: root.screenState.dashboardTab = tab.TabBar.index
|
||||
|
||||
const dist = (ox, oy) => ox * ox + oy * oy;
|
||||
rippleAnim.radius = Math.sqrt(Math.max(dist(event.x, event.y + stateY), dist(event.x, stateWrapper.height - event.y), dist(width - event.x, event.y + stateY), dist(width - event.x, stateWrapper.height - event.y)));
|
||||
|
||||
rippleAnim.restart();
|
||||
}
|
||||
|
||||
SequentialAnimation {
|
||||
id: rippleAnim
|
||||
|
||||
property real radius
|
||||
property real x
|
||||
property real y
|
||||
|
||||
PropertyAction {
|
||||
property: "x"
|
||||
target: ripple
|
||||
value: rippleAnim.x
|
||||
}
|
||||
|
||||
PropertyAction {
|
||||
property: "y"
|
||||
target: ripple
|
||||
value: rippleAnim.y
|
||||
}
|
||||
|
||||
PropertyAction {
|
||||
property: "opacity"
|
||||
target: ripple
|
||||
value: 0.08
|
||||
}
|
||||
|
||||
Anim {
|
||||
from: 0
|
||||
properties: "implicitWidth,implicitHeight"
|
||||
target: ripple
|
||||
to: rippleAnim.radius * 2
|
||||
}
|
||||
|
||||
Anim {
|
||||
easing.type: Easing.BezierSpline
|
||||
property: "opacity"
|
||||
target: ripple
|
||||
to: 0
|
||||
}
|
||||
}
|
||||
|
||||
ClippingRectangle {
|
||||
id: stateWrapper
|
||||
StateLayer {
|
||||
id: stateLayer
|
||||
|
||||
anchors.fill: undefined
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: "transparent"
|
||||
implicitHeight: parent.height + 8 * 2
|
||||
radius: Tokens.rounding.small
|
||||
implicitHeight: parent.height + root.tabIndicatorSpacing * 2
|
||||
|
||||
CustomRect {
|
||||
id: stateLayer
|
||||
|
||||
anchors.fill: parent
|
||||
color: tab.current ? Colors.palette.m3primary : Colors.palette.m3onSurface
|
||||
opacity: mouse.pressed ? 0.1 : tab.hovered ? 0.08 : 0
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CustomRect {
|
||||
id: ripple
|
||||
|
||||
color: tab.current ? Colors.palette.m3primary : Colors.palette.m3onSurface
|
||||
opacity: 0
|
||||
radius: Tokens.rounding.full
|
||||
|
||||
transform: Translate {
|
||||
x: -ripple.width / 2
|
||||
y: -ripple.height / 2
|
||||
}
|
||||
}
|
||||
radius: Tokens.rounding.medium
|
||||
color: tab.current ? Colors.palette.m3primary : Colors.palette.m3onSurface
|
||||
onClicked: root.screenState.dashboardTab = tab.TabBar.index
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
id: icon
|
||||
|
||||
anchors.bottom: label.top
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: label.top
|
||||
|
||||
text: tab.iconName
|
||||
color: tab.current ? Colors.palette.m3primary : Colors.palette.m3onSurfaceVariant
|
||||
fill: tab.current ? 1 : 0
|
||||
font.pointSize: 18
|
||||
text: tab.iconName
|
||||
font.pointSize: Tokens.font.size.large
|
||||
|
||||
Behavior on fill {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -229,10 +174,11 @@ Item {
|
||||
CustomText {
|
||||
id: label
|
||||
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: tab.current ? Colors.palette.m3primary : Colors.palette.m3onSurfaceVariant
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
text: tab.text
|
||||
color: tab.current ? Colors.palette.m3primary : Colors.palette.m3onSurfaceVariant
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import ZShell.Config
|
||||
import qs.Components
|
||||
import qs.Services
|
||||
import qs.Helpers
|
||||
|
||||
RowLayout {
|
||||
id: root
|
||||
|
||||
spacing: Tokens.spacing.largeIncreased
|
||||
|
||||
readonly property list<var> forecastToday: Weather.hourlyForecast.slice(0, 25)
|
||||
|
||||
property var now: new Date()
|
||||
property bool useTwelveHourClock: Config.services.useTwelveHourClock
|
||||
property bool curveSmoothing: true
|
||||
property real smoothing: 2
|
||||
property real markerGap: Math.min(Math.max(Tokens.spacing.medium / 2, 0), 6)
|
||||
|
||||
readonly property real markerX: {
|
||||
const d = root.now;
|
||||
return d.getHours() + d.getMinutes() / 60;
|
||||
}
|
||||
|
||||
function formatHour(h) {
|
||||
if (root.useTwelveHourClock) {
|
||||
const hr = h % 12 === 0 ? 12 : h % 12;
|
||||
return hr + (h < 12 ? " AM" : " PM");
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 60000
|
||||
repeat: true
|
||||
running: true
|
||||
|
||||
onTriggered: root.now = new Date()
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredWidth: root.width / 2
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
MaterialIcon {
|
||||
text: "device_thermostat"
|
||||
font.pointSize: Tokens.font.size.large
|
||||
color: Colors.palette.m3secondary
|
||||
}
|
||||
|
||||
CustomText {
|
||||
text: qsTr("Temperature")
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: temperature
|
||||
|
||||
implicitHeight: 150
|
||||
Layout.fillWidth: true
|
||||
|
||||
LineGraph {
|
||||
id: tempGraph
|
||||
|
||||
anchors.fill: parent
|
||||
values: root.forecastToday.map(e => e.tempC)
|
||||
verticalLineStep: 3
|
||||
xSteps: 24
|
||||
horizontalLineStep: 10
|
||||
minHorizontalLines: 4
|
||||
yPaddingTop: horizontalLineStep
|
||||
yPaddingBottom: horizontalLineStep
|
||||
xLabel: i => root.formatHour(i)
|
||||
yLabel: v => Math.round(v) + "°"
|
||||
|
||||
markerX: root.markerX
|
||||
smoothing: root.smoothing
|
||||
curveSmoothing: root.curveSmoothing
|
||||
markerGap: root.markerGap
|
||||
|
||||
lineColor: Colors.palette.m3secondary
|
||||
gridColor: Colors.palette.m3onSurface
|
||||
markerColor: Colors.palette.m3secondary
|
||||
labelColor: Colors.palette.m3onSurfaceVariant
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredWidth: root.width / 2
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
MaterialIcon {
|
||||
text: "water_drop"
|
||||
font.pointSize: Tokens.font.size.large
|
||||
color: Colors.palette.m3tertiary
|
||||
}
|
||||
|
||||
CustomText {
|
||||
text: qsTr("Chance of precipitation")
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: precipitation
|
||||
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: 150
|
||||
|
||||
LineGraph {
|
||||
id: graph
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
values: root.forecastToday.map(e => e?.precipChance ?? null)
|
||||
xSteps: 24
|
||||
yMin: 0
|
||||
yMax: 100
|
||||
yPaddingTop: 20
|
||||
verticalLineStep: 3
|
||||
lastXLine: false
|
||||
horizontalLineStep: 20
|
||||
xLabel: i => root.formatHour(i)
|
||||
yLabel: v => v + "%"
|
||||
|
||||
markerX: root.markerX
|
||||
smoothing: root.smoothing
|
||||
curveSmoothing: root.curveSmoothing
|
||||
markerGap: root.markerGap
|
||||
|
||||
lineColor: Colors.palette.m3tertiary
|
||||
gridColor: Colors.palette.m3onSurface
|
||||
markerColor: Colors.palette.m3tertiary
|
||||
labelColor: Colors.palette.m3onSurfaceVariant
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import ZShell.Config
|
||||
import qs.Components
|
||||
import qs.Modules.Dashboard.Weather
|
||||
import qs.Services
|
||||
import qs.Helpers
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
readonly property var today: Weather.forecast && Weather.forecast.length > 0 ? Weather.forecast[0] : null
|
||||
|
||||
implicitWidth: layout.implicitWidth > 800 ? layout.implicitWidth : 840
|
||||
implicitHeight: layout.implicitHeight
|
||||
Component.onCompleted: Weather.reload()
|
||||
|
||||
ColumnLayout {
|
||||
id: layout
|
||||
|
||||
anchors.fill: parent
|
||||
spacing: Tokens.spacing.medium
|
||||
|
||||
RowLayout {
|
||||
Layout.leftMargin: Tokens.padding.large
|
||||
Layout.rightMargin: Tokens.padding.large
|
||||
Layout.fillWidth: true
|
||||
|
||||
Column {
|
||||
spacing: Tokens.spacing.extraSmall
|
||||
|
||||
CustomText {
|
||||
text: Weather.city || qsTr("Loading...")
|
||||
font.pointSize: Tokens.font.size.large
|
||||
color: Colors.palette.m3onSurface
|
||||
}
|
||||
|
||||
CustomText {
|
||||
text: new Date().toLocaleDateString(Qt.locale("en_US"), "dddd, MMMM d")
|
||||
font.pointSize: Tokens.font.size.small
|
||||
color: Colors.palette.m3onSurfaceVariant
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: Tokens.spacing.largeIncreased
|
||||
|
||||
WeatherStat {
|
||||
icon: "wb_twilight"
|
||||
label: qsTr("Sunrise")
|
||||
value: Weather.sunrise
|
||||
colour: Colors.palette.m3tertiary
|
||||
}
|
||||
|
||||
WeatherStat {
|
||||
icon: "bedtime"
|
||||
label: qsTr("Sunset")
|
||||
value: Weather.sunset
|
||||
colour: Colors.palette.m3tertiary
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CustomRect {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: bigInfoRow.implicitHeight + bigInfoRow.anchors.margins * 2
|
||||
|
||||
radius: Tokens.rounding.large
|
||||
color: Colors.tPalette.m3surfaceContainer
|
||||
|
||||
Graphs {
|
||||
id: bigInfoRow
|
||||
anchors.fill: parent
|
||||
anchors.margins: Tokens.padding.medium
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Tokens.spacing.medium
|
||||
|
||||
DetailCard {
|
||||
icon: Weather.icon
|
||||
label: qsTr("Temperature")
|
||||
value: Weather.temp
|
||||
colour: Colors.palette.m3primary
|
||||
}
|
||||
DetailCard {
|
||||
icon: "thermostat"
|
||||
label: qsTr("Feels like", "apparent temperature")
|
||||
value: Weather.feelsLike
|
||||
colour: Colors.palette.m3primary
|
||||
}
|
||||
DetailCard {
|
||||
icon: "water_drop"
|
||||
label: qsTr("Humidity")
|
||||
value: Strings.percent(Weather.humidity)
|
||||
colour: Colors.palette.m3secondary
|
||||
}
|
||||
DetailCard {
|
||||
icon: "air"
|
||||
label: qsTr("Wind")
|
||||
value: Weather.windSpeed ? qsTr("%1 km/h").arg(Weather.windSpeed) : "--"
|
||||
colour: Colors.palette.m3tertiary
|
||||
}
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.topMargin: Tokens.spacing.medium
|
||||
Layout.leftMargin: Tokens.padding.medium
|
||||
visible: forecastRepeater.count > 0
|
||||
text: qsTr("7-day forecast")
|
||||
font.pointSize: Tokens.font.size.normal
|
||||
color: Colors.palette.m3onSurface
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Tokens.spacing.medium
|
||||
|
||||
Repeater {
|
||||
id: forecastRepeater
|
||||
|
||||
model: Weather.forecast
|
||||
|
||||
CustomRect {
|
||||
id: forecastItem
|
||||
|
||||
required property int index
|
||||
required property var modelData
|
||||
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: forecastItemColumn.implicitHeight + Tokens.padding.medium * 2
|
||||
|
||||
radius: Tokens.rounding.large
|
||||
color: Colors.tPalette.m3surfaceContainer
|
||||
|
||||
ColumnLayout {
|
||||
id: forecastItemColumn
|
||||
|
||||
anchors.centerIn: parent
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
CustomText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: forecastItem.index === 0 ? qsTr("Today", "forecast column") : new Date(forecastItem.modelData.date).toLocaleDateString(Qt.locale("en_US"), "ddd")
|
||||
font.pointSize: Tokens.font.size.small
|
||||
color: Colors.palette.m3primary
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.topMargin: -Tokens.spacing.extraSmall
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: new Date(forecastItem.modelData.date).toLocaleDateString(Qt.locale("en_US"), "MMM d")
|
||||
font.pointSize: Tokens.font.size.small
|
||||
opacity: 0.7
|
||||
color: Colors.palette.m3onSurfaceVariant
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: forecastItem.modelData.icon
|
||||
font.pointSize: Tokens.font.size.extraLarge
|
||||
color: Colors.palette.m3secondary
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: {
|
||||
const min = Weather.formatTemp(forecastItem.modelData.minTempC, true);
|
||||
const max = Weather.formatTemp(forecastItem.modelData.maxTempC, true);
|
||||
return qsTr("%1 / %2", "min/max temperature").arg(min).arg(max);
|
||||
}
|
||||
font.pointSize: Tokens.font.size.small
|
||||
color: Colors.palette.m3tertiary
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component DetailCard: CustomRect {
|
||||
id: detailRoot
|
||||
|
||||
property string icon
|
||||
property string label
|
||||
property string value
|
||||
property color colour
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 60
|
||||
radius: Tokens.rounding.medium
|
||||
color: Colors.tPalette.m3surfaceContainer
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: Tokens.spacing.medium
|
||||
|
||||
MaterialIcon {
|
||||
text: detailRoot.icon
|
||||
color: detailRoot.colour
|
||||
font.pointSize: Tokens.font.size.large
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 0
|
||||
|
||||
CustomText {
|
||||
text: detailRoot.label
|
||||
font.pointSize: Tokens.font.size.small
|
||||
opacity: 0.7
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
CustomText {
|
||||
text: detailRoot.value
|
||||
font.pointSize: Tokens.font.size.small
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component WeatherStat: Row {
|
||||
id: weatherStat
|
||||
|
||||
property string icon
|
||||
property string label
|
||||
property string value
|
||||
property color colour
|
||||
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
MaterialIcon {
|
||||
text: weatherStat.icon
|
||||
font.pointSize: Tokens.font.size.extraLarge
|
||||
color: weatherStat.colour
|
||||
}
|
||||
|
||||
Column {
|
||||
CustomText {
|
||||
text: weatherStat.label
|
||||
font.pointSize: Tokens.font.size.small
|
||||
color: Colors.palette.m3onSurfaceVariant
|
||||
}
|
||||
CustomText {
|
||||
text: weatherStat.value
|
||||
font.pointSize: Tokens.font.size.small
|
||||
color: Colors.palette.m3onSurface
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,24 +8,13 @@ import ZShell.Config
|
||||
Item {
|
||||
id: root
|
||||
|
||||
readonly property PersistentProperties dashState: PersistentProperties {
|
||||
property date currentDate: new Date()
|
||||
property int currentTab
|
||||
|
||||
reloadableId: "dashboardState"
|
||||
}
|
||||
readonly property real nonAnimHeight: state === "visible" ? (content.item?.nonAnimHeight ?? 0) : 0
|
||||
required property real offsetScale
|
||||
required property string position
|
||||
readonly property bool shouldBeActive: root.visibilities.dashboard && Config.dashboard.enabled
|
||||
required property PersistentProperties visibilities
|
||||
readonly property bool shouldBeActive: root.screenState.dashboard && Config.dashboard.enabled
|
||||
required property ScreenState screenState
|
||||
|
||||
anchors.bottom: position === "bottom" ? parent.bottom : undefined
|
||||
anchors.bottomMargin: position === "bottom" ? (-implicitHeight - 5) * offsetScale : 0
|
||||
anchors.left: position === "left" ? parent.left : undefined
|
||||
anchors.leftMargin: position === "left" ? (-implicitWidth - 5) * offsetScale : 0
|
||||
anchors.top: position === "top" ? parent.top : undefined
|
||||
anchors.topMargin: position === "top" ? (-implicitHeight - 5) * offsetScale : 0
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: (-implicitHeight - 5) * offsetScale
|
||||
implicitHeight: content.implicitHeight
|
||||
implicitWidth: content.implicitWidth || 854
|
||||
opacity: 1 - offsetScale
|
||||
@@ -35,15 +24,11 @@ Item {
|
||||
id: content
|
||||
|
||||
active: root.shouldBeActive || root.visible
|
||||
anchors.bottom: root.position === "top" ? parent.bottom : undefined
|
||||
anchors.horizontalCenter: root.position !== "left" ? parent.horizontalCenter : undefined
|
||||
anchors.right: root.position !== "left" ? undefined : parent.right
|
||||
anchors.top: root.position === "bottom" ? parent.top : undefined
|
||||
anchors.verticalCenter: root.position === "left" ? undefined : parent.verticalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
sourceComponent: Content {
|
||||
dashState: root.dashState
|
||||
visibilities: root.visibilities
|
||||
screenState: root.screenState
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import ZShell.Config
|
||||
import qs.Paths
|
||||
import qs.Helpers
|
||||
import qs.Services
|
||||
import qs.Services
|
||||
|
||||
Item {
|
||||
id: root
|
||||
@@ -97,8 +98,8 @@ Item {
|
||||
anchors.fill: parent
|
||||
|
||||
onClicked: {
|
||||
const visibilities = Visibilities.getForActive();
|
||||
visibilities.settings = true;
|
||||
const state = ShellState.forActive();
|
||||
state.settings = true;
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ Item {
|
||||
property var pendingCommitIds: []
|
||||
readonly property int rounding: Tokens.rounding.largeIncreased
|
||||
required property ShellScreen screen
|
||||
required property PersistentProperties visibilities
|
||||
required property ScreenState screenState
|
||||
property var visualIds: []
|
||||
|
||||
function beginVisualDrag(appId, modelData, item) {
|
||||
@@ -198,7 +198,7 @@ Item {
|
||||
anchors.centerIn: parent
|
||||
appListRoot: root
|
||||
appToplevel: modelData
|
||||
visibilities: root.visibilities
|
||||
screenState: root.screenState
|
||||
visible: root.draggedAppId !== slot.appId
|
||||
}
|
||||
|
||||
@@ -314,7 +314,7 @@ Item {
|
||||
appListRoot: root
|
||||
appToplevel: root.draggedModelData
|
||||
enabled: false
|
||||
visibilities: root.visibilities
|
||||
screenState: root.screenState
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ CustomRect {
|
||||
property real iconSize: implicitHeight - 20
|
||||
readonly property bool isSeparator: appToplevel?.appId === "__dock_separator__"
|
||||
property int lastFocused: -1
|
||||
required property PersistentProperties visibilities
|
||||
required property ScreenState screenState
|
||||
|
||||
implicitHeight: Config.dock.height
|
||||
implicitWidth: isSeparator ? 1 : implicitHeight
|
||||
@@ -62,12 +62,12 @@ CustomRect {
|
||||
onClicked: {
|
||||
if (appToplevel?.toplevels.length === 0) {
|
||||
root.desktopEntry?.execute();
|
||||
root.visibilities.dock = false;
|
||||
root.screenState.dock = false;
|
||||
return;
|
||||
}
|
||||
lastFocused = (lastFocused + 1) % appToplevel?.toplevels.length;
|
||||
appToplevel?.toplevels[lastFocused].activate();
|
||||
root.visibilities.dock = false;
|
||||
root.screenState.dock = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@ Item {
|
||||
property real offsetScale: shouldBeActive ? 0 : 1
|
||||
required property var panels
|
||||
required property ShellScreen screen
|
||||
readonly property bool shouldBeActive: visibilities.dock && Config.dock.enable
|
||||
required property PersistentProperties visibilities
|
||||
readonly property bool shouldBeActive: screenState.dock && Config.dock.enable
|
||||
required property ScreenState screenState
|
||||
|
||||
anchors.bottomMargin: (-implicitHeight - 5) * offsetScale
|
||||
implicitHeight: content.implicitHeight
|
||||
@@ -39,7 +39,7 @@ Item {
|
||||
sourceComponent: Content {
|
||||
panels: root.panels
|
||||
screen: root.screen
|
||||
visibilities: root.visibilities
|
||||
screenState: root.screenState
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ Item {
|
||||
readonly property var colors1: ["#ef4444", "#f97316", "#eab308", "#22c55e", "#06b6d4"]
|
||||
readonly property var colors2: ["#3b82f6", "#a855f7", "#ec4899", "#ffffff", "#000000"]
|
||||
required property var drawing
|
||||
required property var visibilities
|
||||
required property ScreenState screenState
|
||||
required property Wrapper wrapper
|
||||
|
||||
function syncFromPenColor() {
|
||||
@@ -20,17 +20,17 @@ Item {
|
||||
return;
|
||||
|
||||
if (!saturationSlider.pressed)
|
||||
saturationSlider.value = drawing.drawingState.penColor.hsvSaturation;
|
||||
saturationSlider.value = drawing.screenState.penColor.hsvSaturation;
|
||||
|
||||
if (!brightnessSlider.pressed)
|
||||
brightnessSlider.value = drawing.drawingState.penColor.hsvValue;
|
||||
brightnessSlider.value = drawing.screenState.penColor.hsvValue;
|
||||
}
|
||||
|
||||
function updatePenColorFromHsv() {
|
||||
if (!drawing)
|
||||
return;
|
||||
|
||||
drawing.drawingState.penColor = Qt.hsva(huePicker.currentHue, saturationSlider.value, brightnessSlider.value, drawing.drawingState.penColor.a);
|
||||
drawing.screenState.penColor = Qt.hsva(huePicker.currentHue, saturationSlider.value, brightnessSlider.value, drawing.screenState.penColor.a);
|
||||
}
|
||||
|
||||
implicitHeight: column.height + Tokens.padding.small * 2
|
||||
@@ -43,7 +43,7 @@ Item {
|
||||
root.syncFromPenColor();
|
||||
}
|
||||
|
||||
target: root.drawing.drawingState
|
||||
target: root.drawing.screenState
|
||||
}
|
||||
|
||||
Column {
|
||||
@@ -133,9 +133,9 @@ Item {
|
||||
multiplier: 1
|
||||
orientation: Qt.Horizontal
|
||||
to: 45
|
||||
value: root.drawing.drawingState.penWidth
|
||||
value: root.drawing.screenState.penWidth
|
||||
|
||||
onMoved: root.drawing.drawingState.penWidth = value
|
||||
onMoved: root.drawing.screenState.penWidth = value
|
||||
}
|
||||
|
||||
ButtonRow {
|
||||
@@ -153,7 +153,7 @@ Item {
|
||||
shapeMorph: true
|
||||
text: "Exit"
|
||||
|
||||
onClicked: root.visibilities.isDrawing = false
|
||||
onClicked: root.screenState.isDrawing = false
|
||||
}
|
||||
|
||||
IconTextButton {
|
||||
@@ -203,6 +203,6 @@ Item {
|
||||
shapeMorph: true
|
||||
shapeMorphExpansion: pressed ? 12 : 0
|
||||
|
||||
onClicked: root.drawing.drawingState.penColor = modelData
|
||||
onClicked: root.drawing.screenState.penColor = modelData
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@ Item {
|
||||
property real offsetScale: shouldBeActive ? 0 : 1
|
||||
property bool pinned: false
|
||||
required property ShellScreen screen
|
||||
readonly property bool shouldBeActive: visibilities.isDrawing
|
||||
required property var visibilities
|
||||
readonly property bool shouldBeActive: screenState.isDrawing
|
||||
required property ScreenState screenState
|
||||
|
||||
function collapse(): void {
|
||||
if (pinned)
|
||||
@@ -98,7 +98,7 @@ Item {
|
||||
}
|
||||
sourceComponent: Content {
|
||||
drawing: root.drawing
|
||||
visibilities: root.visibilities
|
||||
screenState: root.screenState
|
||||
wrapper: root
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ CustomListView {
|
||||
property string displayText
|
||||
readonly property string requestedState: stateForText(search.text)
|
||||
required property SearchBar search
|
||||
required property PersistentProperties visibilities
|
||||
required property ScreenState screenState
|
||||
|
||||
function resultsForText(text: string): var {
|
||||
switch (stateForText(text)) {
|
||||
@@ -44,7 +44,7 @@ CustomListView {
|
||||
}
|
||||
|
||||
function syncDisplayText(): void {
|
||||
if (visibilities.launcher && requestedState === displayState)
|
||||
if (screenState.launcher && requestedState === displayState)
|
||||
displayText = search.text;
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ CustomListView {
|
||||
preferredHighlightBegin: 0
|
||||
preferredHighlightEnd: height
|
||||
spacing: Tokens.spacing.small
|
||||
state: visibilities.launcher ? requestedState : displayState
|
||||
state: screenState.launcher ? requestedState : displayState
|
||||
verticalLayoutDirection: ListView.BottomToTop
|
||||
|
||||
CustomScrollBar.vertical: CustomScrollBar {
|
||||
@@ -236,7 +236,7 @@ CustomListView {
|
||||
id: appItem
|
||||
|
||||
AppItem {
|
||||
visibilities: root.visibilities
|
||||
screenState: root.screenState
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,6 +277,6 @@ CustomListView {
|
||||
root.syncDisplayText();
|
||||
}
|
||||
|
||||
target: root.visibilities
|
||||
target: root.screenState
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,14 +13,14 @@ CarouselView {
|
||||
readonly property var screen: QsWindow.window?.screen
|
||||
required property SearchBar search
|
||||
readonly property var values: Wallpapers.query(root.search.text.split(" ").slice(1).join(" "))
|
||||
required property var visibilities
|
||||
required property ScreenState screenState
|
||||
|
||||
currentIndex: {
|
||||
const idx = Wallpapers.list.findIndex(w => w.path === Wallpapers.actualCurrent);
|
||||
return idx >= 0 ? idx : 0;
|
||||
}
|
||||
delegateProperties: ({
|
||||
visibilities: root.visibilities
|
||||
screenState: root.screenState
|
||||
})
|
||||
focalWidth: Config.launcher.sizes.wallpaperWidth
|
||||
glideDuration: 250
|
||||
@@ -32,7 +32,7 @@ CarouselView {
|
||||
return 0;
|
||||
const barMargins = Config.bar.border * 2;
|
||||
let margins = Math.round(screen.width / 6);
|
||||
if (visibilities.utilities || visibilities.sidebar)
|
||||
if (screenState.utilities || screenState.sidebar)
|
||||
margins = Config.sidebar.sizes.width;
|
||||
return screen.width - (barMargins + margins) * 2 - Config.bar.rounding * 4;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ Item {
|
||||
readonly property int padding: Tokens.padding.largeIncreased / 4
|
||||
required property var panels
|
||||
readonly property int rounding: Tokens.rounding.largeIncreased
|
||||
required property PersistentProperties visibilities
|
||||
required property ScreenState screenState
|
||||
|
||||
implicitHeight: search.height + listWrapper.height + padding * 2
|
||||
implicitWidth: listWrapper.width + padding * 2
|
||||
@@ -38,7 +38,7 @@ Item {
|
||||
panels: root.panels
|
||||
rounding: root.rounding
|
||||
search: search
|
||||
visibilities: root.visibilities
|
||||
screenState: root.screenState
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ Item {
|
||||
|
||||
Component.onCompleted: forceActiveFocus()
|
||||
Keys.onDownPressed: list.currentList?.decrementCurrentIndex()
|
||||
Keys.onEscapePressed: root.visibilities.launcher = false
|
||||
Keys.onEscapePressed: root.screenState.launcher = false
|
||||
Keys.onPressed: event => {
|
||||
if (event.modifiers & Qt.ControlModifier) {
|
||||
if (event.key === Qt.Key_J) {
|
||||
@@ -81,7 +81,7 @@ Item {
|
||||
if (Colors.scheme === "dynamic" && currentItem.modelData.path !== Wallpapers.actualCurrent)
|
||||
Wallpapers.previewColorLock = true;
|
||||
Wallpapers.setWallpaper(currentItem.modelData.path);
|
||||
root.visibilities.launcher = false;
|
||||
root.screenState.launcher = false;
|
||||
} else if (text.startsWith(Config.launcher.actionPrefix)) {
|
||||
if (text.startsWith(`${Config.launcher.actionPrefix}calc `))
|
||||
currentItem.onClicked();
|
||||
@@ -89,18 +89,18 @@ Item {
|
||||
currentItem.modelData.onClicked(list.currentList);
|
||||
} else {
|
||||
Apps.launch(currentItem.modelData);
|
||||
root.visibilities.launcher = false;
|
||||
root.screenState.launcher = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onLauncherChanged(): void {
|
||||
if (!root.visibilities.launcher)
|
||||
if (!root.screenState.launcher)
|
||||
search.text = "";
|
||||
}
|
||||
|
||||
target: root.visibilities
|
||||
target: root.screenState
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ Item {
|
||||
required property int rounding
|
||||
required property SearchBar search
|
||||
readonly property bool showWallpapers: search.text.startsWith(`${Config.launcher.actionPrefix}wallpaper `)
|
||||
required property PersistentProperties visibilities
|
||||
required property ScreenState screenState
|
||||
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
@@ -28,7 +28,7 @@ Item {
|
||||
state: showWallpapers ? "wallpapers" : "apps"
|
||||
|
||||
Behavior on implicitHeight {
|
||||
enabled: root.visibilities.launcher
|
||||
enabled: root.screenState.launcher
|
||||
|
||||
Anim {
|
||||
duration: Tokens.anim.durations.small
|
||||
@@ -36,7 +36,7 @@ Item {
|
||||
}
|
||||
}
|
||||
Behavior on implicitWidth {
|
||||
enabled: root.visibilities.launcher
|
||||
enabled: root.screenState.launcher
|
||||
|
||||
Anim {
|
||||
duration: Tokens.anim.durations.small
|
||||
@@ -99,7 +99,7 @@ Item {
|
||||
|
||||
sourceComponent: AppList {
|
||||
search: root.search
|
||||
visibilities: root.visibilities
|
||||
screenState: root.screenState
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ Item {
|
||||
sourceComponent: CarouselView {
|
||||
panels: root.panels
|
||||
search: root.search
|
||||
visibilities: root.visibilities
|
||||
screenState: root.screenState
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ Item {
|
||||
id: root
|
||||
|
||||
required property DesktopEntry modelData
|
||||
required property PersistentProperties visibilities
|
||||
required property ScreenState screenState
|
||||
|
||||
anchors.left: parent?.left
|
||||
anchors.right: parent?.right
|
||||
@@ -23,7 +23,7 @@ Item {
|
||||
|
||||
onClicked: {
|
||||
Apps.launch(root.modelData);
|
||||
root.visibilities.launcher = false;
|
||||
root.screenState.launcher = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ Item {
|
||||
|
||||
function onClicked(): void {
|
||||
Quickshell.execDetached(["wl-copy", Qalculator.eval(math, false)]);
|
||||
root.list.visibilities.launcher = false;
|
||||
root.list.screenState.launcher = false;
|
||||
}
|
||||
|
||||
anchors.left: parent?.left
|
||||
@@ -81,7 +81,7 @@ Item {
|
||||
|
||||
onClicked: {
|
||||
Quickshell.execDetached(["app2unit", "--", ...Config.general.apps.terminal, "fish", "-C", `exec qalc -i '${root.math}'`]);
|
||||
root.list.visibilities.launcher = false;
|
||||
root.list.screenState.launcher = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import ZShell.Config
|
||||
import qs.Components
|
||||
import qs.Helpers
|
||||
import qs.Modules.Launcher.Services
|
||||
import qs.Services
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property var list
|
||||
required property var modelData
|
||||
|
||||
anchors.left: parent?.left
|
||||
anchors.right: parent?.right
|
||||
implicitHeight: Config.launcher.sizes.itemHeight
|
||||
|
||||
function onClicked(): void {
|
||||
root.list.screenState.launcher = false;
|
||||
if (root.modelData?.path)
|
||||
Files.launch(["xdg-open", root.modelData.path]);
|
||||
}
|
||||
|
||||
StateLayer {
|
||||
id: sLayer
|
||||
|
||||
radius: Tokens.rounding.medium
|
||||
manualHoverOverride: openLocation.hovered
|
||||
|
||||
onClicked: {
|
||||
root.onClicked();
|
||||
}
|
||||
}
|
||||
|
||||
IconButton {
|
||||
id: openLocation
|
||||
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Tokens.padding.medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
icon: "open_in_new"
|
||||
isRound: true
|
||||
|
||||
scale: sLayer.containsMouse || sLayer.manualHoverOverride ? 1 : 0.4
|
||||
opacity: sLayer.containsMouse || sLayer.manualHoverOverride ? 1 : 0
|
||||
visible: opacity > 0
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
root.list.screenState.launcher = false;
|
||||
if (root.modelData?.path)
|
||||
Files.showInFolder(root.modelData.path);
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Tokens.padding.small
|
||||
anchors.margins: Tokens.padding.small
|
||||
anchors.rightMargin: Tokens.padding.small
|
||||
|
||||
IconImage {
|
||||
id: icon
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
implicitSize: Tokens.font.size.extraLarge
|
||||
source: Quickshell.iconPath(root.modelData?.icon) ?? ""
|
||||
visible: !root.modelData?.isImage
|
||||
}
|
||||
|
||||
Loader {
|
||||
active: opacity > 0
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
asynchronous: true
|
||||
opacity: root.modelData?.isImage && image.status === Image.Loading ? 1 : 0
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
sourceComponent: LoadingIndicator {
|
||||
color: Colors.palette.m3primaryContainer
|
||||
implicitSize: Tokens.font.size.extraLarge
|
||||
}
|
||||
}
|
||||
|
||||
FadeImage {
|
||||
id: image
|
||||
|
||||
property int implicitSize: Tokens.font.size.extraLarge
|
||||
|
||||
width: implicitSize
|
||||
fillMode: Image.PreserveAspectFit
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
source: root.modelData?.isImage ? root.modelData?.path ?? "" : ""
|
||||
visible: root.modelData?.isImage
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.left: icon.right
|
||||
anchors.leftMargin: Tokens.spacing.medium
|
||||
anchors.verticalCenter: icon.verticalCenter
|
||||
implicitHeight: name.implicitHeight + desc.implicitHeight
|
||||
implicitWidth: parent.width - icon.width
|
||||
|
||||
CustomText {
|
||||
id: name
|
||||
|
||||
font.pointSize: Tokens.font.size.normal
|
||||
text: root.modelData?.name ?? ""
|
||||
elide: Text.ElideRight
|
||||
width: root.width - icon.width - openLocation.implicitWidth - openLocation.anchors.rightMargin - Tokens.spacing.medium - Tokens.rounding.medium * 2
|
||||
}
|
||||
|
||||
CustomText {
|
||||
id: desc
|
||||
|
||||
anchors.top: name.bottom
|
||||
color: Colors.palette.m3outline
|
||||
elide: Text.ElideRight
|
||||
font.pointSize: Tokens.font.size.small
|
||||
text: root.modelData?.path ?? ""
|
||||
width: root.width - icon.width - openLocation.implicitWidth - openLocation.anchors.rightMargin - Tokens.spacing.medium - Tokens.rounding.medium * 2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ Item {
|
||||
required property var modelData // FileSystemEntry-like {path, relativePath}
|
||||
required property real tileHeight
|
||||
required property real tileWidth
|
||||
required property PersistentProperties visibilities
|
||||
required property ScreenState screenState
|
||||
|
||||
signal requestActivate
|
||||
|
||||
|
||||
@@ -42,10 +42,10 @@ Searcher {
|
||||
if (command[0] === "autocomplete" && command.length > 1) {
|
||||
list.search.text = `${Config.launcher.actionPrefix}${command[1]} `;
|
||||
} else if (command[0] === "setMode" && command.length > 1) {
|
||||
list.visibilities.launcher = false;
|
||||
list.screenState.launcher = false;
|
||||
Colors.setMode(command[1]);
|
||||
} else {
|
||||
list.visibilities.launcher = false;
|
||||
list.screenState.launcher = false;
|
||||
Quickshell.execDetached(command);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ Searcher {
|
||||
required property string variant
|
||||
|
||||
function onClicked(list: AppList): void {
|
||||
list.visibilities.launcher = false;
|
||||
list.screenState.launcher = false;
|
||||
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--scheme", variant]);
|
||||
Config.colors.schemeType = variant;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ Item {
|
||||
property int contentHeight
|
||||
readonly property real maxHeight: {
|
||||
let max = screen.height - Tokens.spacing.largeIncreased * 2;
|
||||
if (visibilities.resources && panels.resourcesWrapper.x + panels.resourcesWrapper.width > root.x)
|
||||
if (screenState.resources && panels.resourcesWrapper.x + panels.resourcesWrapper.width > root.x)
|
||||
max -= panels.resources.nonAnimHeight;
|
||||
if (panels.popouts.hasCurrent)
|
||||
if (panels.popouts.current?.x + panels.popouts.current?.width > root.x && panels.popouts.current?.x < root.x + root.width)
|
||||
@@ -22,8 +22,8 @@ Item {
|
||||
property real offsetScale: shouldBeActive ? 0 : 1
|
||||
required property var panels
|
||||
required property ShellScreen screen
|
||||
readonly property bool shouldBeActive: visibilities.launcher
|
||||
required property PersistentProperties visibilities
|
||||
readonly property bool shouldBeActive: screenState.launcher
|
||||
required property ScreenState screenState
|
||||
|
||||
anchors.bottomMargin: (-implicitHeight - 5) * offsetScale
|
||||
implicitHeight: content.implicitHeight
|
||||
@@ -57,7 +57,7 @@ Item {
|
||||
sourceComponent: Content {
|
||||
maxHeight: root.maxHeight
|
||||
panels: root.panels
|
||||
visibilities: root.visibilities
|
||||
screenState: root.screenState
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ CustomRect {
|
||||
|
||||
MaterialShape {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
color: Qt.alpha(Colors.palette.m3primary, hour.index === 0 ? 1 : 0)
|
||||
color: Qt.alpha(Colors.palette.m3primary, hour.index === Weather.currentHourIndex ? 1 : 0)
|
||||
implicitSize: temp.implicitHeight + Tokens.padding.small * 2
|
||||
shape: MaterialShape.Cookie4Sided
|
||||
|
||||
@@ -73,7 +73,7 @@ CustomRect {
|
||||
id: temp
|
||||
|
||||
anchors.centerIn: parent
|
||||
color: hour.index === 0 ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
|
||||
color: hour.index === Weather.currentHourIndex ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
|
||||
font.pointSize: Tokens.font.size.normal
|
||||
text: Weather.formatTemp(hour.cond.tempC).slice(0, -1) // Remove C/F
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ Item {
|
||||
|
||||
readonly property int padding: Tokens.padding.smaller
|
||||
required property Item panels
|
||||
required property PersistentProperties visibilities
|
||||
required property ScreenState screenState
|
||||
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
@@ -27,16 +27,16 @@ Item {
|
||||
for (let i = 0; i < count; i++)
|
||||
height += (list.itemAtIndex(i) as NotifWrapper)?.nonAnimHeight ?? 0;
|
||||
|
||||
if (panels.popouts.hasCurrent && (panels.popouts.currentCenter + (panels.popouts.current?.width / 2)) > panels.notifications.x || visibilities.dashboard)
|
||||
if (panels.popouts.hasCurrent && (panels.popouts.currentCenter + (panels.popouts.current?.width / 2)) > panels.notifications.x || screenState.dashboard)
|
||||
return 0;
|
||||
|
||||
if (visibilities.osd) {
|
||||
if (screenState.osd) {
|
||||
const h = panels.osd.y - Tokens.spacing.small * 2 - padding * 2;
|
||||
if (height > h)
|
||||
height = h;
|
||||
}
|
||||
|
||||
if (visibilities.session) {
|
||||
if (screenState.session) {
|
||||
const h = panels.session.y - Tokens.spacing.small * 2 - padding * 2;
|
||||
if (height > h)
|
||||
height = h;
|
||||
|
||||
@@ -7,8 +7,7 @@ import qs.Services
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property Props props
|
||||
required property var visibilities
|
||||
required property ScreenState screenState
|
||||
|
||||
ColumnLayout {
|
||||
id: layout
|
||||
@@ -23,8 +22,7 @@ Item {
|
||||
radius: Tokens.rounding.small
|
||||
|
||||
NotifDock {
|
||||
props: root.props
|
||||
visibilities: root.visibilities
|
||||
screenState: root.screenState
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,8 +15,7 @@ CustomRect {
|
||||
required property bool expanded
|
||||
required property NotifServer.Notif modelData
|
||||
readonly property real nonAnimHeight: expanded ? summary.implicitHeight + expandedContent.implicitHeight + expandedContent.anchors.topMargin + Tokens.padding.medium * 2 : summaryHeightMetrics.height
|
||||
required property Props props
|
||||
required property PersistentProperties visibilities
|
||||
required property ScreenState screenState
|
||||
|
||||
color: {
|
||||
const c = root.modelData?.urgency === "critical" ? Colors.palette.m3secondaryContainer : Colors.layer(Colors.palette.m3surfaceContainerHigh, 2);
|
||||
@@ -137,7 +136,7 @@ CustomRect {
|
||||
|
||||
onLinkActivated: link => {
|
||||
Qt.openUrlExternally(link);
|
||||
root.visibilities.sidebar = false;
|
||||
root.screenState.sidebar = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,8 +14,7 @@ Item {
|
||||
id: root
|
||||
|
||||
readonly property int notifCount: NotifServer.list.reduce((acc, n) => n.closed ? acc : acc + 1, 0)
|
||||
required property Props props
|
||||
required property var visibilities
|
||||
required property ScreenState screenState
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Tokens.padding.small
|
||||
@@ -120,8 +119,7 @@ Item {
|
||||
id: notifList
|
||||
|
||||
container: view
|
||||
props: root.props
|
||||
visibilities: root.visibilities
|
||||
screenState: root.screenState
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,7 @@ LazyListView {
|
||||
id: root
|
||||
|
||||
required property Flickable container
|
||||
required property Props props
|
||||
required property PersistentProperties visibilities
|
||||
required property ScreenState screenState
|
||||
|
||||
anchors.left: parent?.left
|
||||
anchors.right: parent?.right
|
||||
@@ -118,8 +117,7 @@ LazyListView {
|
||||
|
||||
container: root.container
|
||||
modelData: notif.modelData
|
||||
props: root.props
|
||||
visibilities: root.visibilities
|
||||
screenState: root.screenState
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ CustomRect {
|
||||
readonly property list<var> activeNotifs: notifs.filter(n => !n.closed)
|
||||
readonly property string appIcon: activeNotifs.find(n => n.appIcon.length > 0)?.appIcon ?? ""
|
||||
required property Flickable container
|
||||
readonly property bool expanded: props.expandedNotifs.includes(modelData)
|
||||
readonly property bool expanded: screenState.expandedNotifs.includes(modelData)
|
||||
readonly property string image: activeNotifs.find(n => n.image.length > 0)?.image ?? ""
|
||||
required property string modelData
|
||||
readonly property int nonAnimHeight: {
|
||||
@@ -26,7 +26,7 @@ CustomRect {
|
||||
}
|
||||
readonly property int notifCount: activeNotifs.length
|
||||
readonly property list<var> notifs: NotifServer.list.filter(n => n.appName === modelData)
|
||||
required property Props props
|
||||
required property ScreenState screenState
|
||||
readonly property int urgency: {
|
||||
if (activeNotifs.find(n => n.urgency === NotificationUrgency.Critical))
|
||||
return NotificationUrgency.Critical;
|
||||
@@ -34,14 +34,13 @@ CustomRect {
|
||||
return NotificationUrgency.Normal;
|
||||
return NotificationUrgency.Low;
|
||||
}
|
||||
required property PersistentProperties visibilities
|
||||
|
||||
function toggleExpand(expand: bool): void {
|
||||
if (expand) {
|
||||
if (!expanded)
|
||||
props.expandedNotifs.push(modelData);
|
||||
screenState.expandedNotifs.push(modelData);
|
||||
} else if (expanded) {
|
||||
props.expandedNotifs.splice(props.expandedNotifs.indexOf(modelData), 1);
|
||||
screenState.expandedNotifs.splice(screenState.expandedNotifs.indexOf(modelData), 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +58,7 @@ CustomRect {
|
||||
|
||||
Component.onDestruction: {
|
||||
if (notifCount === 0 && expanded)
|
||||
props.expandedNotifs.splice(props.expandedNotifs.indexOf(modelData), 1);
|
||||
screenState.expandedNotifs.splice(screenState.expandedNotifs.indexOf(modelData), 1);
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
@@ -233,8 +232,7 @@ CustomRect {
|
||||
container: root.container
|
||||
expanded: root.expanded
|
||||
notifs: root.notifs
|
||||
props: root.props
|
||||
visibilities: root.visibilities
|
||||
screenState: root.screenState
|
||||
|
||||
onRequestToggleExpand: expand => root.toggleExpand(expand)
|
||||
}
|
||||
|
||||
@@ -14,8 +14,7 @@ LazyListView {
|
||||
required property Flickable container
|
||||
required property bool expanded
|
||||
required property list<var> notifs
|
||||
required property Props props
|
||||
required property PersistentProperties visibilities
|
||||
required property ScreenState screenState
|
||||
|
||||
signal requestToggleExpand(expand: bool)
|
||||
|
||||
@@ -122,8 +121,7 @@ LazyListView {
|
||||
anchors.fill: parent
|
||||
expanded: root.expanded
|
||||
modelData: notif.modelData
|
||||
props: root.props
|
||||
visibilities: root.visibilities
|
||||
screenState: root.screenState
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import Quickshell
|
||||
|
||||
PersistentProperties {
|
||||
property list<string> expandedNotifs: []
|
||||
|
||||
reloadableId: "sidebar"
|
||||
}
|
||||
@@ -11,8 +11,8 @@ import qs.Services
|
||||
CustomRect {
|
||||
id: root
|
||||
|
||||
required property var props
|
||||
required property PersistentProperties visibilities
|
||||
required property ScreenState screenState
|
||||
|
||||
|
||||
Layout.fillWidth: true
|
||||
color: Colors.tPalette.m3surfaceContainer
|
||||
@@ -72,7 +72,7 @@ CustomRect {
|
||||
}
|
||||
|
||||
SplitButton {
|
||||
active: menuItems.find(m => root.props.recordingMode === m.icon + m.text) ?? menuItems[0]
|
||||
active: menuItems.find(m => root.screenState.recordingMode === m.icon + m.text) ?? menuItems[0]
|
||||
enabled: !Recorder.running
|
||||
menuOnTop: true
|
||||
|
||||
@@ -107,7 +107,7 @@ CustomRect {
|
||||
}
|
||||
]
|
||||
|
||||
menu.onItemSelected: item => root.props.recordingMode = item.icon + item.text
|
||||
menu.onItemSelected: item => root.screenState.recordingMode = item.icon + item.text
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,8 +190,7 @@ CustomRect {
|
||||
id: recordingList
|
||||
|
||||
RecordingList {
|
||||
props: root.props
|
||||
visibilities: root.visibilities
|
||||
screenState: root.screenState
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,8 +14,7 @@ import qs.Services
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
required property var props
|
||||
required property PersistentProperties visibilities
|
||||
required property ScreenState screenState
|
||||
|
||||
spacing: 0
|
||||
|
||||
@@ -23,7 +22,7 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: root.props.recordingListExpanded = !root.props.recordingListExpanded
|
||||
onClicked: root.screenState.recordingListExpanded = !root.screenState.recordingListExpanded
|
||||
|
||||
RowLayout {
|
||||
spacing: Tokens.spacing.small
|
||||
@@ -42,11 +41,11 @@ ColumnLayout {
|
||||
}
|
||||
|
||||
IconButton {
|
||||
icon: root.props.recordingListExpanded ? "unfold_less" : "unfold_more"
|
||||
icon: root.screenState.recordingListExpanded ? "unfold_less" : "unfold_more"
|
||||
label.animate: true
|
||||
type: IconButton.Text
|
||||
|
||||
onClicked: root.props.recordingListExpanded = !root.props.recordingListExpanded
|
||||
onClicked: root.screenState.recordingListExpanded = !root.screenState.recordingListExpanded
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,7 +56,7 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.rightMargin: -Tokens.spacing.small
|
||||
clip: true
|
||||
implicitHeight: (Tokens.font.size.larger + Tokens.padding.extraSmall) * (root.props.recordingListExpanded ? 10 : 3)
|
||||
implicitHeight: (Tokens.font.size.larger + Tokens.padding.extraSmall) * (root.screenState.recordingListExpanded ? 10 : 3)
|
||||
|
||||
CustomScrollBar.vertical: CustomScrollBar {
|
||||
flickable: list
|
||||
@@ -109,8 +108,11 @@ ColumnLayout {
|
||||
type: IconButton.Text
|
||||
|
||||
onClicked: {
|
||||
root.visibilities.sidebar = false;
|
||||
Quickshell.execDetached(["app2unit", "--", ...Config.general.apps.playback, recording.modelData.path]);
|
||||
root.screenState.sidebar = false;
|
||||
if (Config.launcher.uwsm)
|
||||
Quickshell.execDetached(["app2unit", "--", ...Config.general.apps.playback, recording.modelData.path]);
|
||||
else
|
||||
Quickshell.execDetached([...Config.general.apps.playback, recording.modelData.path]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,8 +121,11 @@ ColumnLayout {
|
||||
type: IconButton.Text
|
||||
|
||||
onClicked: {
|
||||
root.visibilities.sidebar = false;
|
||||
Quickshell.execDetached(["app2unit", "--", ...Config.general.apps.explorer, recording.modelData.path]);
|
||||
root.screenState.sidebar = false;
|
||||
if (Config.launcher.uwsm)
|
||||
Quickshell.execDetached(["app2unit", "--", ...Config.general.apps.explorer, recording.modelData.path]);
|
||||
else
|
||||
Quickshell.execDetached([...Config.general.apps.explorer, recording.modelData.path]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -170,11 +175,11 @@ ColumnLayout {
|
||||
|
||||
MaterialIcon {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredHeight: root.props.recordingListExpanded ? implicitHeight : 0
|
||||
Layout.preferredHeight: root.screenState.recordingListExpanded ? implicitHeight : 0
|
||||
color: Colors.palette.m3outline
|
||||
font.pointSize: Tokens.font.size.extraLarge
|
||||
opacity: root.props.recordingListExpanded ? 1 : 0
|
||||
scale: root.props.recordingListExpanded ? 1 : 0
|
||||
opacity: root.screenState.recordingListExpanded ? 1 : 0
|
||||
scale: root.screenState.recordingListExpanded ? 1 : 0
|
||||
text: "scan_delete"
|
||||
|
||||
Behavior on Layout.preferredHeight {
|
||||
@@ -196,10 +201,10 @@ ColumnLayout {
|
||||
|
||||
MaterialIcon {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredWidth: !root.props.recordingListExpanded ? implicitWidth : 0
|
||||
Layout.preferredWidth: !root.screenState.recordingListExpanded ? implicitWidth : 0
|
||||
color: Colors.palette.m3outline
|
||||
opacity: !root.props.recordingListExpanded ? 1 : 0
|
||||
scale: !root.props.recordingListExpanded ? 1 : 0
|
||||
opacity: !root.screenState.recordingListExpanded ? 1 : 0
|
||||
scale: !root.screenState.recordingListExpanded ? 1 : 0
|
||||
text: "scan_delete"
|
||||
|
||||
Behavior on Layout.preferredWidth {
|
||||
|
||||
@@ -12,13 +12,11 @@ import qs.Services
|
||||
import qs.Helpers
|
||||
import qs.Daemons
|
||||
import qs.Modules.Settings
|
||||
import qs.Modules.Bar.Popouts as BarPopouts
|
||||
|
||||
CustomRect {
|
||||
id: root
|
||||
|
||||
readonly property bool needExtraRow: quickToggles.length > 6
|
||||
required property BarPopouts.Wrapper popouts
|
||||
readonly property var quickToggles: {
|
||||
const seenIds = new Set();
|
||||
|
||||
@@ -40,8 +38,8 @@ CustomRect {
|
||||
return true;
|
||||
});
|
||||
}
|
||||
required property ScreenState screenState
|
||||
readonly property int splitIndex: Math.ceil(quickToggles.length / 2)
|
||||
required property PersistentProperties visibilities
|
||||
|
||||
color: Colors.tPalette.m3surfaceContainer
|
||||
implicitHeight: layout.implicitHeight + Tokens.padding.extraLargeIncreased
|
||||
@@ -168,8 +166,8 @@ CustomRect {
|
||||
isToggle: false
|
||||
|
||||
onClicked: {
|
||||
root.visibilities.sidebar = false;
|
||||
root.visibilities.settings = true;
|
||||
root.screenState.sidebar = false;
|
||||
root.screenState.settings = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import qs.Modules.Bar.Popouts as BarPopouts
|
||||
import qs.Components
|
||||
import qs.Modules.Notifications.Sidebar.Utils.Cards
|
||||
import ZShell.Config
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property BarPopouts.Wrapper popouts
|
||||
required property PersistentProperties props
|
||||
required property var visibilities
|
||||
required property ScreenState screenState
|
||||
|
||||
implicitHeight: layout.implicitHeight
|
||||
implicitWidth: layout.implicitWidth
|
||||
@@ -25,15 +23,13 @@ Item {
|
||||
}
|
||||
|
||||
Record {
|
||||
props: root.props
|
||||
visibilities: root.visibilities
|
||||
screenState: root.screenState
|
||||
z: 1
|
||||
}
|
||||
|
||||
Toggles {
|
||||
Layout.fillWidth: true
|
||||
popouts: root.popouts
|
||||
visibilities: root.visibilities
|
||||
screenState: root.screenState
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,17 +10,9 @@ Item {
|
||||
id: root
|
||||
|
||||
property real offsetScale: shouldBeActive ? 0 : 1
|
||||
required property BarPopouts.Wrapper popouts
|
||||
readonly property PersistentProperties props: PersistentProperties {
|
||||
property string recordingConfirmDelete
|
||||
property bool recordingListExpanded: false
|
||||
property string recordingMode
|
||||
|
||||
reloadableId: "utilities"
|
||||
}
|
||||
readonly property bool shouldBeActive: visibilities.sidebar
|
||||
required property ScreenState screenState
|
||||
readonly property bool shouldBeActive: screenState.sidebar && !sidebar.chatActive
|
||||
required property Item sidebar
|
||||
required property var visibilities
|
||||
|
||||
anchors.bottomMargin: (-implicitHeight - 5) * offsetScale
|
||||
implicitHeight: content.implicitHeight + 8 * 2
|
||||
@@ -45,9 +37,7 @@ Item {
|
||||
|
||||
sourceComponent: Content {
|
||||
implicitWidth: root.implicitWidth - 8 * 2
|
||||
popouts: root.popouts
|
||||
props: root.props
|
||||
visibilities: root.visibilities
|
||||
screenState: root.screenState
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,12 +7,11 @@ import QtQuick
|
||||
Item {
|
||||
id: root
|
||||
|
||||
readonly property bool chatActive: screenState.sidebarTab === 1
|
||||
property real offsetScale: shouldBeActive ? 0 : 1
|
||||
required property var panels
|
||||
readonly property Props props: Props {
|
||||
}
|
||||
readonly property bool shouldBeActive: root.visibilities.sidebar && Config.sidebar.enabled
|
||||
required property var visibilities
|
||||
required property ScreenState screenState
|
||||
readonly property bool shouldBeActive: root.screenState.sidebar && Config.sidebar.enabled
|
||||
|
||||
anchors.rightMargin: (-implicitWidth - 5) * offsetScale
|
||||
implicitWidth: Config.sidebar.sizes.width
|
||||
@@ -38,8 +37,7 @@ Item {
|
||||
|
||||
sourceComponent: Content {
|
||||
implicitWidth: Config.sidebar.sizes.width - Tokens.padding.small * 2
|
||||
props: root.props
|
||||
visibilities: root.visibilities
|
||||
screenState: root.screenState
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ Item {
|
||||
|
||||
required property Item panels
|
||||
required property Item sidebarPanel
|
||||
required property var visibilities
|
||||
required property ScreenState screenState
|
||||
|
||||
implicitHeight: content.implicitHeight
|
||||
implicitWidth: content.implicitWidth
|
||||
@@ -17,6 +17,6 @@ Item {
|
||||
id: content
|
||||
|
||||
panels: root.panels
|
||||
visibilities: root.visibilities
|
||||
screenState: root.screenState
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ Item {
|
||||
required property bool muted
|
||||
required property bool sourceMuted
|
||||
required property real sourceVolume
|
||||
required property var visibilities
|
||||
required property ScreenState screenState
|
||||
required property real volume
|
||||
|
||||
implicitHeight: layout.implicitHeight + Tokens.padding.smaller * 2
|
||||
@@ -53,7 +53,7 @@ Item {
|
||||
|
||||
// Microphone volume
|
||||
WrappedLoader {
|
||||
shouldBeActive: Config.osd.enableMicrophone && (!Config.osd.enableBrightness || !root.visibilities.session)
|
||||
shouldBeActive: Config.osd.enableMicrophone && (!Config.osd.enableBrightness || !root.screenState.session)
|
||||
|
||||
sourceComponent: CustomMouseArea {
|
||||
function onWheel(event: WheelEvent) {
|
||||
|
||||
@@ -16,16 +16,16 @@ Item {
|
||||
property bool muted
|
||||
property real offsetScale: shouldBeActive ? 0 : 1
|
||||
required property ShellScreen screen
|
||||
readonly property bool shouldBeActive: visibilities.osd && Config.osd.enabled && !(visibilities.utilities && Config.utilities.enabled)
|
||||
readonly property bool shouldBeActive: screenState.osd && Config.osd.enabled && !(screenState.utilities && Config.utilities.enabled)
|
||||
property real sidebarOffset: sidebarOrSessionVisible ? 12 : 0
|
||||
required property bool sidebarOrSessionVisible
|
||||
property bool sourceMuted
|
||||
property real sourceVolume
|
||||
required property var visibilities
|
||||
required property ScreenState screenState
|
||||
property real volume
|
||||
|
||||
function show(): void {
|
||||
visibilities.osd = true;
|
||||
screenState.osd = true;
|
||||
timer.restart();
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ Item {
|
||||
|
||||
onTriggered: {
|
||||
if (!root.hovered)
|
||||
root.visibilities.osd = false;
|
||||
root.screenState.osd = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ Item {
|
||||
muted: root.muted
|
||||
sourceMuted: root.sourceMuted
|
||||
sourceVolume: root.sourceVolume
|
||||
visibilities: root.visibilities
|
||||
screenState: root.screenState
|
||||
volume: root.volume
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@ Item {
|
||||
readonly property real nonAnimHeight: content.item?.nonAnimHeight ?? 0
|
||||
property real offsetScale: shouldBeActive ? 0 : 1
|
||||
required property string position
|
||||
readonly property bool shouldBeActive: root.visibilities.resources
|
||||
required property PersistentProperties visibilities
|
||||
readonly property bool shouldBeActive: root.screenState.resources
|
||||
required property ScreenState screenState
|
||||
|
||||
anchors.bottom: position === "bottom" ? parent.bottom : undefined
|
||||
anchors.bottomMargin: position === "bottom" ? (-implicitHeight - 5) * offsetScale : 0
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import ZShell.Config
|
||||
import qs.Components
|
||||
import qs.Services
|
||||
|
||||
ConnectedRect {
|
||||
id: root
|
||||
|
||||
property alias label: label.text
|
||||
property string subtext
|
||||
property string value
|
||||
property alias placeholderText: input.placeholderText
|
||||
property string errorText
|
||||
property alias maximumLength: input.maximumLength
|
||||
property alias validate: input.validate
|
||||
property alias validator: input.validator
|
||||
property bool smallField
|
||||
readonly property alias field: input
|
||||
|
||||
signal valueEdited(value: string)
|
||||
signal editingFinished(value: string)
|
||||
|
||||
function clear(): void {
|
||||
input.clear();
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
if (value !== input.text)
|
||||
editingFinished(input.text);
|
||||
}
|
||||
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: rowLayout.implicitHeight + Tokens.padding.medium + Math.max(0, Tokens.padding.large - input.verticalPadding) * 2
|
||||
|
||||
RowLayout {
|
||||
id: rowLayout
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: Tokens.padding.largeIncreased
|
||||
anchors.rightMargin: Tokens.padding.largeIncreased
|
||||
spacing: Tokens.spacing.medium
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
|
||||
CustomText {
|
||||
id: label
|
||||
|
||||
Layout.fillWidth: true
|
||||
font: Tokens.font.body.small
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
visible: root.subtext
|
||||
text: input.isError && root.errorText ? root.errorText : root.subtext
|
||||
color: input.isError && root.errorText ? Colors.palette.m3error : Colors.palette.m3outline
|
||||
font.pointSize: Tokens.font.size.small
|
||||
elide: Text.ElideRight
|
||||
animate: root.errorText
|
||||
}
|
||||
}
|
||||
|
||||
CustomTextField {
|
||||
id: input
|
||||
|
||||
Layout.preferredWidth: root.smallField ? Tokens.sizes.nexus.smallTextFieldWidth : Tokens.sizes.nexus.textFieldWidth
|
||||
Layout.maximumWidth: root.width / 2
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
verticalPadding: Tokens.padding.small
|
||||
|
||||
text: root.value
|
||||
|
||||
onTextEdited: root.valueEdited(text)
|
||||
onEditingFinished: root.editingFinished(text)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,5 +33,33 @@ PageBase {
|
||||
|
||||
onToggled: Config.dashboard.enabled = checked
|
||||
}
|
||||
|
||||
// Behavior
|
||||
SectionHeader {
|
||||
text: qsTr("Behavior")
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
checked: Config.dashboard.showOnHover
|
||||
first: true
|
||||
settingAnchor: "dashboard-showonhover"
|
||||
text: qsTr("Show on hover")
|
||||
subtext: qsTr("Dashboard %1 be shown when hovering the middle-top part of the shell").arg(checked ? "will" : "will not")
|
||||
|
||||
onToggled: Config.dashboard.showOnHover = checked
|
||||
}
|
||||
|
||||
SpinRow {
|
||||
from: 30
|
||||
settingAnchor: "dashboard-drag-threshold"
|
||||
stepSize: 20
|
||||
enabled: !Config.dashboard.showOnHover
|
||||
subtext: qsTr("How far you need to drag before the panel opens")
|
||||
text: qsTr("Drag threshold")
|
||||
to: 100
|
||||
value: Config.dashboard.dragThreshold
|
||||
|
||||
onMoved: v => Config.dashboard.dragThreshold = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ Item {
|
||||
required property var panels
|
||||
property string queuedMode: "wallpaper"
|
||||
required property ShellScreen screen
|
||||
readonly property bool shouldBeActive: visibilities.settings
|
||||
required property PersistentProperties visibilities
|
||||
readonly property bool shouldBeActive: screenState.settings
|
||||
required property ScreenState screenState
|
||||
|
||||
implicitHeight: content.implicitHeight
|
||||
implicitWidth: content.implicitWidth
|
||||
|
||||
+13
-12
@@ -3,6 +3,7 @@ import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Components
|
||||
import qs.Helpers
|
||||
import qs.Services
|
||||
|
||||
Scope {
|
||||
id: root
|
||||
@@ -17,8 +18,8 @@ Scope {
|
||||
onPressed: {
|
||||
root.launcherInterrupted = false;
|
||||
if (!root.launcherInterrupted && !root.hasFullscreen) {
|
||||
const visibilities = Visibilities.getForActive();
|
||||
visibilities.launcher = !visibilities.launcher;
|
||||
const state = ShellState.forActive();
|
||||
state.launcher = !state.launcher;
|
||||
}
|
||||
root.launcherInterrupted = false;
|
||||
}
|
||||
@@ -28,8 +29,8 @@ Scope {
|
||||
name: "toggle-drawing"
|
||||
|
||||
onPressed: {
|
||||
const visibilities = Visibilities.getForActive();
|
||||
visibilities.isDrawing = !visibilities.isDrawing;
|
||||
const state = ShellState.forActive();
|
||||
state.isDrawing = !state.isDrawing;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,8 +38,8 @@ Scope {
|
||||
name: "toggle-nc"
|
||||
|
||||
onPressed: {
|
||||
const visibilities = Visibilities.getForActive();
|
||||
visibilities.sidebar = !visibilities.sidebar;
|
||||
const state = ShellState.forActive();
|
||||
state.sidebar = !state.sidebar;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,8 +47,8 @@ Scope {
|
||||
name: "show-osd"
|
||||
|
||||
onPressed: {
|
||||
const visibilities = Visibilities.getForActive();
|
||||
visibilities.osd = !visibilities.osd;
|
||||
const state = ShellState.forActive();
|
||||
state.osd = !state.osd;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,8 +56,8 @@ Scope {
|
||||
name: "toggle-settings"
|
||||
|
||||
onPressed: {
|
||||
const visibilities = Visibilities.getForActive();
|
||||
visibilities.settings = !visibilities.settings;
|
||||
const state = ShellState.forActive();
|
||||
state.settings = !state.settings;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,8 +65,8 @@ Scope {
|
||||
name: "toggle-clipboard"
|
||||
|
||||
onPressed: {
|
||||
const visibilities = Visibilities.getForActive();
|
||||
visibilities.clipboard = !visibilities.clipboard;
|
||||
const state = ShellState.forActive();
|
||||
state.clipboard = !state.clipboard;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user