forked from Shinonome/dots-hyprland
Merge branch 'main' into background-clock-setting
This commit is contained in:
@@ -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, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user