Merge branch 'main' into background-clock-setting

This commit is contained in:
end-4
2025-08-31 13:05:04 +07:00
committed by GitHub
68 changed files with 2756 additions and 331 deletions
@@ -350,6 +350,10 @@ Singleton {
property real baseVerticalBarWidth: 46
property real verticalBarWidth: Config.options.bar.cornerStyle === 1 ?
(baseVerticalBarWidth + root.sizes.hyprlandGapsOut * 2) : baseVerticalBarWidth
property real wallpaperSelectorWidth: 1200
property real wallpaperSelectorHeight: 690
property real wallpaperSelectorItemMargins: 8
property real wallpaperSelectorItemPadding: 6
}
syntaxHighlightingTheme: root.m3colors.darkmode ? "Monokai" : "ayu Light"
@@ -128,7 +128,7 @@ Singleton {
property real workspaceZoom: 1.07 // Relative to your screen, not wallpaper size
property bool enableSidebar: true
}
property string mantra: ""
property string quote: ""
property bool hideWhenFullscreen: true
}
@@ -175,6 +175,8 @@ Singleton {
property bool showAppIcons: true
property bool alwaysShowNumbers: false
property int showNumberDelay: 300 // milliseconds
property list<string> numberMap: ["1", "2"] // Characters to show instead of numbers on workspace indicator
property bool useNerdFont: false
}
property JsonObject weather: JsonObject {
property bool enable: false
@@ -8,12 +8,17 @@ import Quickshell
Singleton {
// XDG Dirs, with "file://"
readonly property string home: StandardPaths.standardLocations(StandardPaths.HomeLocation)[0]
readonly property string config: StandardPaths.standardLocations(StandardPaths.ConfigLocation)[0]
readonly property string state: StandardPaths.standardLocations(StandardPaths.StateLocation)[0]
readonly property string cache: StandardPaths.standardLocations(StandardPaths.CacheLocation)[0]
readonly property string pictures: StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0]
readonly property string genericCache: StandardPaths.standardLocations(StandardPaths.GenericCacheLocation)[0]
readonly property string documents: StandardPaths.standardLocations(StandardPaths.DocumentsLocation)[0]
readonly property string downloads: StandardPaths.standardLocations(StandardPaths.DownloadLocation)[0]
readonly property string pictures: StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0]
readonly property string music: StandardPaths.standardLocations(StandardPaths.MusicLocation)[0]
readonly property string videos: StandardPaths.standardLocations(StandardPaths.MoviesLocation)[0]
// Other dirs used by the shell, without "file://"
property string assetsPath: Quickshell.shellPath("assets")
property string scriptPath: Quickshell.shellPath("scripts")
@@ -0,0 +1,23 @@
pragma Singleton
// From https://github.com/caelestia-dots/shell (GPLv3)
import Quickshell
Singleton {
id: root
function getBluetoothDeviceMaterialSymbol(systemIconName: string): string {
if (systemIconName.includes("headset") || systemIconName.includes("headphones"))
return "headphones";
if (systemIconName.includes("audio"))
return "speaker";
if (systemIconName.includes("phone"))
return "smartphone";
if (systemIconName.includes("mouse"))
return "mouse";
if (systemIconName.includes("keyboard"))
return "keyboard";
return "bluetooth";
}
}
@@ -0,0 +1,31 @@
pragma Singleton
import Quickshell
Singleton {
// Formats
readonly property list<string> validImageTypes: ["jpeg", "png", "webp", "tiff", "svg"]
readonly property list<string> validImageExtensions: ["jpg", "jpeg", "png", "webp", "tif", "tiff", "svg"]
function isValidImageByName(name: string): bool {
return validImageExtensions.some(t => name.endsWith(`.${t}`));
}
// Thumbnails
// https://specifications.freedesktop.org/thumbnail-spec/latest/directory.html
readonly property var thumbnailSizes: ({
"normal": 128,
"large": 256,
"x-large": 512,
"xx-large": 1024
})
function thumbnailSizeNameForDimensions(width: int, height: int): string {
const sizeNames = Object.keys(thumbnailSizes);
for(let i = 0; i < sizeNames.length; i++) {
const sizeName = sizeNames[i];
const maxSize = thumbnailSizes[sizeName];
if (width <= maxSize && height <= maxSize) return sizeName;
}
return "xx-large";
}
}
@@ -0,0 +1,56 @@
import QtQuick
import Quickshell
import Quickshell.Io
import qs.modules.common
import qs.modules.common.functions
/**
* Thumbnail image. It currently generates to the right place at the right size, but does not handle metadata/maintenance on modification.
* See Freedesktop's spec: https://specifications.freedesktop.org/thumbnail-spec/thumbnail-spec-latest.html
*/
Image {
id: root
property bool generateThumbnail: true
required property string sourcePath
property string thumbnailSizeName: Images.thumbnailSizeNameForDimensions(sourceSize.width, sourceSize.height)
property string thumbnailPath: {
if (sourcePath.length == 0) return;
const resolvedUrlWithoutFileProtocol = FileUtils.trimFileProtocol(`${Qt.resolvedUrl(sourcePath)}`);
const encodedUrlWithoutFileProtocol = resolvedUrlWithoutFileProtocol.split("/").map(part => encodeURIComponent(part)).join("/");
const md5Hash = Qt.md5(`file://${encodedUrlWithoutFileProtocol}`);
return `${Directories.genericCache}/thumbnails/${thumbnailSizeName}/${md5Hash}.png`;
}
source: thumbnailPath
asynchronous: true
cache: false
smooth: true
mipmap: false
opacity: status === Image.Ready ? 1 : 0
Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
onSourceSizeChanged: {
if (!root.generateThumbnail) return;
thumbnailGeneration.running = false;
thumbnailGeneration.running = true;
}
Process {
id: thumbnailGeneration
command: {
const maxSize = Images.thumbnailSizes[root.thumbnailSizeName];
return ["bash", "-c",
`[ -f '${FileUtils.trimFileProtocol(root.thumbnailPath)}' ] && exit 0 || { magick '${root.sourcePath}' -resize ${maxSize}x${maxSize} '${FileUtils.trimFileProtocol(root.thumbnailPath)}' && exit 1; }`
]
}
onExited: (exitCode, exitStatus) => {
if (exitCode === 1) { // Force reload if thumbnail had to be generated
root.source = "";
root.source = root.thumbnailPath; // Force reload
}
}
}
}
@@ -24,6 +24,20 @@ Singleton {
return trimmed.split(/[\\/]/).pop();
}
/**
* Extracts the folder name from a directory path
* @param {string} str
* @returns {string}
*/
function folderNameForPath(str) {
if (typeof str !== "string") return "";
const trimmed = trimFileProtocol(str);
// Remove trailing slash if present
const noTrailing = trimmed.endsWith("/") ? trimmed.slice(0, -1) : trimmed;
if (!noTrailing) return "";
return noTrailing.split(/[\\/]/).pop();
}
/**
* Removes the file extension from a file path or name
* @param {string} str
@@ -38,4 +52,18 @@ Singleton {
}
return trimmed;
}
/**
* Returns the parent directory of a given file path
* @param {string} str
* @returns {string}
*/
function parentDirectory(str) {
if (typeof str !== "string") return "";
const trimmed = trimFileProtocol(str);
const parts = trimmed.split(/[\\/]/);
if (parts.length <= 1) return "";
parts.pop();
return parts.join("/");
}
}
@@ -217,4 +217,8 @@ Singleton {
return str;
return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;');
}
function cleanCliphistEntry(str: string): string {
return str.replace(/^\d+\t/, "");
}
}
@@ -0,0 +1,120 @@
import QtQuick
import QtQuick.Layouts
import qs
import qs.modules.common
import qs.modules.common.widgets
import qs.modules.common.functions
Rectangle {
id: root
required property var directory
property bool showBreadcrumb: true
onShowBreadcrumbChanged: {
addressInput.text = root.directory;
}
signal navigateToDirectory(string path)
property real padding: 6
implicitWidth: mainLayout.implicitWidth + padding * 2
implicitHeight: mainLayout.implicitHeight + padding * 2
color: Appearance.colors.colLayer2
function focusBreadcrumb() {
root.showBreadcrumb = false;
addressInput.forceActiveFocus();
}
RowLayout {
id: mainLayout
anchors {
fill: parent
margins: root.padding
}
spacing: 8
RippleButton {
id: parentDirButton
onClicked: root.navigateToDirectory(FileUtils.parentDirectory(root.directory))
contentItem: MaterialSymbol {
text: "drive_folder_upload"
iconSize: Appearance.font.pixelSize.larger
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
Rectangle {
id: directoryEntry
visible: !root.showBreadcrumb
anchors.fill: parent
color: Appearance.colors.colLayer1
radius: Appearance.rounding.full
implicitWidth: addressInput.implicitWidth
implicitHeight: addressInput.implicitHeight
Keys.onPressed: event => {
if (directoryEntry.visible && event.key === Qt.Key_Escape) {
root.showBreadcrumb = true;
event.accepted = true;
return;
}
event.accepted = false;
}
StyledTextInput {
id: addressInput
anchors.fill: parent
padding: 10
text: root.directory
Keys.onPressed: event => {
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
root.navigateToDirectory(text);
root.showBreadcrumb = true;
event.accepted = true;
}
}
MouseArea {
// I-beam cursor
anchors.fill: parent
acceptedButtons: Qt.NoButton
hoverEnabled: true
cursorShape: Qt.IBeamCursor
}
}
}
Loader {
id: breadcrumbLoader
active: root.showBreadcrumb
visible: root.showBreadcrumb
anchors.fill: parent
sourceComponent: AddressBreadcrumb {
directory: root.directory
onNavigateToDirectory: dir => {
root.navigateToDirectory(dir);
}
}
}
}
RippleButton {
id: dirEditButton
toggled: !root.showBreadcrumb
onClicked: root.showBreadcrumb = !root.showBreadcrumb
contentItem: MaterialSymbol {
text: "edit"
iconSize: Appearance.font.pixelSize.larger
color: dirEditButton.toggled ? Appearance.colors.colOnPrimary : Appearance.colors.colOnLayer2
}
StyledToolTip {
content: Translation.tr("Edit directory")
}
}
}
}
@@ -0,0 +1,41 @@
import QtQuick
import QtQuick.Layouts
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import qs.modules.common.functions
ListView {
id: root
required property var directory
property var breadcrumbDirectory: ""
Component.onCompleted: breadcrumbDirectory = directory;
onDirectoryChanged: {
if (breadcrumbDirectory.startsWith(directory)) return;
breadcrumbDirectory = directory
}
signal navigateToDirectory(string path)
orientation: ListView.Horizontal
clip: true
spacing: 2
model: breadcrumbDirectory.split("/")
delegate: SelectionGroupButton {
id: folderButton
required property var modelData
required property int index
buttonText: index === 0 ? "/" : modelData
toggled: {
if (directory.trim() === "/") return index === 0;
return index === directory.split("/").length - 1
}
leftmost: index === 0
rightmost: index === breadcrumbDirectory.split("/").length - 1
onClicked: {
root.navigateToDirectory(breadcrumbDirectory.split("/").slice(0, index + 1).join("/"))
}
}
}
@@ -1,29 +1,36 @@
import qs.modules.common
import qs.modules.common.functions
import qs.modules.common.widgets
import QtQuick
/**
* Material 3 dialog button. See https://m3.material.io/components/dialogs/overview
*/
RippleButton {
id: button
id: root
property string buttonText
implicitHeight: 30
implicitWidth: buttonTextWidget.implicitWidth + 15 * 2
padding: 14
implicitHeight: 36
implicitWidth: buttonTextWidget.implicitWidth + padding * 2
buttonRadius: Appearance?.rounding.full ?? 9999
property color colEnabled: Appearance?.colors.colPrimary ?? "#65558F"
property color colDisabled: Appearance?.m3colors.m3outline ?? "#8D8C96"
colBackground: ColorUtils.transparentize(Appearance.colors.colLayer3)
colBackgroundHover: Appearance.colors.colLayer3Hover
colRipple: Appearance.colors.colLayer3Active
property alias colText: buttonTextWidget.color
contentItem: StyledText {
id: buttonTextWidget
anchors.fill: parent
anchors.leftMargin: 15
anchors.rightMargin: 15
anchors.leftMargin: root.padding
anchors.rightMargin: root.padding
text: buttonText
horizontalAlignment: Text.AlignHCenter
font.pixelSize: Appearance?.font.pixelSize.small ?? 12
color: button.enabled ? button.colEnabled : button.colDisabled
color: root.enabled ? root.colEnabled : root.colDisabled
Behavior on color {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
@@ -0,0 +1,25 @@
import qs.modules.common
import qs.modules.common.functions
import qs.modules.common.widgets
import QtQuick
RippleButton {
id: root
property bool active: false
horizontalPadding: Appearance.rounding.large
verticalPadding: 12
clip: true
pointingHandCursor: !active
implicitWidth: contentItem.implicitWidth + horizontalPadding * 2
implicitHeight: contentItem.implicitHeight + verticalPadding * 2
Behavior on implicitHeight {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
}
colBackground: ColorUtils.transparentize(Appearance.colors.colLayer3)
colBackgroundHover: active ? colBackground : Appearance.colors.colLayer3Hover
colRipple: Appearance.colors.colLayer3Active
buttonRadius: 0
}
@@ -0,0 +1,41 @@
import QtQuick
import Quickshell
import Quickshell.Io
import qs.modules.common
import qs.modules.common.functions
// From https://github.com/caelestia-dots/shell with modifications.
// License: GPLv3
Image {
id: root
required property var fileModelData
asynchronous: true
fillMode: Image.PreserveAspectFit
source: {
if (!fileModelData.fileIsDir)
return Quickshell.iconPath("application-x-zerosize");
if ([Directories.documents, Directories.downloads, Directories.music, Directories.pictures, Directories.videos].some(dir => FileUtils.trimFileProtocol(dir) === fileModelData.filePath))
return Quickshell.iconPath(`folder-${fileModelData.fileName.toLowerCase()}`);
return Quickshell.iconPath("inode-directory");
}
onStatusChanged: {
if (status === Image.Error)
source = Quickshell.iconPath("error");
}
Process {
running: !fileModelData.fileIsDir
command: ["file", "--mime", "-b", fileModelData.filePath]
stdout: StdioCollector {
onStreamFinished: {
const mime = text.split(";")[0].replace("/", "-");
root.source = Images.validImageTypes.some(t => mime === `image-${t}`) ? fileModelData.fileUrl : Quickshell.iconPath(mime, "image-missing");
}
}
}
}
@@ -0,0 +1,52 @@
import qs.modules.common
import QtQuick
import QtQuick.Controls.Material
import QtQuick.Controls
/**
* Material 3 styled TextArea (filled style)
* https://m3.material.io/components/text-fields/overview
* Note: We don't use NativeRendering because it makes the small placeholder text look weird
*/
TextArea {
id: root
Material.theme: Material.System
Material.accent: Appearance.m3colors.m3primary
Material.primary: Appearance.m3colors.m3primary
Material.background: Appearance.m3colors.m3surface
Material.foreground: Appearance.m3colors.m3onSurface
Material.containerStyle: Material.Filled
renderType: Text.QtRendering
selectedTextColor: Appearance.m3colors.m3onSecondaryContainer
selectionColor: Appearance.colors.colSecondaryContainer
placeholderTextColor: Appearance.m3colors.m3outline
background: Rectangle {
implicitHeight: 56
color: Appearance.m3colors.m3surface
topLeftRadius: 4
topRightRadius: 4
Rectangle {
anchors {
left: parent.left
right: parent.right
bottom: parent.bottom
}
height: 1
color: root.focus ? Appearance.m3colors.m3primary :
root.hovered ? Appearance.m3colors.m3outline : Appearance.m3colors.m3outlineVariant
Behavior on color {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
}
}
}
font {
family: Appearance?.font.family.main ?? "sans-serif"
pixelSize: Appearance?.font.pixelSize.small ?? 15
hintingPreference: Font.PreferFullHinting
}
wrapMode: TextEdit.Wrap
}
@@ -4,44 +4,24 @@ import QtQuick.Controls.Material
import QtQuick.Controls
/**
* Material 3 styled TextArea (filled style)
* Material 3 styled TextField (filled style)
* https://m3.material.io/components/text-fields/overview
* Note: We don't use NativeRendering because it makes the small placeholder text look weird
*/
TextArea {
TextField {
id: root
Material.theme: Material.System
Material.accent: Appearance.m3colors.m3primary
Material.primary: Appearance.m3colors.m3primary
Material.background: Appearance.m3colors.m3surface
Material.foreground: Appearance.m3colors.m3onSurface
Material.containerStyle: Material.Filled
Material.containerStyle: Material.Outlined
renderType: Text.QtRendering
selectedTextColor: Appearance.m3colors.m3onSecondaryContainer
selectionColor: Appearance.colors.colSecondaryContainer
placeholderTextColor: Appearance.m3colors.m3outline
background: Rectangle {
implicitHeight: 56
color: Appearance.m3colors.m3surface
topLeftRadius: 4
topRightRadius: 4
Rectangle {
anchors {
left: parent.left
right: parent.right
bottom: parent.bottom
}
height: 1
color: root.focus ? Appearance.m3colors.m3primary :
root.hovered ? Appearance.m3colors.m3outline : Appearance.m3colors.m3outlineVariant
Behavior on color {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
}
}
}
clip: true
font {
family: Appearance?.font.family.main ?? "sans-serif"
@@ -49,4 +29,11 @@ TextArea {
hintingPreference: Font.PreferFullHinting
}
wrapMode: TextEdit.Wrap
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
hoverEnabled: true
cursorShape: Qt.IBeamCursor
}
}
@@ -3,5 +3,5 @@ import QtQuick
MouseArea {
anchors.fill: parent
onPressed: (mouse) => mouse.accepted = false
cursorShape: Qt.PointingHandCursor
cursorShape: Qt.PointingHandCursor
}
@@ -12,6 +12,7 @@ Button {
id: root
property bool toggled
property string buttonText
property bool pointingHandCursor: true
property real buttonRadius: Appearance?.rounding?.small ?? 4
property real buttonRadiusPressed: buttonRadius
property real buttonEffectiveRadius: root.down ? root.buttonRadiusPressed : root.buttonRadius
@@ -58,7 +59,7 @@ Button {
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
cursorShape: root.pointingHandCursor ? Qt.PointingHandCursor : Qt.ArrowCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onPressed: (event) => {
if(event.button === Qt.RightButton) {
@@ -5,7 +5,7 @@ Item {
id: root
enum CornerEnum { TopLeft, TopRight, BottomLeft, BottomRight }
property var corner: RoundCorner.CornerEnum.TopLeft // Default to TopLeft
property var corner: RoundCorner.CornerEnum.TopLeft
property int implicitSize: 25
property color color: "#000000"
@@ -0,0 +1,9 @@
import qs.modules.common
import QtQuick
import QtQuick.Controls.Material
import QtQuick.Controls
ProgressBar {
indeterminate: true
Material.accent: Appearance.colors.colPrimary
}
@@ -14,6 +14,8 @@ ListView {
property int dragIndex: -1
property real dragDistance: 0
property bool popin: true
property bool animateAppearance: true
property bool animateMovement: false
// Accumulated scroll destination so wheel deltas stack while animating
property real scrollTargetY: 0
@@ -66,17 +68,17 @@ ListView {
}
add: Transition {
animations: [
animations: animateAppearance ? [
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
properties: popin ? "opacity,scale" : "opacity",
from: 0,
to: 1,
}),
]
] : []
}
addDisplaced: Transition {
animations: [
animations: animateAppearance ? [
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
property: "y",
}),
@@ -84,46 +86,46 @@ ListView {
properties: popin ? "opacity,scale" : "opacity",
to: 1,
}),
]
] : []
}
// displaced: Transition {
// animations: [
// Appearance?.animation.elementMove.numberAnimation.createObject(this, {
// property: "y",
// }),
// Appearance?.animation.elementMove.numberAnimation.createObject(this, {
// properties: "opacity,scale",
// to: 1,
// }),
// ]
// }
displaced: Transition {
animations: root.animateMovement ? [
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
property: "y",
}),
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
properties: "opacity,scale",
to: 1,
}),
] : []
}
// move: Transition {
// animations: [
// Appearance?.animation.elementMove.numberAnimation.createObject(this, {
// property: "y",
// }),
// Appearance?.animation.elementMove.numberAnimation.createObject(this, {
// properties: "opacity,scale",
// to: 1,
// }),
// ]
// }
// moveDisplaced: Transition {
// animations: [
// Appearance?.animation.elementMove.numberAnimation.createObject(this, {
// property: "y",
// }),
// Appearance?.animation.elementMove.numberAnimation.createObject(this, {
// properties: "opacity,scale",
// to: 1,
// }),
// ]
// }
move: Transition {
animations: root.animateMovement ? [
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
property: "y",
}),
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
properties: "opacity,scale",
to: 1,
}),
] : []
}
moveDisplaced: Transition {
animations: root.animateMovement ? [
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
property: "y",
}),
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
properties: "opacity,scale",
to: 1,
}),
] : []
}
remove: Transition {
animations: [
animations: animateAppearance ? [
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
property: "x",
to: root.width + root.removeOvershoot,
@@ -132,12 +134,12 @@ ListView {
property: "opacity",
to: 0,
})
]
] : []
}
// This is movement when something is removed, not removing animation!
removeDisplaced: Transition {
animations: [
animations: animateAppearance ? [
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
property: "y",
}),
@@ -145,6 +147,6 @@ ListView {
properties: "opacity,scale",
to: 1,
}),
]
] : []
}
}
@@ -0,0 +1,26 @@
import QtQuick
import QtQuick.Controls
import qs.modules.common
import qs.modules.common.functions
ScrollBar {
id: root
policy: ScrollBar.AsNeeded
contentItem: Rectangle {
implicitWidth: 4
implicitHeight: root.visualSize
radius: width / 2
color: Appearance.colors.colOnSurfaceVariant
opacity: root.policy === ScrollBar.AlwaysOn || (root.active && root.size < 1.0) ? 0.5 : 0
Behavior on opacity {
NumberAnimation {
duration: 350
easing.type: Appearance.animation.elementMoveFast.type
easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve
}
}
}
}
@@ -6,6 +6,7 @@ import QtQuick.Controls
* Does not include visual layout, but includes the easily neglected colors.
*/
TextInput {
color: Appearance.colors.colOnLayer1
renderType: Text.NativeRendering
selectedTextColor: Appearance.m3colors.m3onSecondaryContainer
selectionColor: Appearance.colors.colSecondaryContainer
@@ -10,7 +10,7 @@ import qs.modules.common.widgets
Item {
id: root
property real padding: 6
property real padding: 8
property alias colBackground: background.color
default property alias data: toolbarLayout.data
implicitWidth: background.implicitWidth
@@ -23,13 +23,14 @@ Item {
Rectangle {
id: background
anchors.centerIn: parent
color: Appearance.colors.colLayer2
color: Appearance.m3colors.m3surfaceContainer // Needs to be opaque
implicitHeight: toolbarLayout.implicitHeight + root.padding * 2
implicitWidth: toolbarLayout.implicitWidth + root.padding * 2
radius: Appearance.rounding.full
RowLayout {
id: toolbarLayout
spacing: 4
anchors {
fill: parent
margins: root.padding
@@ -4,7 +4,5 @@ import qs.modules.common
RippleButton {
Layout.fillHeight: true
Layout.topMargin: 2
Layout.bottomMargin: 2
buttonRadius: Appearance.rounding.full
}
@@ -10,8 +10,6 @@ TextField {
property alias colBackground: background.color
Layout.fillHeight: true
Layout.topMargin: 2
Layout.bottomMargin: 2
implicitWidth: 200
padding: 10
@@ -0,0 +1,90 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.modules.common
import qs.modules.common.functions
import qs.modules.common.widgets
Rectangle {
id: root
property bool show: false
default property alias data: contentColumn.data
property real backgroundHeight: 600
property real backgroundAnimationMovementDistance: 60
signal dismiss()
Keys.onPressed: (event) => {
if (event.key === Qt.Key_Escape) {
root.dismiss();
event.accepted = true;
}
}
color: root.show ? Appearance.colors.colScrim : ColorUtils.transparentize(Appearance.colors.colScrim)
Behavior on color {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
}
visible: dialogBackground.implicitHeight > 0
onShowChanged: {
dialogBackgroundHeightAnimation.easing.bezierCurve = (show ? Appearance.animationCurves.emphasizedDecel : Appearance.animationCurves.emphasizedAccel)
dialogBackground.implicitHeight = show ? backgroundHeight : 0
}
radius: Appearance.rounding.screenRounding - Appearance.sizes.hyprlandGapsOut + 1
MouseArea { // Clicking outside the dialog should dismiss
anchors.fill: parent
acceptedButtons: Qt.AllButtons
hoverEnabled: true
onPressed: root.dismiss()
}
Rectangle {
id: dialogBackground
anchors.horizontalCenter: parent.horizontalCenter
radius: Appearance.rounding.large
color: Appearance.m3colors.m3surfaceContainerHigh // Use opaque version of layer3
property real targetY: root.height / 2 - root.backgroundHeight / 2
y: root.show ? targetY : (targetY - root.backgroundAnimationMovementDistance)
implicitWidth: 350
implicitHeight: 0
Behavior on implicitHeight {
NumberAnimation {
id: dialogBackgroundHeightAnimation
duration: Appearance.animation.elementMoveFast.duration
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.animationCurves.emphasizedDecel
}
}
Behavior on y {
NumberAnimation {
duration: dialogBackgroundHeightAnimation.duration
easing.type: dialogBackgroundHeightAnimation.easing.type
easing.bezierCurve: dialogBackgroundHeightAnimation.easing.bezierCurve
}
}
MouseArea { // So clicking inside the dialog won't dismiss
anchors.fill: parent
acceptedButtons: Qt.AllButtons
hoverEnabled: true
}
ColumnLayout {
id: contentColumn
anchors {
fill: parent
margins: dialogBackground.radius
}
spacing: 16
opacity: root.show ? 1 : 0
Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
}
}
}
@@ -0,0 +1,15 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.modules.common
import qs.modules.common.functions
import qs.modules.common.widgets
RowLayout {
id: root
spacing: 4
// These shouldn't be needed but it would be a terrible waste of space to follow the spec
Layout.margins: -8
Layout.topMargin: 0
}
@@ -0,0 +1,16 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.modules.common
import qs.modules.common.functions
import qs.modules.common.widgets
Rectangle {
implicitHeight: 1
color: Appearance.colors.colOutline
Layout.fillWidth: true
Layout.leftMargin: -Appearance.rounding.large
Layout.rightMargin: -Appearance.rounding.large
Layout.topMargin: -8
Layout.bottomMargin: -8
}
@@ -0,0 +1,13 @@
import QtQuick
import Quickshell
import qs.modules.common
import qs.modules.common.functions
import qs.modules.common.widgets
StyledText {
text: "Dialog Title"
font {
pixelSize: Appearance.font.pixelSize.title
family: Appearance.font.family.title
}
}