mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-04-12 16:52:10 -04:00
meta: integrate wallpaper, FileBrowser, StateLayer
- A lot of this is implements patterns implemented by soramannew's caelestia-shell
This commit is contained in:
@@ -2,7 +2,7 @@ import QtQuick
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
StyledRect {
|
||||
id: root
|
||||
|
||||
property string iconName: ""
|
||||
@@ -18,7 +18,7 @@ Rectangle {
|
||||
width: buttonSize
|
||||
height: buttonSize
|
||||
radius: circular ? buttonSize / 2 : Theme.cornerRadius
|
||||
color: mouseArea.containsMouse ? hoverColor : backgroundColor
|
||||
color: backgroundColor
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
@@ -27,21 +27,10 @@ Rectangle {
|
||||
color: root.iconColor
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
StateLayer {
|
||||
stateColor: Theme.primary
|
||||
cornerRadius: root.radius
|
||||
onClicked: root.clicked()
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,18 +1,48 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
|
||||
Text {
|
||||
StyledText {
|
||||
id: icon
|
||||
|
||||
property alias name: icon.text
|
||||
property alias size: icon.font.pixelSize
|
||||
property alias color: icon.color
|
||||
property bool filled: false
|
||||
property real fill: filled ? 1 : 0
|
||||
property int grade: Theme.isLightMode ? 0 : -25
|
||||
property int weight: filled ? 500 : 400
|
||||
|
||||
font.family: "Material Symbols Rounded"
|
||||
font.pixelSize: Theme.iconSize
|
||||
font.weight: filled ? Font.Medium : Font.Normal
|
||||
font.pixelSize: Appearance.fontSize.normal
|
||||
font.weight: weight
|
||||
color: Theme.surfaceText
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
// Material Icons variable font axes support
|
||||
font.variableAxes: ({
|
||||
"FILL": fill.toFixed(1),
|
||||
"GRAD": grade,
|
||||
"opsz": 24,
|
||||
"wght": weight
|
||||
})
|
||||
|
||||
// Smooth transitions for variable axes
|
||||
Behavior on fill {
|
||||
NumberAnimation {
|
||||
duration: Appearance.anim.durations.quick
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.standard
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on weight {
|
||||
NumberAnimation {
|
||||
duration: Appearance.anim.durations.quick
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.standard
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ Item {
|
||||
spacing: Theme.spacingM
|
||||
|
||||
// Value display
|
||||
Text {
|
||||
StyledText {
|
||||
text: slider.value + slider.unit
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: slider.enabled ? Theme.surfaceText : Theme.surfaceVariantText
|
||||
@@ -49,7 +49,7 @@ Item {
|
||||
}
|
||||
|
||||
// Slider track
|
||||
Rectangle {
|
||||
StyledRect {
|
||||
id: sliderTrack
|
||||
|
||||
property int leftIconWidth: slider.leftIcon.length > 0 ? Theme.iconSize : 0
|
||||
@@ -62,7 +62,7 @@ Item {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
// Fill
|
||||
Rectangle {
|
||||
StyledRect {
|
||||
id: sliderFill
|
||||
|
||||
width: parent.width * ((slider.value - slider.minimum) / (slider.maximum - slider.minimum))
|
||||
@@ -72,8 +72,8 @@ Item {
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: 150
|
||||
easing.type: Easing.OutCubic
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
@@ -81,7 +81,7 @@ Item {
|
||||
}
|
||||
|
||||
// Draggable handle
|
||||
Rectangle {
|
||||
StyledRect {
|
||||
id: sliderHandle
|
||||
|
||||
width: 18
|
||||
@@ -95,7 +95,7 @@ Item {
|
||||
scale: sliderMouseArea.containsMouse || sliderMouseArea.pressed ? 1.2 : 1
|
||||
|
||||
// Handle glow effect when active
|
||||
Rectangle {
|
||||
StyledRect {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width + 4
|
||||
height: parent.height + 4
|
||||
@@ -104,19 +104,12 @@ Item {
|
||||
border.color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3)
|
||||
border.width: 2
|
||||
visible: sliderMouseArea.containsMouse && slider.enabled
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: 150
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: 150
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Widgets
|
||||
import qs.Common
|
||||
|
||||
IconImage {
|
||||
id: root
|
||||
|
||||
property string colorOverride: ""
|
||||
property real brightnessOverride: 0.5
|
||||
property real contrastOverride: 1
|
||||
|
||||
smooth: true
|
||||
asynchronous: true
|
||||
layer.enabled: colorOverride !== ""
|
||||
|
||||
Process {
|
||||
running: true
|
||||
command: ["sh", "-c", ". /etc/os-release && echo $LOGO"]
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: () => {
|
||||
root.source = Quickshell.iconPath(this.text.trim());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
layer.effect: MultiEffect {
|
||||
colorization: 1
|
||||
colorizationColor: colorOverride
|
||||
brightness: brightnessOverride
|
||||
contrast: contrastOverride
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: toggle
|
||||
@@ -16,13 +17,25 @@ Item {
|
||||
width: text ? parent.width : 48
|
||||
height: text ? 60 : 24
|
||||
|
||||
Rectangle {
|
||||
StyledRect {
|
||||
id: background
|
||||
|
||||
anchors.fill: parent
|
||||
radius: toggle.text ? Theme.cornerRadius : 0
|
||||
color: toggle.text ? (toggleArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08)) : "transparent"
|
||||
color: toggle.text ? Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08) : "transparent"
|
||||
visible: toggle.text
|
||||
|
||||
StateLayer {
|
||||
visible: toggle.text
|
||||
stateColor: Theme.primary
|
||||
cornerRadius: parent.radius
|
||||
onClicked: {
|
||||
toggle.checked = !toggle.checked;
|
||||
toggle.clicked();
|
||||
toggle.toggled(toggle.checked);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Row {
|
||||
@@ -40,16 +53,15 @@ Item {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Text {
|
||||
StyledText {
|
||||
text: toggle.text
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.pixelSize: Appearance.fontSize.normal
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
Text {
|
||||
StyledText {
|
||||
text: toggle.description
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.pixelSize: Appearance.fontSize.small
|
||||
color: Theme.surfaceVariantText
|
||||
wrapMode: Text.WordWrap
|
||||
width: Math.min(implicitWidth, toggle.width - 120)
|
||||
@@ -60,7 +72,7 @@ Item {
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
StyledRect {
|
||||
id: toggleTrack
|
||||
|
||||
width: toggle.text ? 48 : parent.width
|
||||
@@ -72,7 +84,7 @@ Item {
|
||||
color: toggle.checked ? Theme.primary : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
|
||||
opacity: toggle.toggling ? 0.6 : 1
|
||||
|
||||
Rectangle {
|
||||
StyledRect {
|
||||
id: toggleHandle
|
||||
|
||||
width: 20
|
||||
@@ -82,7 +94,7 @@ Item {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
x: toggle.checked ? parent.width - width - 2 : 2
|
||||
|
||||
Rectangle {
|
||||
StyledRect {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width + 2
|
||||
height: parent.height + 2
|
||||
@@ -95,28 +107,26 @@ Item {
|
||||
|
||||
Behavior on x {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
duration: Appearance.anim.durations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.emphasizedDecel
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: toggleArea
|
||||
|
||||
anchors.fill: toggle.text ? toggle : toggleTrack
|
||||
hoverEnabled: true
|
||||
cursorShape: toggle.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
enabled: toggle.enabled
|
||||
onClicked: {
|
||||
toggle.checked = !toggle.checked;
|
||||
toggle.clicked();
|
||||
toggle.toggled(toggle.checked);
|
||||
StateLayer {
|
||||
disabled: !toggle.enabled
|
||||
stateColor: Theme.primary
|
||||
cornerRadius: parent.radius
|
||||
onClicked: {
|
||||
toggle.checked = !toggle.checked;
|
||||
toggle.clicked();
|
||||
toggle.toggled(toggle.checked);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
281
Widgets/FileBrowser.qml
Normal file
281
Widgets/FileBrowser.qml
Normal file
@@ -0,0 +1,281 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtCore
|
||||
import Qt.labs.folderlistmodel
|
||||
import Quickshell.Io
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
import qs.Modals
|
||||
|
||||
DankModal {
|
||||
id: fileBrowser
|
||||
|
||||
signal fileSelected(string path)
|
||||
|
||||
property string homeDir: StandardPaths.writableLocation(StandardPaths.HomeLocation)
|
||||
property string currentPath: ""
|
||||
property var fileExtensions: ["*.*"]
|
||||
property string browserTitle: "Select File"
|
||||
property string browserIcon: "folder_open"
|
||||
property string browserType: "generic" // "wallpaper" or "profile" for last path memory
|
||||
|
||||
FolderListModel {
|
||||
id: folderModel
|
||||
showDirsFirst: true
|
||||
showDotAndDotDot: false
|
||||
showHidden: false
|
||||
nameFilters: fileExtensions
|
||||
showFiles: true
|
||||
showDirs: true
|
||||
folder: currentPath ? "file://" + currentPath : "file://" + homeDir
|
||||
}
|
||||
|
||||
function getLastPath() {
|
||||
var lastPath = "";
|
||||
if (browserType === "wallpaper") {
|
||||
lastPath = Prefs.wallpaperLastPath;
|
||||
} else if (browserType === "profile") {
|
||||
lastPath = Prefs.profileLastPath;
|
||||
}
|
||||
|
||||
// Check if last path exists, otherwise use home
|
||||
if (lastPath && lastPath !== "") {
|
||||
// TODO: Could add directory existence check here
|
||||
return lastPath;
|
||||
}
|
||||
return homeDir;
|
||||
}
|
||||
|
||||
function saveLastPath(path) {
|
||||
if (browserType === "wallpaper") {
|
||||
Prefs.wallpaperLastPath = path;
|
||||
} else if (browserType === "profile") {
|
||||
Prefs.profileLastPath = path;
|
||||
}
|
||||
Prefs.saveSettings();
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
currentPath = getLastPath();
|
||||
}
|
||||
|
||||
width: 800
|
||||
height: 600
|
||||
keyboardFocus: "ondemand"
|
||||
enableShadow: true
|
||||
visible: false
|
||||
|
||||
onBackgroundClicked: visible = false
|
||||
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
// Use last path or home directory when opening
|
||||
var startPath = getLastPath();
|
||||
console.log("Opening file browser, setting path to:", startPath);
|
||||
currentPath = startPath;
|
||||
}
|
||||
}
|
||||
|
||||
onCurrentPathChanged: {
|
||||
console.log("Current path changed to:", currentPath);
|
||||
console.log("Model count:", folderModel.count);
|
||||
// Log first few files to debug
|
||||
for (var i = 0; i < Math.min(3, folderModel.count); i++) {
|
||||
console.log("File", i, ":", folderModel.get(i, "fileName"));
|
||||
}
|
||||
}
|
||||
|
||||
function navigateUp() {
|
||||
var path = currentPath;
|
||||
|
||||
// Don't go above home directory
|
||||
if (path === homeDir) {
|
||||
console.log("Already at home directory, can't go up");
|
||||
return;
|
||||
}
|
||||
|
||||
var lastSlash = path.lastIndexOf('/');
|
||||
if (lastSlash > 0) {
|
||||
var newPath = path.substring(0, lastSlash);
|
||||
// Make sure we don't go above home (check if newPath starts with homeDir)
|
||||
if (newPath.startsWith(homeDir)) {
|
||||
console.log("Navigating up from", path, "to", newPath);
|
||||
currentPath = newPath;
|
||||
saveLastPath(newPath);
|
||||
} else {
|
||||
console.log("Would go above home directory, stopping at", homeDir);
|
||||
currentPath = homeDir;
|
||||
saveLastPath(homeDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function navigateTo(path) {
|
||||
currentPath = path;
|
||||
saveLastPath(path); // Save the path when navigating
|
||||
}
|
||||
|
||||
content: Component {
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
spacing: Theme.spacingS
|
||||
|
||||
// Header
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: browserIcon
|
||||
size: Theme.iconSizeLarge
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: browserTitle
|
||||
font.pixelSize: Theme.fontSizeXLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width - 200
|
||||
height: 1
|
||||
}
|
||||
|
||||
// Close button
|
||||
DankActionButton {
|
||||
iconName: "close"
|
||||
iconSize: Theme.iconSizeSmall
|
||||
iconColor: Theme.surfaceText
|
||||
onClicked: fileBrowser.visible = false
|
||||
}
|
||||
}
|
||||
|
||||
// Current path display and navigation
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
StyledRect {
|
||||
width: 32
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: mouseArea.containsMouse && currentPath !== homeDir ? Theme.surfaceVariant : "transparent"
|
||||
opacity: currentPath !== homeDir ? 1.0 : 0.0
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "arrow_back"
|
||||
size: Theme.iconSizeSmall
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: currentPath !== homeDir
|
||||
cursorShape: currentPath !== homeDir ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
enabled: currentPath !== homeDir
|
||||
onClicked: navigateUp()
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Current folder: " + fileBrowser.currentPath
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
width: parent.width - 40 - Theme.spacingS
|
||||
elide: Text.ElideMiddle
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
maximumLineCount: 1
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
}
|
||||
|
||||
// File grid
|
||||
ScrollView {
|
||||
width: parent.width
|
||||
height: parent.height - 80
|
||||
clip: true
|
||||
|
||||
GridView {
|
||||
id: fileGrid
|
||||
|
||||
cellWidth: 150
|
||||
cellHeight: 130
|
||||
|
||||
model: folderModel
|
||||
|
||||
delegate: StyledRect {
|
||||
width: 140
|
||||
height: 120
|
||||
radius: Theme.cornerRadius
|
||||
color: mouseArea.containsMouse ? Theme.surfaceVariant : "transparent"
|
||||
border.color: Theme.outline
|
||||
border.width: mouseArea.containsMouse ? 1 : 0
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
// Image preview or folder icon
|
||||
Item {
|
||||
width: 80
|
||||
height: 60
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
Image {
|
||||
anchors.fill: parent
|
||||
source: !model.fileIsDir ? model.fileURL : ""
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
visible: !model.fileIsDir
|
||||
asynchronous: true
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: model.fileIsDir ? "folder" : "description"
|
||||
size: Theme.iconSizeLarge
|
||||
color: Theme.primary
|
||||
visible: model.fileIsDir
|
||||
}
|
||||
}
|
||||
|
||||
// File name
|
||||
StyledText {
|
||||
text: model.fileName || ""
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
width: 120
|
||||
elide: Text.ElideMiddle
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
maximumLineCount: 2
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
if (model.fileIsDir) {
|
||||
navigateTo(model.filePath);
|
||||
} else {
|
||||
fileSelected(model.filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property Item target
|
||||
property int direction: Anims.direction.fadeOnly
|
||||
|
||||
function show() { _apply(true) }
|
||||
function hide() { _apply(false) }
|
||||
|
||||
function _apply(showing) {
|
||||
const off = Anims.slidePx
|
||||
let fromX = 0
|
||||
let toX = 0
|
||||
switch(direction) {
|
||||
case Anims.direction.fromLeft: fromX = -off; toX = 0; break
|
||||
case Anims.direction.fromRight: fromX = off; toX = 0; break
|
||||
default: fromX = 0; toX = 0;
|
||||
}
|
||||
|
||||
if (showing) {
|
||||
target.x = fromX
|
||||
target.opacity = 0
|
||||
target.visible = true
|
||||
animX.from = fromX; animX.to = toX
|
||||
animO.from = 0; animO.to = 1
|
||||
} else {
|
||||
animX.from = target.x; animX.to = (direction === Anims.direction.fromLeft ? -off :
|
||||
direction === Anims.direction.fromRight ? off : 0)
|
||||
animO.from = target.opacity; animO.to = 0
|
||||
}
|
||||
seq.restart()
|
||||
}
|
||||
|
||||
SequentialAnimation {
|
||||
id: seq
|
||||
ParallelAnimation {
|
||||
NumberAnimation {
|
||||
id: animX
|
||||
target: root.target
|
||||
property: "x"
|
||||
duration: Anims.durMed
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
NumberAnimation {
|
||||
id: animO
|
||||
target: root.target
|
||||
property: "opacity"
|
||||
duration: Anims.durShort
|
||||
}
|
||||
}
|
||||
ScriptAction { script: if (root.target.opacity === 0) root.target.visible = false }
|
||||
}
|
||||
}
|
||||
98
Widgets/StateLayer.qml
Normal file
98
Widgets/StateLayer.qml
Normal file
@@ -0,0 +1,98 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
MouseArea {
|
||||
id: root
|
||||
|
||||
property bool disabled: false
|
||||
property color stateColor: Theme.surfaceText
|
||||
property real cornerRadius: parent?.radius ?? Appearance.rounding.normal
|
||||
|
||||
anchors.fill: parent
|
||||
cursorShape: disabled ? undefined : Qt.PointingHandCursor
|
||||
hoverEnabled: true
|
||||
|
||||
onPressed: event => {
|
||||
if (disabled) return;
|
||||
|
||||
rippleAnimation.x = event.x;
|
||||
rippleAnimation.y = event.y;
|
||||
|
||||
const dist = (ox, oy) => ox * ox + oy * oy;
|
||||
rippleAnimation.radius = Math.sqrt(Math.max(
|
||||
dist(event.x, event.y),
|
||||
dist(event.x, height - event.y),
|
||||
dist(width - event.x, event.y),
|
||||
dist(width - event.x, height - event.y)
|
||||
));
|
||||
|
||||
rippleAnimation.restart();
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: hoverLayer
|
||||
anchors.fill: parent
|
||||
radius: root.cornerRadius
|
||||
color: Qt.rgba(root.stateColor.r, root.stateColor.g, root.stateColor.b,
|
||||
root.disabled ? 0 :
|
||||
root.pressed ? 0.12 :
|
||||
root.containsMouse ? 0.08 : 0)
|
||||
|
||||
Rectangle {
|
||||
id: ripple
|
||||
radius: width / 2
|
||||
color: root.stateColor
|
||||
opacity: 0
|
||||
|
||||
transform: Translate {
|
||||
x: -ripple.width / 2
|
||||
y: -ripple.height / 2
|
||||
}
|
||||
}
|
||||
|
||||
// Clip ripple to container bounds
|
||||
clip: true
|
||||
}
|
||||
|
||||
SequentialAnimation {
|
||||
id: rippleAnimation
|
||||
|
||||
property real x
|
||||
property real y
|
||||
property real radius
|
||||
|
||||
PropertyAction {
|
||||
target: ripple
|
||||
property: "x"
|
||||
value: rippleAnimation.x
|
||||
}
|
||||
PropertyAction {
|
||||
target: ripple
|
||||
property: "y"
|
||||
value: rippleAnimation.y
|
||||
}
|
||||
PropertyAction {
|
||||
target: ripple
|
||||
property: "opacity"
|
||||
value: 0.12
|
||||
}
|
||||
NumberAnimation {
|
||||
target: ripple
|
||||
properties: "width,height"
|
||||
from: 0
|
||||
to: rippleAnimation.radius * 2
|
||||
duration: Appearance.anim.durations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.standardDecel
|
||||
}
|
||||
NumberAnimation {
|
||||
target: ripple
|
||||
property: "opacity"
|
||||
to: 0
|
||||
duration: Appearance.anim.durations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.standard
|
||||
}
|
||||
}
|
||||
}
|
||||
37
Widgets/StyledRect.qml
Normal file
37
Widgets/StyledRect.qml
Normal file
@@ -0,0 +1,37 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
color: "transparent"
|
||||
radius: Appearance.rounding.normal
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Appearance.anim.durations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.standard
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on radius {
|
||||
NumberAnimation {
|
||||
duration: Appearance.anim.durations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.standard
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Appearance.anim.durations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.standard
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
35
Widgets/StyledText.qml
Normal file
35
Widgets/StyledText.qml
Normal file
@@ -0,0 +1,35 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
|
||||
Text {
|
||||
id: root
|
||||
|
||||
color: Theme.surfaceText
|
||||
font.pixelSize: Appearance.fontSize.normal
|
||||
wrapMode: Text.WordWrap
|
||||
elide: Text.ElideRight
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
// Font rendering improvements for crisp text
|
||||
renderType: Text.NativeRendering
|
||||
textFormat: Text.PlainText
|
||||
antialiasing: true
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Appearance.anim.durations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.standard
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Appearance.anim.durations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.standard
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user