Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eba5458f15
|
||
|
|
068319a4cb
|
||
|
|
bdb95af7a1
|
@@ -40,7 +40,5 @@ jobs:
|
||||
run: |
|
||||
sed -i 's/-mno-direct-extern-access //g' build/compile_commands.json
|
||||
set -o pipefail
|
||||
run-clang-tidy -p build -j "$(nproc)" \
|
||||
-source-filter="^$(pwd)/Plugins/" \
|
||||
-header-filter=".*Plugins/.*" \
|
||||
-removed-arg=-mno-direct-extern-access
|
||||
run-clang-tidy -p build -j $(nproc) -header-filter="Plugins/.*" 2>&1 | \
|
||||
grep -v 'Suppressed\|non-user\|non-system\|unknown argument.*mno-direct\|Found compiler error\|warnings generated\.\|^Use -header-filter'
|
||||
|
||||
+10
-22
@@ -1,35 +1,23 @@
|
||||
**/__pycache__/
|
||||
./result/
|
||||
.pyre/
|
||||
.cache/
|
||||
.venv/
|
||||
.qmlls.ini
|
||||
build/
|
||||
compile_commands.json
|
||||
testpython
|
||||
pkg/
|
||||
*.tar.*
|
||||
uv.lock
|
||||
.qtcreator/
|
||||
dist/
|
||||
**/target/
|
||||
**/test-plugins/
|
||||
**/Charts/
|
||||
network-dev/
|
||||
**/zshell.dist/
|
||||
|
||||
# Qml
|
||||
.qtcreator/
|
||||
.qmlls.ini
|
||||
|
||||
# CMake compile artifacts
|
||||
.cache/
|
||||
compile_commands.json
|
||||
build/
|
||||
|
||||
# Python
|
||||
uv.lock
|
||||
testpython
|
||||
.venv/
|
||||
**/__pycache__/
|
||||
|
||||
# Nuitka compile
|
||||
**/zshell.pyi
|
||||
**/zshell.cpython-*
|
||||
**/zshell.build/
|
||||
|
||||
# Agents stuff
|
||||
**/zshell.dist/
|
||||
.opencode/
|
||||
run-agent.sh
|
||||
AGENTS.md
|
||||
|
||||
@@ -50,7 +50,6 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
set(QT_NO_PRIVATE_MODULE_WARNING ON)
|
||||
set(ENABLE_MODULES "plugin;shell;cli;m3shapes" CACHE STRING "Modules to build/install")
|
||||
|
||||
set(INSTALL_LIBDIR "${CMAKE_INSTALL_LIBDIR}/ZShell" CACHE STRING "Library install dir")
|
||||
|
||||
@@ -2,57 +2,55 @@ import QtQuick
|
||||
import ZShell.Config
|
||||
import qs.Services
|
||||
|
||||
Item {
|
||||
CustomClippingRect {
|
||||
id: track
|
||||
|
||||
required property var scrollBar
|
||||
required property var mouseArea
|
||||
|
||||
ClipRect {
|
||||
radius: Tokens.rounding.full
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
implicitHeight: handle.implicitHeight
|
||||
radius: Tokens.rounding.full
|
||||
|
||||
CustomRect {
|
||||
id: handle
|
||||
|
||||
anchors.bottom: parent.bottom
|
||||
implicitHeight: handle.implicitHeight
|
||||
|
||||
CustomRect {
|
||||
id: handle
|
||||
|
||||
anchors.bottom: parent.bottom
|
||||
color: Colors.palette.m3secondary
|
||||
implicitWidth: track.scrollBar.width * track.scrollBar.effectiveSize
|
||||
implicitHeight: track.mouseArea.pressed || track.mouseArea.containsMouse ? Tokens.padding.extraSmall * 2 : Tokens.padding.extraSmall
|
||||
opacity: {
|
||||
if (!track.scrollBar.enabled)
|
||||
return 0;
|
||||
if (track.scrollBar.size === 1)
|
||||
return 0;
|
||||
if (track.mouseArea.pressed)
|
||||
return 1;
|
||||
if (track.mouseArea.containsMouse)
|
||||
return 0.8;
|
||||
if (track.scrollBar.policy === CustomScrollBar.AlwaysOn || track.scrollBar.shouldBeActive)
|
||||
return 0.6;
|
||||
color: Colors.palette.m3secondary
|
||||
implicitWidth: track.scrollBar.width * track.scrollBar.effectiveSize
|
||||
implicitHeight: track.mouseArea.pressed || track.mouseArea.containsMouse ? Tokens.padding.extraSmall * 2 : Tokens.padding.extraSmall
|
||||
opacity: {
|
||||
if (!track.scrollBar.enabled)
|
||||
return 0;
|
||||
}
|
||||
radius: Tokens.rounding.full
|
||||
x: track.scrollBar.reversed ? track.scrollBar.width * (1 + track.scrollBar.nonAnimY) * track.scrollBar.travelScale : track.scrollBar.width * track.scrollBar.nonAnimY * track.scrollBar.travelScale
|
||||
if (track.scrollBar.size === 1)
|
||||
return 0;
|
||||
if (track.mouseArea.pressed)
|
||||
return 1;
|
||||
if (track.mouseArea.containsMouse)
|
||||
return 0.8;
|
||||
if (track.scrollBar.policy === CustomScrollBar.AlwaysOn || track.scrollBar.shouldBeActive)
|
||||
return 0.6;
|
||||
return 0;
|
||||
}
|
||||
radius: Tokens.rounding.full
|
||||
x: track.scrollBar.reversed ? track.scrollBar.width * (1 + track.scrollBar.nonAnimY) * track.scrollBar.travelScale : track.scrollBar.width * track.scrollBar.nonAnimY * track.scrollBar.travelScale
|
||||
|
||||
Behavior on implicitHeight {
|
||||
Anim {}
|
||||
}
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
Behavior on implicitHeight {
|
||||
Anim {}
|
||||
}
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
acceptedButtons: Qt.NoButton
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
hoverEnabled: true
|
||||
}
|
||||
MouseArea {
|
||||
acceptedButtons: Qt.NoButton
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
hoverEnabled: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ TextField {
|
||||
implicitHeight: contentHeight + topPadding + bottomPadding
|
||||
implicitWidth: contentWidth + leftPadding + rightPadding
|
||||
placeholderTextColor: Colors.palette.m3onSurfaceVariant // No anim cause placeholder is custom
|
||||
renderType: TextField.QtRendering
|
||||
renderType: echoMode === TextField.Password ? TextField.QtRendering : TextField.QtRendering
|
||||
selectedTextColor: color
|
||||
selectionColor: Qt.alpha(Colors.palette.m3primary, 0.4)
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
|
||||
@@ -30,7 +30,7 @@ CustomFlickable {
|
||||
type: Anim.SlowEffects
|
||||
}
|
||||
}
|
||||
layer.textureSize: Qt.size(width * dpr, height * dpr)
|
||||
// layer.textureSize: Qt.size(width * dpr, height * dpr)
|
||||
layer.effect: Mask {
|
||||
maskSource: mask
|
||||
|
||||
|
||||
@@ -2,57 +2,55 @@ import QtQuick
|
||||
import ZShell.Config
|
||||
import qs.Services
|
||||
|
||||
Item {
|
||||
CustomClippingRect {
|
||||
id: track
|
||||
|
||||
required property var scrollBar
|
||||
required property var mouseArea
|
||||
|
||||
ClipRect {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
implicitWidth: handle.implicitWidth
|
||||
radius: Tokens.rounding.full
|
||||
|
||||
CustomRect {
|
||||
id: handle
|
||||
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
radius: Tokens.rounding.full
|
||||
implicitWidth: handle.implicitWidth
|
||||
|
||||
CustomRect {
|
||||
id: handle
|
||||
|
||||
anchors.right: parent.right
|
||||
color: Colors.palette.m3secondary
|
||||
implicitHeight: track.scrollBar.height * track.scrollBar.effectiveSize
|
||||
implicitWidth: track.mouseArea.pressed || track.mouseArea.containsMouse ? Tokens.padding.extraSmall * 2 : Tokens.padding.extraSmall
|
||||
opacity: {
|
||||
if (!track.scrollBar.enabled)
|
||||
return 0;
|
||||
if (track.scrollBar.size === 1)
|
||||
return 0;
|
||||
if (track.mouseArea.pressed)
|
||||
return 1;
|
||||
if (track.mouseArea.containsMouse)
|
||||
return 0.8;
|
||||
if (track.scrollBar.policy === CustomScrollBar.AlwaysOn || track.scrollBar.shouldBeActive)
|
||||
return 0.6;
|
||||
color: Colors.palette.m3secondary
|
||||
implicitHeight: track.scrollBar.height * track.scrollBar.effectiveSize
|
||||
implicitWidth: track.mouseArea.pressed || track.mouseArea.containsMouse ? Tokens.padding.extraSmall * 2 : Tokens.padding.extraSmall
|
||||
opacity: {
|
||||
if (!track.scrollBar.enabled)
|
||||
return 0;
|
||||
}
|
||||
radius: Tokens.rounding.full
|
||||
y: track.scrollBar.reversed ? track.scrollBar.height * (1 + track.scrollBar.nonAnimY) * track.scrollBar.travelScale : track.scrollBar.height * track.scrollBar.nonAnimY * track.scrollBar.travelScale
|
||||
if (track.scrollBar.size === 1)
|
||||
return 0;
|
||||
if (track.mouseArea.pressed)
|
||||
return 1;
|
||||
if (track.mouseArea.containsMouse)
|
||||
return 0.8;
|
||||
if (track.scrollBar.policy === CustomScrollBar.AlwaysOn || track.scrollBar.shouldBeActive)
|
||||
return 0.6;
|
||||
return 0;
|
||||
}
|
||||
radius: Tokens.rounding.full
|
||||
y: track.scrollBar.reversed ? track.scrollBar.height * (1 + track.scrollBar.nonAnimY) * track.scrollBar.travelScale : track.scrollBar.height * track.scrollBar.nonAnimY * track.scrollBar.travelScale
|
||||
|
||||
Behavior on implicitWidth {
|
||||
Anim {}
|
||||
}
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
Behavior on implicitWidth {
|
||||
Anim {}
|
||||
}
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
acceptedButtons: Qt.NoButton
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
hoverEnabled: true
|
||||
}
|
||||
MouseArea {
|
||||
acceptedButtons: Qt.NoButton
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
hoverEnabled: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,13 @@ Scope {
|
||||
hasBar: root.geometry.barOnBottom
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 5000
|
||||
running: true
|
||||
|
||||
onTriggered: console.log("top height:", top.exclusiveZone, "left width:", left.exclusiveZone, "right width:", right.exclusiveZone, "bottom height:", bottom.exclusiveZone)
|
||||
}
|
||||
|
||||
component ExclusionZone: CustomWindow {
|
||||
property bool hasBar
|
||||
|
||||
@@ -52,6 +59,7 @@ Scope {
|
||||
name: "Bar-Exclusion"
|
||||
screen: root.screen
|
||||
|
||||
mask: Region {}
|
||||
mask: Region {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+6
-7
@@ -19,11 +19,15 @@ import ZShell.Config
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property Item bar
|
||||
required property real borderThickness
|
||||
readonly property alias clipboard: clipboard
|
||||
readonly property alias dashboard: dashboard
|
||||
readonly property alias dashboardWrapper: dashboardWrapper
|
||||
readonly property alias dock: dock
|
||||
readonly property alias drawing: drawing
|
||||
required property var drawingItem
|
||||
required property EdgeGeometry geometry
|
||||
readonly property alias launcher: launcher
|
||||
readonly property alias notifications: notifications
|
||||
readonly property alias osd: osd
|
||||
@@ -32,19 +36,14 @@ Item {
|
||||
readonly property alias popoutsWrapper: popouts
|
||||
readonly property alias resources: resources
|
||||
readonly property alias resourcesWrapper: resourcesWrapper
|
||||
required property ShellScreen screen
|
||||
required property ScreenState screenState
|
||||
readonly property alias settings: settings
|
||||
readonly property alias settingsWrapper: settingsWrapper
|
||||
readonly property alias sidebar: sidebar
|
||||
readonly property alias toasts: toasts
|
||||
readonly property alias utilities: utilities
|
||||
|
||||
required property Item bar
|
||||
required property real borderThickness
|
||||
required property var drawingItem
|
||||
required property EdgeGeometry geometry
|
||||
required property ShellScreen screen
|
||||
required property ScreenState screenState
|
||||
|
||||
anchors.bottomMargin: geometry.insetBottom(borderThickness)
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: geometry.insetLeft(borderThickness)
|
||||
|
||||
+28
-12
@@ -27,21 +27,27 @@ ColumnLayout {
|
||||
CustomText {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
color: Colors.palette.m3secondary
|
||||
font: Tokens.font.headline.builders.large.scale(3 * root.centerScale).weight(Font.Bold).width(30).build()
|
||||
font.bold: true
|
||||
font.family: Config.appearance.font.family.clock
|
||||
font.pointSize: Math.floor(Tokens.font.size.extraLarge * 3 * root.centerScale)
|
||||
text: Time.hourStr
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
color: Colors.palette.m3primary
|
||||
font: Tokens.font.headline.builders.large.scale(3 * root.centerScale).weight(Font.Bold).width(30).build()
|
||||
font.bold: true
|
||||
font.family: Config.appearance.font.family.clock
|
||||
font.pointSize: Math.floor(Tokens.font.size.extraLarge * 3 * root.centerScale)
|
||||
text: ":"
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
color: Colors.palette.m3secondary
|
||||
font: Tokens.font.headline.builders.large.scale(3 * root.centerScale).weight(Font.Bold).width(30).build()
|
||||
font.bold: true
|
||||
font.family: Config.appearance.font.family.clock
|
||||
font.pointSize: Math.floor(Tokens.font.size.extraLarge * 3 * root.centerScale)
|
||||
text: Time.minuteStr
|
||||
}
|
||||
}
|
||||
@@ -50,11 +56,13 @@ ColumnLayout {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.topMargin: -Tokens.padding.large * 2
|
||||
color: Colors.palette.m3tertiary
|
||||
font: Tokens.font.title.builders.medium.weight(Font.DemiBold).build()
|
||||
font.bold: true
|
||||
font.family: Config.appearance.font.family.mono
|
||||
font.pointSize: Math.floor(Tokens.font.size.extraLarge * root.centerScale)
|
||||
text: Time.format("dddd, d MMMM yyyy")
|
||||
}
|
||||
|
||||
ClipRect {
|
||||
CustomClippingRect {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.topMargin: Tokens.spacing.largeIncreased * 2
|
||||
color: Colors.tPalette.m3surfaceContainer
|
||||
@@ -65,7 +73,7 @@ ColumnLayout {
|
||||
MaterialIcon {
|
||||
anchors.centerIn: parent
|
||||
color: Colors.palette.m3onSurfaceVariant
|
||||
fontStyle: Tokens.font.icon.size(root.centerWidth / 4).build()
|
||||
font.pointSize: Math.floor(root.centerWidth / 4)
|
||||
text: "person"
|
||||
}
|
||||
|
||||
@@ -80,7 +88,9 @@ ColumnLayout {
|
||||
CustomText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
color: Colors.palette.m3onSurfaceVariant
|
||||
font: Tokens.font.body.large
|
||||
font.family: Config.appearance.font.family.mono
|
||||
font.pointSize: Tokens.font.size.normal
|
||||
font.weight: 600
|
||||
text: root.greeter.username
|
||||
visible: text.length > 0
|
||||
}
|
||||
@@ -145,7 +155,8 @@ ColumnLayout {
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {}
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,6 +191,7 @@ ColumnLayout {
|
||||
|
||||
anchors.centerIn: parent
|
||||
color: root.greeter.buffer && !root.greeter.launching ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
|
||||
font.weight: 500
|
||||
text: root.greeter.launching ? "hourglass_top" : "arrow_forward"
|
||||
}
|
||||
}
|
||||
@@ -192,7 +204,8 @@ ColumnLayout {
|
||||
implicitHeight: Math.max(message.implicitHeight, stateMessage.implicitHeight)
|
||||
|
||||
Behavior on implicitHeight {
|
||||
Anim {}
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
|
||||
CustomText {
|
||||
@@ -232,10 +245,12 @@ ColumnLayout {
|
||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {}
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
Behavior on scale {
|
||||
Anim {}
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
|
||||
onMsgChanged: {
|
||||
@@ -277,7 +292,8 @@ ColumnLayout {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
color: isError ? Colors.palette.m3error : Colors.palette.m3primary
|
||||
font: Tokens.font.mono.medium
|
||||
font.family: Config.appearance.font.family.mono
|
||||
font.pointSize: Tokens.font.size.small
|
||||
horizontalAlignment: Qt.AlignHCenter
|
||||
opacity: 0
|
||||
scale: 0.7
|
||||
|
||||
@@ -20,7 +20,7 @@ BusyIndicator {
|
||||
property int animState
|
||||
property color bgColor: Colors.palette.m3secondaryContainer
|
||||
property color fgColor: Colors.palette.m3primary
|
||||
property real implicitSize: Tokens.font.body.medium.pointSize * 3
|
||||
property real implicitSize: Tokens.font.size.normal * 3
|
||||
property real internalStrokeWidth: strokeWidth
|
||||
readonly property alias progress: manager.progress
|
||||
property real strokeWidth: Tokens.padding.extraSmall * 0.8
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
import QtQuick
|
||||
import ZShell.Components
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property real radius
|
||||
property real topLeftRadius: radius
|
||||
property real topRightRadius: radius
|
||||
property real bottomRightRadius: radius
|
||||
property real bottomLeftRadius: radius
|
||||
property color color: "transparent"
|
||||
default property alias contentData: contentItem.data
|
||||
|
||||
Item {
|
||||
id: contentItem
|
||||
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
ShaderEffectSource {
|
||||
id: source
|
||||
|
||||
hideSource: true
|
||||
sourceItem: contentItem
|
||||
}
|
||||
|
||||
RoundedMask {
|
||||
anchors.fill: parent
|
||||
source: source
|
||||
topLeftRadius: root.topLeftRadius
|
||||
topRightRadius: root.topRightRadius
|
||||
bottomRightRadius: root.bottomRightRadius
|
||||
bottomLeftRadius: root.bottomLeftRadius
|
||||
color: root.color
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
CAnim {}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +1,13 @@
|
||||
import QtQuick
|
||||
import qs.Services
|
||||
|
||||
Flickable {
|
||||
id: root
|
||||
|
||||
property bool doneFakeFlick
|
||||
|
||||
interactive: !ShellState.forActive().isDrawing
|
||||
maximumFlickVelocity: 3000
|
||||
|
||||
rebound: Transition {
|
||||
onRunningChanged: {
|
||||
if (!running && !root.doneFakeFlick) {
|
||||
root.doneFakeFlick = true;
|
||||
root.flick(1, 1);
|
||||
root.flick(-1, -1);
|
||||
Qt.callLater(() => root.cancelFlick());
|
||||
}
|
||||
}
|
||||
|
||||
Anim {
|
||||
properties: "x,y"
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 10
|
||||
running: root.doneFakeFlick
|
||||
|
||||
onTriggered: {
|
||||
root.doneFakeFlick = false;
|
||||
root.returnToBounds();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +1,13 @@
|
||||
import QtQuick
|
||||
import qs.Services
|
||||
|
||||
ListView {
|
||||
id: root
|
||||
|
||||
property bool doneFakeFlick
|
||||
|
||||
interactive: !ShellState.forActive()?.isDrawing
|
||||
maximumFlickVelocity: 3000
|
||||
|
||||
rebound: Transition {
|
||||
// onRunningChanged: {
|
||||
// if (!running && !root.doneFakeFlick) {
|
||||
// root.doneFakeFlick = true;
|
||||
// root.flick(1, 1);
|
||||
// root.flick(-1, -1);
|
||||
// Qt.callLater(() => root.cancelFlick());
|
||||
// }
|
||||
// }
|
||||
|
||||
Anim {
|
||||
properties: "x,y"
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
|
||||
// Timer {
|
||||
// interval: 10
|
||||
// running: root.doneFakeFlick
|
||||
//
|
||||
// onTriggered: {
|
||||
// root.doneFakeFlick = false;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ MouseArea {
|
||||
property int scrollAccumulatedY: 0
|
||||
|
||||
function onWheel(event: WheelEvent): void {
|
||||
event.accepted = false;
|
||||
}
|
||||
|
||||
onWheel: event => {
|
||||
|
||||
@@ -6,6 +6,7 @@ Rectangle {
|
||||
color: "transparent"
|
||||
|
||||
Behavior on color {
|
||||
CAnim {}
|
||||
CAnim {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,9 +14,10 @@ Text {
|
||||
property real animateTo: 1
|
||||
|
||||
color: Colors.palette.m3onSurface
|
||||
font: Tokens.font.body.small
|
||||
font.family: Config.appearance.font.family.sans
|
||||
font.pointSize: Tokens.font.size.normal
|
||||
linkColor: Colors.palette.m3onPrimaryFixedVariant
|
||||
renderType: Text.QtRendering
|
||||
renderType: Text.NativeRendering
|
||||
textFormat: Text.PlainText
|
||||
|
||||
Behavior on color {
|
||||
@@ -27,25 +28,22 @@ Text {
|
||||
enabled: root.animate
|
||||
|
||||
SequentialAnimation {
|
||||
TAnim {
|
||||
target: root
|
||||
Anim {
|
||||
to: root.animateFrom
|
||||
type: Anim.FastEffects
|
||||
}
|
||||
|
||||
PropertyAction {
|
||||
}
|
||||
|
||||
TAnim {
|
||||
target: root
|
||||
Anim {
|
||||
to: root.animateTo
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component TAnim: Anim {
|
||||
component Anim: NumberAnimation {
|
||||
duration: root.animateDuration / 2
|
||||
easing.type: Easing.BezierSpline
|
||||
properties: root.animateProp.split(",").length > 1 ? root.animateProp : ""
|
||||
property: root.animateProp.split(",").length === 1 ? root.animateProp : ""
|
||||
target: root
|
||||
|
||||
@@ -1,305 +1,76 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Shapes
|
||||
import QtQuick.Controls
|
||||
import ZShell.Config
|
||||
import qs.Services
|
||||
|
||||
TextFieldBase {
|
||||
TextField {
|
||||
id: root
|
||||
|
||||
enum TextFieldType {
|
||||
Outlined,
|
||||
Filled
|
||||
}
|
||||
background: null
|
||||
color: Colors.palette.m3onSurface
|
||||
cursorVisible: !readOnly
|
||||
font.family: Config.appearance.font.family.sans
|
||||
font.pointSize: Tokens.font.size.smaller
|
||||
placeholderTextColor: Colors.palette.m3outline
|
||||
renderType: echoMode === TextField.Password ? TextField.QtRendering : TextField.NativeRendering
|
||||
|
||||
readonly property int clampedRadius: Math.min(horizontalPadding, Math.min(width, height) / 2, radius)
|
||||
readonly property string effectiveSupportingText: isError && errorText ? errorText : supportingText
|
||||
readonly property int filledOffset: type === CustomTextField.Filled ? Tokens.spacing.small : 0
|
||||
readonly property int leadingOffset: leadingIcon ? leadingIconLoader.width + leadingIconLoader.anchors.leftMargin : 0
|
||||
readonly property real smallFontScale: smallFontSize / font.pointSize
|
||||
readonly property int supportingTextOffset: effectiveSupportingText ? supportingTextLoader.height + Tokens.spacing.extraSmall : 0
|
||||
readonly property int trailingOffset: trailingIcon ? trailingIconLoader.width + trailingIconLoader.anchors.rightMargin : 0
|
||||
readonly property bool valid: !validate || (!text && emptyIsValid) || (validate instanceof RegExp ? validate.test(text) : !!validate(text))
|
||||
|
||||
property int horizontalPadding: Tokens.padding.large
|
||||
property int verticalPadding: Tokens.padding.large
|
||||
|
||||
property int radius: Tokens.rounding.small
|
||||
|
||||
property string supportingText
|
||||
property string trailingIcon
|
||||
property int smallFontSize: Tokens.font.label.small.pointSize
|
||||
|
||||
property int type: CustomTextField.Outlined
|
||||
property var validate // Regex or function
|
||||
property bool emptyIsValid: true
|
||||
property string errorText
|
||||
property bool isError
|
||||
property string leadingIcon
|
||||
|
||||
bottomPadding: verticalPadding + supportingTextOffset - filledOffset
|
||||
leftPadding: horizontalPadding + leadingOffset
|
||||
rightPadding: horizontalPadding + trailingOffset
|
||||
topPadding: verticalPadding + filledOffset
|
||||
|
||||
background: Loader {
|
||||
anchors.bottomMargin: root.supportingTextOffset
|
||||
anchors.fill: parent
|
||||
sourceComponent: root.type === CustomTextField.Filled ? filledComp : outlineComp
|
||||
|
||||
StateLayer {
|
||||
id: stateLayer
|
||||
|
||||
cursorShape: Qt.IBeamCursor
|
||||
enabled: !root.activeFocus
|
||||
manualPressOverride: tapHandler.pressed
|
||||
radius: root.type === CustomTextField.Outlined ? root.clampedRadius : 0
|
||||
topLeftRadius: root.clampedRadius
|
||||
topRightRadius: root.clampedRadius
|
||||
|
||||
onClicked: root.focus = true
|
||||
Behavior on color {
|
||||
CAnim {
|
||||
}
|
||||
}
|
||||
cursorDelegate: CustomRect {
|
||||
id: cursor
|
||||
|
||||
onEditingFinished: {
|
||||
if (!valid)
|
||||
isError = true;
|
||||
}
|
||||
onPressed: {
|
||||
if (stateLayer.enabled)
|
||||
stateLayer.press(stateLayer.mouseX, stateLayer.mouseY);
|
||||
}
|
||||
onTextEdited: {
|
||||
if (isError)
|
||||
isError = false;
|
||||
}
|
||||
property bool disableBlink
|
||||
|
||||
Item {
|
||||
id: contentWrapper
|
||||
color: Colors.palette.m3primary
|
||||
implicitWidth: 2
|
||||
radius: Tokens.rounding.medium
|
||||
|
||||
anchors.bottomMargin: root.supportingTextOffset
|
||||
anchors.fill: parent
|
||||
|
||||
CustomText {
|
||||
id: placeholder
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: root.leftPadding
|
||||
anchors.topMargin: Tokens.padding.extraSmall
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: root.isError ? Colors.palette.m3error : (root.activeFocus ? Colors.palette.m3primary : root.text ? Colors.palette.m3outline : root.placeholderTextColor)
|
||||
font.family: root.font.family
|
||||
font.pointSize: root.font.pointSize
|
||||
font.variableAxes: root.font.variableAxes
|
||||
font.weight: root.font.weight
|
||||
renderType: Text.QtRendering
|
||||
text: root.placeholderText
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "smallOutlined"
|
||||
when: root.type === CustomTextField.Outlined && (root.activeFocus || root.text)
|
||||
|
||||
PropertyChanges {
|
||||
placeholder.anchors.leftMargin: -(1 - root.smallFontScale) * placeholder.width / 2 + root.horizontalPadding
|
||||
placeholder.scale: root.smallFontScale
|
||||
}
|
||||
|
||||
AnchorChanges {
|
||||
anchors.verticalCenter: contentWrapper.top
|
||||
target: placeholder
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "smallFilled"
|
||||
when: root.type === CustomTextField.Filled && (root.activeFocus || root.text)
|
||||
|
||||
PropertyChanges {
|
||||
placeholder.anchors.leftMargin: -(1 - root.smallFontScale) * placeholder.width / 2 + root.horizontalPadding + root.leadingOffset
|
||||
placeholder.scale: root.smallFontScale
|
||||
}
|
||||
|
||||
AnchorChanges {
|
||||
anchors.top: contentWrapper.top
|
||||
anchors.verticalCenter: undefined
|
||||
target: placeholder
|
||||
}
|
||||
}
|
||||
]
|
||||
transitions: Transition {
|
||||
Anim {
|
||||
properties: "scale,leftMargin"
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
|
||||
AnchorAnim {
|
||||
duration: Tokens.anim.durations.expressiveDefaultEffects
|
||||
easing: Tokens.anim.expressiveDefaultEffects
|
||||
}
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
duration: Tokens.anim.durations.small
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: leadingIconLoader
|
||||
|
||||
active: root.leadingIcon
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Tokens.padding.small
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
sourceComponent: MaterialIcon {
|
||||
color: Colors.palette.m3onSurfaceVariant
|
||||
text: root.leadingIcon
|
||||
fontStyle: Tokens.font.icon.builders.medium.scale(0.9).build()
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: trailingIconLoader
|
||||
|
||||
active: root.trailingIcon
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Tokens.padding.small
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
sourceComponent: MaterialIcon {
|
||||
color: Colors.palette.m3onSurfaceVariant
|
||||
text: root.trailingIcon
|
||||
fontStyle: Tokens.font.icon.builders.medium.scale(0.9).build()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: supportingTextLoader
|
||||
|
||||
active: root.effectiveSupportingText
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: root.horizontalPadding
|
||||
|
||||
sourceComponent: CustomText {
|
||||
color: root.isError ? Colors.palette.m3error : Colors.palette.m3onSurfaceVariant
|
||||
font: Tokens.font.label.small
|
||||
text: root.effectiveSupportingText
|
||||
}
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
id: tapHandler
|
||||
}
|
||||
|
||||
Component {
|
||||
id: outlineComp
|
||||
|
||||
Shape {
|
||||
id: bg
|
||||
|
||||
asynchronous: true
|
||||
preferredRendererType: Shape.CurveRenderer
|
||||
|
||||
ShapePath {
|
||||
id: path
|
||||
|
||||
readonly property real inset: strokeWidth / 2
|
||||
readonly property real outlineGap: placeholder.width * root.smallFontScale + Tokens.spacing.small * 2
|
||||
property real outlineGapScale: root.activeFocus || root.text ? 1 : 0
|
||||
|
||||
capStyle: ShapePath.RoundCap
|
||||
fillColor: "transparent"
|
||||
startX: path.inset + root.horizontalPadding - root.clampedRadius + path.outlineGap * (1 - path.outlineGapScale) / 2 + path.outlineGap * path.outlineGapScale
|
||||
startY: path.inset
|
||||
strokeColor: root.isError ? Colors.palette.m3error : (root.activeFocus ? Colors.palette.m3primary : Colors.palette.m3outline)
|
||||
strokeWidth: root.activeFocus ? 2 : 1
|
||||
|
||||
Behavior on outlineGapScale {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
Behavior on strokeColor {
|
||||
CAnim {}
|
||||
}
|
||||
Behavior on strokeWidth {
|
||||
Anim {}
|
||||
}
|
||||
|
||||
PathLine {
|
||||
x: bg.width - path.inset - root.clampedRadius
|
||||
y: path.inset
|
||||
}
|
||||
|
||||
PathArc {
|
||||
radiusX: root.clampedRadius
|
||||
radiusY: root.clampedRadius
|
||||
x: bg.width - path.inset
|
||||
y: path.inset + root.clampedRadius
|
||||
}
|
||||
|
||||
PathLine {
|
||||
x: bg.width - path.inset
|
||||
y: bg.height - path.inset - root.clampedRadius
|
||||
}
|
||||
|
||||
PathArc {
|
||||
radiusX: root.clampedRadius
|
||||
radiusY: root.clampedRadius
|
||||
x: bg.width - path.inset - root.clampedRadius
|
||||
y: bg.height - path.inset
|
||||
}
|
||||
|
||||
PathLine {
|
||||
x: path.inset + root.clampedRadius
|
||||
y: bg.height - path.inset
|
||||
}
|
||||
|
||||
PathArc {
|
||||
radiusX: root.clampedRadius
|
||||
radiusY: root.clampedRadius
|
||||
x: path.inset
|
||||
y: bg.height - path.inset - root.clampedRadius
|
||||
}
|
||||
|
||||
PathLine {
|
||||
x: path.inset
|
||||
y: path.inset + root.clampedRadius
|
||||
}
|
||||
|
||||
PathArc {
|
||||
radiusX: root.clampedRadius
|
||||
radiusY: root.clampedRadius
|
||||
x: path.inset + root.clampedRadius
|
||||
y: path.inset
|
||||
}
|
||||
|
||||
PathLine {
|
||||
x: path.inset + root.horizontalPadding - root.clampedRadius / 2 + path.outlineGap * (1 - path.outlineGapScale) / 2
|
||||
y: path.inset
|
||||
Connections {
|
||||
function onCursorPositionChanged(): void {
|
||||
if (root.activeFocus && root.cursorVisible) {
|
||||
cursor.opacity = 1;
|
||||
cursor.disableBlink = true;
|
||||
enableBlink.restart();
|
||||
}
|
||||
}
|
||||
|
||||
target: root
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: enableBlink
|
||||
|
||||
interval: 100
|
||||
|
||||
onTriggered: cursor.disableBlink = false
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 500
|
||||
repeat: true
|
||||
running: root.activeFocus && root.cursorVisible && !cursor.disableBlink
|
||||
triggeredOnStart: true
|
||||
|
||||
onTriggered: parent.opacity = parent.opacity === 1 ? 0 : 1
|
||||
}
|
||||
|
||||
Binding {
|
||||
cursor.opacity: 0
|
||||
when: !root.activeFocus || !root.cursorVisible
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: filledComp
|
||||
|
||||
CustomRect {
|
||||
color: root.activeFocus ? Colors.tPalette.m3surfaceContainerHighest : Colors.tPalette.m3surfaceContainerHigh
|
||||
topLeftRadius: root.clampedRadius
|
||||
topRightRadius: root.clampedRadius
|
||||
|
||||
CustomRect {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
color: root.isError ? Colors.palette.m3error : (root.activeFocus ? Colors.palette.m3primary : Colors.palette.m3outline)
|
||||
implicitHeight: root.activeFocus ? 2 : 1
|
||||
|
||||
Behavior on implicitHeight {
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
Behavior on placeholderTextColor {
|
||||
CAnim {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import ZShell.Config
|
||||
import qs.Services
|
||||
|
||||
TextInput {
|
||||
renderType: Text.NativeRendering
|
||||
selectedTextColor: Colors.palette.m3onSecondaryContainer
|
||||
selectionColor: Colors.tPalette.colSecondaryContainer
|
||||
selectionColor: Colors.tPalette.m3onSecondaryContainer
|
||||
|
||||
font {
|
||||
family: Config.appearance?.font.family.sans ?? "sans-serif"
|
||||
hintingPreference: Font.PreferFullHinting
|
||||
pixelSize: Config.appearance?.font.size.normal ?? 15
|
||||
pixelSize: Tokens?.font.size.normal ?? 15
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import ZShell.Config
|
||||
|
||||
Image {
|
||||
id: root
|
||||
|
||||
property int fadeInAnim: Anim.DefaultEffects
|
||||
property int fadeInLargeAnim: Anim.StandardLarge
|
||||
property int fadeOutAnim: Anim.FastEffects
|
||||
property bool fadingOut
|
||||
property bool hadPrevious
|
||||
property bool preventInit
|
||||
|
||||
function maybeStartInAnim(): void {
|
||||
if (!preventInit && !opacityInAnim.running && status === Image.Ready) {
|
||||
opacityInAnim.type = hadPrevious ? fadeInAnim : fadeInLargeAnim;
|
||||
opacityInAnim.start();
|
||||
}
|
||||
}
|
||||
|
||||
asynchronous: true
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
opacity: 0
|
||||
retainWhileLoading: true
|
||||
sourceSize: {
|
||||
const dpr = (QsWindow.window as QsWindow)?.devicePixelRatio ?? 1;
|
||||
return Qt.size(width * dpr, height * dpr);
|
||||
}
|
||||
|
||||
Anim on opacity {
|
||||
id: opacityInAnim
|
||||
|
||||
running: false
|
||||
to: 1
|
||||
}
|
||||
Behavior on source {
|
||||
SequentialAnimation {
|
||||
ScriptAction {
|
||||
script: opacityInAnim.stop()
|
||||
}
|
||||
|
||||
PropertyAction {
|
||||
property: "fadingOut"
|
||||
target: root
|
||||
value: true
|
||||
}
|
||||
|
||||
Anim {
|
||||
property: "opacity"
|
||||
target: root
|
||||
to: 0
|
||||
type: root.fadeOutAnim
|
||||
}
|
||||
|
||||
PropertyAction {
|
||||
property: "fadingOut"
|
||||
target: root
|
||||
value: false
|
||||
}
|
||||
|
||||
PropertyAction {
|
||||
property: "hadPrevious"
|
||||
target: root
|
||||
value: root.source
|
||||
}
|
||||
|
||||
PropertyAction {
|
||||
}
|
||||
|
||||
ScriptAction {
|
||||
script: root.maybeStartInAnim()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onPreventInitChanged: maybeStartInAnim()
|
||||
onStatusChanged: maybeStartInAnim()
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import M3Shapes
|
||||
import ZShell.Config
|
||||
import qs.Services
|
||||
|
||||
MaterialShape {
|
||||
id: root
|
||||
|
||||
property bool animated: true
|
||||
property real cRotation
|
||||
property bool containsIcon
|
||||
property real dampingRatio: 0.6
|
||||
property real lRotation
|
||||
property int morphAnimRotation: 60
|
||||
property real morphScale: 0.14
|
||||
property alias rotateAnimDuration: rotateAnim.duration
|
||||
property int shapeIndex
|
||||
property list<int> shapes: {
|
||||
if (containsIcon)
|
||||
return [MaterialShape.SoftBurst, MaterialShape.Cookie9Sided, MaterialShape.Pill, MaterialShape.Sunny, MaterialShape.Cookie4Sided, MaterialShape.Oval];
|
||||
return [MaterialShape.SoftBurst, MaterialShape.Cookie9Sided, MaterialShape.Pentagon, MaterialShape.Pill, MaterialShape.Sunny, MaterialShape.Cookie4Sided, MaterialShape.Oval];
|
||||
}
|
||||
readonly property real springDuration: {
|
||||
const wn = Math.sqrt(stiffness);
|
||||
const r = -dampingRatio * wn;
|
||||
const c = 1 / Math.sqrt(1 - dampingRatio * dampingRatio);
|
||||
return Math.log(visibilityThreshold / c) / r;
|
||||
}
|
||||
readonly property real springMaxVelocity: {
|
||||
const wn = Math.sqrt(stiffness);
|
||||
const factor = Math.exp(-z * Math.acos(z) / Math.sqrt(1 - z * z));
|
||||
return wn * factor;
|
||||
}
|
||||
property bool springSettled: true
|
||||
property real stiffness: 180
|
||||
property real thisLRotation
|
||||
property real visibilityThreshold: 0.075
|
||||
|
||||
function spring(t: real): var {
|
||||
const wn = Math.sqrt(stiffness);
|
||||
const za = dampingRatio * wn;
|
||||
|
||||
const wd = wn * Math.sqrt(1 - dampingRatio * dampingRatio);
|
||||
const r = za / wd;
|
||||
const pos = 1 - Math.exp(-za * t) * (Math.cos(wd * t) + r * Math.sin(wd * t));
|
||||
const vel = Math.exp(-za * t) * (wn * wn / wd) * Math.sin(wd * t);
|
||||
|
||||
return [pos, vel];
|
||||
}
|
||||
|
||||
color: Colors.palette.m3primary
|
||||
implicitSize: 38
|
||||
toShape: shapes[0]
|
||||
|
||||
RotationAnimation on cRotation {
|
||||
id: rotateAnim
|
||||
|
||||
duration: 4666
|
||||
easing.type: Easing.Linear
|
||||
from: 0
|
||||
loops: Animation.Infinite
|
||||
running: root.animated
|
||||
to: 360
|
||||
}
|
||||
Behavior on color {
|
||||
CAnim {
|
||||
}
|
||||
}
|
||||
|
||||
ElapsedTimer {
|
||||
id: timer
|
||||
}
|
||||
|
||||
FrameAnimation {
|
||||
running: root.animated && !root.springSettled
|
||||
|
||||
onTriggered: {
|
||||
const t = timer.elapsed();
|
||||
|
||||
if (t >= root.springDuration) {
|
||||
root.springSettled = true;
|
||||
} else {
|
||||
const [pos, vel] = root.spring(t);
|
||||
root.morphProgress = Math.min(1, pos); // Overshooting the morph looks weird
|
||||
root.thisLRotation = pos * root.morphAnimRotation;
|
||||
root.scale = 1 + vel * root.morphScale / root.springMaxVelocity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 650
|
||||
repeat: true
|
||||
running: root.animated
|
||||
triggeredOnStart: true
|
||||
|
||||
onTriggered: {
|
||||
root.beginBatchUpdate();
|
||||
root.fromShape = root.toShape;
|
||||
root.shapeIndex = (root.shapeIndex + 1) % root.shapes.length;
|
||||
root.toShape = root.shapes[root.shapeIndex];
|
||||
root.morphProgress = 0;
|
||||
|
||||
root.rotation = root.rotation;
|
||||
root.lRotation = (root.lRotation + root.thisLRotation) % 360;
|
||||
root.thisLRotation = 0;
|
||||
root.rotation = Qt.binding(() => root.cRotation + root.lRotation + root.thisLRotation);
|
||||
|
||||
root.springSettled = false;
|
||||
timer.restart();
|
||||
root.endBatchUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,16 @@
|
||||
import QtQuick
|
||||
import ZShell.Config
|
||||
import qs.Services
|
||||
|
||||
CustomText {
|
||||
id: root
|
||||
|
||||
property real fill: 0
|
||||
property real fill
|
||||
property int grade: Colors.light ? 0 : -25
|
||||
property font fontStyle: Tokens.font.icon.small
|
||||
|
||||
renderType: Text.NativeRendering
|
||||
font: Tokens.font.icon.size(fontStyle.pointSize).weight(fontStyle.weight).vaxes(fontStyle.variableAxes).fill(fill.toFixed(1)).grade(grade).build()
|
||||
font.family: "Material Symbols Rounded"
|
||||
font.pointSize: Tokens.font.size.larger
|
||||
font.variableAxes: ({
|
||||
FILL: fill.toFixed(1),
|
||||
GRAD: grade,
|
||||
opsz: fontInfo.pixelSize,
|
||||
wght: fontInfo.weight
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import QtQuick
|
||||
import QtQuick.Shapes
|
||||
import ZShell
|
||||
import ZShell.Components
|
||||
import qs.Helpers
|
||||
import ZShell.Config
|
||||
import qs.Services
|
||||
|
||||
@@ -11,6 +13,7 @@ MouseArea {
|
||||
property alias bottomRightRadius: base.bottomRightRadius
|
||||
property real circleRadius
|
||||
property alias color: base.color
|
||||
property bool disabled
|
||||
readonly property real endRadius: {
|
||||
const d1 = distSq(0, 0);
|
||||
const d2 = distSq(width, 0);
|
||||
@@ -19,7 +22,6 @@ MouseArea {
|
||||
return (Math.sqrt(Math.max(d1, d2, d3, d4)) + (shapeMorph ? 24 : 0)) * 1.3;
|
||||
}
|
||||
property real endRadiusAtPress
|
||||
property bool manualHoverOverride
|
||||
property bool manualPressOverride
|
||||
property real pressX: width / 2
|
||||
property real pressY: height / 2
|
||||
@@ -27,7 +29,7 @@ MouseArea {
|
||||
readonly property alias rect: base
|
||||
property bool shapeMorph
|
||||
property bool showHoverBackground: true
|
||||
property real stateOpacity: containsMouse || manualHoverOverride ? 0.08 : 0
|
||||
property real stateOpacity: containsMouse ? 0.08 : 0
|
||||
property alias topLeftRadius: base.topLeftRadius
|
||||
property alias topRightRadius: base.topRightRadius
|
||||
|
||||
@@ -50,8 +52,8 @@ MouseArea {
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
cursorShape: !enabled ? undefined : Qt.PointingHandCursor
|
||||
enabled: parent.enabled
|
||||
cursorShape: disabled ? undefined : Qt.PointingHandCursor
|
||||
enabled: !disabled
|
||||
hoverEnabled: true
|
||||
|
||||
Behavior on stateOpacity {
|
||||
@@ -98,13 +100,13 @@ MouseArea {
|
||||
id: base
|
||||
|
||||
anchors.fill: parent
|
||||
bottomLeftRadius: root.parent?.bottomLeftRadius ?? radius ?? 0
|
||||
bottomRightRadius: root.parent?.bottomRightRadius ?? radius ?? 0
|
||||
color: Colors.palette.m3onSurface
|
||||
opacity: root.stateOpacity
|
||||
// Pick up radius from parent if it has one (parent can be anything with radius props)
|
||||
// qmllint disable missing-property
|
||||
radius: root.parent?.radius ?? 0
|
||||
bottomLeftRadius: root.parent?.bottomLeftRadius ?? radius ?? 0
|
||||
bottomRightRadius: root.parent?.bottomRightRadius ?? radius ?? 0
|
||||
topLeftRadius: root.parent?.topLeftRadius ?? radius ?? 0
|
||||
topRightRadius: root.parent?.topRightRadius ?? radius ?? 0
|
||||
// qmllint enable missing-property
|
||||
|
||||
+39
-16
@@ -13,16 +13,34 @@ RowLayout {
|
||||
|
||||
spacing: Tokens.spacing.largeIncreased * 2
|
||||
|
||||
CustomRect {
|
||||
Layout.fillHeight: true
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
topLeftRadius: Tokens.rounding.largeIncreased
|
||||
bottomLeftRadius: Tokens.rounding.largeIncreased
|
||||
color: Colors.tPalette.m3surfaceContainer
|
||||
radius: Tokens.rounding.small
|
||||
spacing: Tokens.spacing.medium
|
||||
|
||||
UserDock {
|
||||
greeter: root.greeter
|
||||
CustomRect {
|
||||
Layout.fillWidth: true
|
||||
color: Colors.tPalette.m3surfaceContainer
|
||||
implicitHeight: weather.implicitHeight
|
||||
radius: Tokens.rounding.small
|
||||
topLeftRadius: Tokens.rounding.largeIncreased
|
||||
|
||||
WeatherInfo {
|
||||
id: weather
|
||||
|
||||
rootHeight: root.height
|
||||
}
|
||||
}
|
||||
|
||||
CustomClippingRect {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
bottomLeftRadius: Tokens.rounding.largeIncreased
|
||||
color: Colors.tPalette.m3surfaceContainer
|
||||
radius: Tokens.rounding.small
|
||||
|
||||
UserDock {
|
||||
greeter: root.greeter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,16 +49,21 @@ RowLayout {
|
||||
screenHeight: root.screenHeight
|
||||
}
|
||||
|
||||
CustomRect {
|
||||
Layout.fillHeight: true
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
bottomRightRadius: Tokens.rounding.largeIncreased
|
||||
color: Colors.tPalette.m3surfaceContainer
|
||||
radius: Tokens.rounding.small
|
||||
topRightRadius: Tokens.rounding.largeIncreased
|
||||
spacing: Tokens.spacing.medium
|
||||
|
||||
SessionDock {
|
||||
greeter: root.greeter
|
||||
CustomRect {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
bottomRightRadius: Tokens.rounding.largeIncreased
|
||||
color: Colors.tPalette.m3surfaceContainer
|
||||
radius: Tokens.rounding.small
|
||||
topRightRadius: Tokens.rounding.largeIncreased
|
||||
|
||||
SessionDock {
|
||||
greeter: root.greeter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,7 +134,8 @@ Scope {
|
||||
id: lockIcon
|
||||
|
||||
anchors.centerIn: parent
|
||||
fontStyle: Tokens.font.icon.builders.extraLarge.size(Tokens.font.icon.extraLarge.pointSize * 3).build()
|
||||
font.bold: true
|
||||
font.pointSize: Tokens.font.size.extraLarge * 4
|
||||
text: "lock"
|
||||
}
|
||||
|
||||
|
||||
+15
-8
@@ -39,7 +39,8 @@ Item {
|
||||
anchors.centerIn: parent
|
||||
animate: true
|
||||
color: root.greeter.launching ? Colors.palette.m3secondary : Colors.palette.m3outline
|
||||
font: Tokens.font.mono.medium
|
||||
font.family: Config.appearance.font.family.mono
|
||||
font.pointSize: Tokens.font.size.normal
|
||||
opacity: root.buffer ? 0 : 1
|
||||
text: {
|
||||
if (root.greeter.launching)
|
||||
@@ -50,7 +51,8 @@ Item {
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {}
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,13 +64,15 @@ Item {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: Colors.palette.m3onSurface
|
||||
elide: Text.ElideLeft
|
||||
font: Tokens.font.mono.medium
|
||||
font.family: Config.appearance.font.family.mono
|
||||
font.pointSize: Tokens.font.size.normal
|
||||
horizontalAlignment: Qt.AlignHCenter
|
||||
opacity: root.greeter.echoResponse && root.buffer ? 1 : 0
|
||||
text: root.buffer
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {}
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +89,7 @@ Item {
|
||||
|
||||
anchors.centerIn: parent
|
||||
anchors.horizontalCenterOffset: implicitWidth > root.width ? -(implicitWidth - root.width) / 2 : 0
|
||||
implicitHeight: Tokens.font.body.medium
|
||||
implicitHeight: Tokens.font.size.normal
|
||||
implicitWidth: fullWidth
|
||||
interactive: false
|
||||
opacity: root.greeter.echoResponse ? 0 : 1
|
||||
@@ -103,7 +107,8 @@ Item {
|
||||
scale: 0
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {}
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
Behavior on scale {
|
||||
Anim {
|
||||
@@ -151,13 +156,15 @@ Item {
|
||||
Behavior on implicitWidth {
|
||||
id: imWidthBehavior
|
||||
|
||||
Anim {}
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
model: ScriptModel {
|
||||
values: root.buffer.split("")
|
||||
}
|
||||
Behavior on opacity {
|
||||
Anim {}
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+17
-11
@@ -13,19 +13,22 @@ ColumnLayout {
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Tokens.padding.large
|
||||
spacing: Tokens.spacing.large
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
color: Colors.palette.m3outline
|
||||
elide: Text.ElideRight
|
||||
font: Tokens.font.mono.builders.small.weight(Font.Medium).build()
|
||||
font.family: Config.appearance.font.family.mono
|
||||
font.weight: 500
|
||||
text: root.greeter.sessions.length > 0 ? qsTr("%1 session%2").arg(root.greeter.sessions.length).arg(root.greeter.sessions.length === 1 ? "" : "s") : qsTr("Sessions")
|
||||
}
|
||||
|
||||
Item {
|
||||
CustomClippingRect {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
color: "transparent"
|
||||
radius: Tokens.rounding.small
|
||||
|
||||
Loader {
|
||||
active: opacity > 0
|
||||
@@ -43,14 +46,16 @@ ColumnLayout {
|
||||
MaterialIcon {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
color: Colors.palette.m3outlineVariant
|
||||
fontStyle: Tokens.font.icon.large
|
||||
font.pointSize: Tokens.font.size.extraLarge * 2
|
||||
text: "desktop_windows"
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
color: Colors.palette.m3outlineVariant
|
||||
font: Tokens.font.headline.builders.small.width(90).build()
|
||||
font.family: Config.appearance.font.family.mono
|
||||
font.pointSize: Tokens.font.size.large
|
||||
font.weight: 500
|
||||
text: qsTr("No Sessions Found")
|
||||
}
|
||||
}
|
||||
@@ -63,7 +68,6 @@ ColumnLayout {
|
||||
clip: true
|
||||
currentIndex: root.greeter.sessionIndex
|
||||
highlightFollowsCurrentItem: false
|
||||
boundsBehavior: ListView.StopAtBounds
|
||||
model: root.greeter.sessions
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
@@ -76,7 +80,7 @@ ColumnLayout {
|
||||
anchors.left: parent?.left
|
||||
anchors.right: parent?.right
|
||||
implicitHeight: row.implicitHeight + Tokens.padding.small * 2
|
||||
radius: Tokens.rounding.large
|
||||
radius: Tokens.rounding.medium - Tokens.padding.small
|
||||
|
||||
StateLayer {
|
||||
onClicked: {
|
||||
@@ -93,7 +97,7 @@ ColumnLayout {
|
||||
|
||||
MaterialIcon {
|
||||
color: session.index === sessions.currentIndex ? Colors.palette.m3onPrimary : Colors.palette.m3onSurfaceVariant
|
||||
fontStyle: Tokens.font.icon.extraLarge
|
||||
font.pointSize: Tokens.font.size.extraLarge
|
||||
text: modelData.kind === "x11" ? "tv" : "desktop_windows"
|
||||
}
|
||||
|
||||
@@ -105,7 +109,8 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
color: session.index === sessions.currentIndex ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
|
||||
elide: Text.ElideRight
|
||||
font: Tokens.font.body.builders.medium.weight(Font.Bold).build()
|
||||
font.pointSize: Tokens.font.size.normal
|
||||
font.weight: 600
|
||||
text: modelData.name
|
||||
}
|
||||
|
||||
@@ -113,7 +118,8 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
color: session.index === sessions.currentIndex ? Colors.palette.m3onPrimaryFixedVariant : Colors.palette.m3onSurfaceVariant
|
||||
elide: Text.ElideRight
|
||||
font: Tokens.font.mono.small
|
||||
font.family: Config.appearance.font.family.mono
|
||||
font.pointSize: Tokens.font.size.small
|
||||
text: modelData.kind
|
||||
}
|
||||
}
|
||||
@@ -123,7 +129,7 @@ ColumnLayout {
|
||||
color: Colors.palette.m3primary
|
||||
implicitHeight: sessions.currentItem?.implicitHeight ?? 0
|
||||
implicitWidth: sessions.width
|
||||
radius: Tokens.rounding.large
|
||||
radius: Tokens.rounding.medium - Tokens.padding.small
|
||||
y: sessions.currentItem?.y ?? 0
|
||||
|
||||
Behavior on y {
|
||||
|
||||
+16
-11
@@ -14,19 +14,22 @@ ColumnLayout {
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Tokens.padding.large
|
||||
spacing: Tokens.spacing.large
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
color: Colors.palette.m3outline
|
||||
elide: Text.ElideRight
|
||||
font: Tokens.font.mono.builders.small.weight(Font.Medium).build()
|
||||
font.family: Config.appearance.font.family.mono
|
||||
font.weight: 500
|
||||
text: root.greeter.users.length > 0 ? qsTr("%1 user%2").arg(root.greeter.users.length).arg(root.greeter.users.length === 1 ? "" : "s") : qsTr("Users")
|
||||
}
|
||||
|
||||
Item {
|
||||
CustomClippingRect {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
color: "transparent"
|
||||
radius: Tokens.rounding.small
|
||||
|
||||
Loader {
|
||||
active: opacity > 0
|
||||
@@ -45,14 +48,16 @@ ColumnLayout {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
color: Colors.palette.m3outlineVariant
|
||||
fill: 1
|
||||
fontStyle: Tokens.font.icon.large
|
||||
font.pointSize: Tokens.font.size.extraLarge * 2
|
||||
text: "account_circle"
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
color: Colors.palette.m3outlineVariant
|
||||
font: Tokens.font.headline.builders.small.width(90).build()
|
||||
font.family: Config.appearance.font.family.mono
|
||||
font.pointSize: Tokens.font.size.large
|
||||
font.weight: 500
|
||||
text: qsTr("No Users Found")
|
||||
}
|
||||
}
|
||||
@@ -65,7 +70,6 @@ ColumnLayout {
|
||||
clip: true
|
||||
currentIndex: root.greeter.selectedUser ? root.greeter.users.indexOf(root.greeter.selectedUser) : -1
|
||||
highlightFollowsCurrentItem: false
|
||||
boundsBehavior: ListView.StopAtBounds
|
||||
model: root.greeter.users
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
@@ -78,7 +82,7 @@ ColumnLayout {
|
||||
anchors.left: parent?.left
|
||||
anchors.right: parent?.right
|
||||
implicitHeight: row.implicitHeight + Tokens.padding.small * 2
|
||||
radius: Tokens.rounding.large
|
||||
radius: Tokens.rounding.medium - Tokens.padding.small
|
||||
|
||||
StateLayer {
|
||||
onClicked: {
|
||||
@@ -93,7 +97,7 @@ ColumnLayout {
|
||||
anchors.margins: Tokens.padding.small
|
||||
spacing: Tokens.spacing.medium
|
||||
|
||||
ClipRect {
|
||||
CustomClippingRect {
|
||||
Layout.preferredHeight: 50
|
||||
Layout.preferredWidth: 50
|
||||
radius: Tokens.rounding.full
|
||||
@@ -102,7 +106,7 @@ ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
color: user.index === users.currentIndex ? Colors.palette.m3onPrimary : Colors.palette.m3onSurfaceVariant
|
||||
fill: 1
|
||||
fontStyle: Tokens.font.icon.extraLarge
|
||||
font.pointSize: Tokens.font.size.extraLarge
|
||||
text: modelData.kind === "x11" ? "tv" : "account_circle"
|
||||
}
|
||||
|
||||
@@ -120,7 +124,8 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
color: user.index === users.currentIndex ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
|
||||
elide: Text.ElideRight
|
||||
font: Tokens.font.body.builders.medium.weight(Font.Bold).build()
|
||||
font.pointSize: Tokens.font.size.normal
|
||||
font.weight: 600
|
||||
text: modelData.username
|
||||
}
|
||||
}
|
||||
@@ -130,7 +135,7 @@ ColumnLayout {
|
||||
color: Colors.palette.m3primary
|
||||
implicitHeight: users.currentItem?.implicitHeight ?? 0
|
||||
implicitWidth: users.width
|
||||
radius: Tokens.rounding.large
|
||||
radius: Tokens.rounding.medium - Tokens.padding.small
|
||||
y: users.currentItem?.y ?? 0
|
||||
|
||||
Behavior on y {
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import ZShell.Config
|
||||
import qs.Components
|
||||
import qs.Helpers
|
||||
import qs.Services
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
required property int rootHeight
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.margins: Tokens.padding.large * 2
|
||||
anchors.right: parent.right
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
Loader {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.bottomMargin: -Tokens.padding.large
|
||||
Layout.topMargin: Tokens.padding.large * 2
|
||||
active: root.rootHeight > 610
|
||||
visible: active
|
||||
|
||||
sourceComponent: CustomText {
|
||||
color: Colors.palette.m3primary
|
||||
font.pointSize: Tokens.font.size.extraLarge
|
||||
font.weight: 500
|
||||
text: qsTr("Weather")
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Tokens.spacing.largeIncreased
|
||||
|
||||
MaterialIcon {
|
||||
animate: true
|
||||
color: Colors.palette.m3secondary
|
||||
font.pointSize: Tokens.font.size.extraLarge * 2.5
|
||||
text: Weather.icon
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
animate: true
|
||||
color: Colors.palette.m3secondary
|
||||
elide: Text.ElideRight
|
||||
font.pointSize: Tokens.font.size.large
|
||||
font.weight: 500
|
||||
text: Weather.description
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
animate: true
|
||||
color: Colors.palette.m3onSurfaceVariant
|
||||
elide: Text.ElideRight
|
||||
font.pointSize: Tokens.font.size.normal
|
||||
text: qsTr("Humidity: %1%").arg(Weather.humidity)
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
Layout.rightMargin: Tokens.padding.small
|
||||
active: root.width > 400
|
||||
visible: active
|
||||
|
||||
sourceComponent: ColumnLayout {
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
animate: true
|
||||
color: Colors.palette.m3primary
|
||||
elide: Text.ElideLeft
|
||||
font.pointSize: Tokens.font.size.extraLarge
|
||||
font.weight: 500
|
||||
horizontalAlignment: Text.AlignRight
|
||||
text: Weather.temp
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
animate: true
|
||||
color: Colors.palette.m3outline
|
||||
elide: Text.ElideLeft
|
||||
font.pointSize: Tokens.font.size.smaller
|
||||
horizontalAlignment: Text.AlignRight
|
||||
text: qsTr("Feels like: %1").arg(Weather.feelsLike)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: forecastLoader
|
||||
|
||||
Layout.bottomMargin: Tokens.padding.large * 2
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Tokens.spacing.small
|
||||
active: root.rootHeight > 820
|
||||
visible: active
|
||||
|
||||
sourceComponent: RowLayout {
|
||||
spacing: Tokens.spacing.largeIncreased
|
||||
|
||||
Repeater {
|
||||
model: {
|
||||
const forecast = Weather.hourlyForecast;
|
||||
const count = root.width < 320 ? 3 : root.width < 400 ? 4 : 5;
|
||||
if (!forecast)
|
||||
return Array.from({
|
||||
length: count
|
||||
}, () => null);
|
||||
|
||||
const start = Math.max(0, Weather.currentHourIndex);
|
||||
return forecast.slice(start, start + count);
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: forecastHour
|
||||
|
||||
required property var modelData
|
||||
|
||||
Layout.fillWidth: true
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
color: Colors.palette.m3outline
|
||||
font.pointSize: Tokens.font.size.larger
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: {
|
||||
const hour = forecastHour.modelData?.hour ?? 0;
|
||||
return hour > 12 ? `${(hour - 12).toString().padStart(2, "0")} PM` : `${hour.toString().padStart(2, "0")} AM`;
|
||||
}
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
font.pointSize: Tokens.font.size.extraLarge * 1.5
|
||||
font.weight: 500
|
||||
text: forecastHour.modelData?.icon ?? "cloud_alert"
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
color: Colors.palette.m3secondary
|
||||
font.pointSize: Tokens.font.size.larger
|
||||
text: Config.services.useFahrenheit ? `${forecastHour.modelData?.tempF ?? 0}°F` : `${forecastHour.modelData?.tempC ?? 0}°C`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 900000 // 15 minutes
|
||||
repeat: true
|
||||
running: true
|
||||
triggeredOnStart: true
|
||||
|
||||
onTriggered: Weather.reload()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import ZShell.Config
|
||||
import qs.Components
|
||||
import qs.Services
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property Item bar
|
||||
required property ScreenState screenState
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
CustomRect {
|
||||
anchors.fill: parent
|
||||
anchors.margins: -1
|
||||
color: Colors.palette.m3surface
|
||||
layer.enabled: true
|
||||
|
||||
layer.effect: MultiEffect {
|
||||
maskEnabled: true
|
||||
maskInverted: true
|
||||
maskSource: mask
|
||||
maskSpreadAtMin: 1
|
||||
maskThresholdMin: 0.5
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: mask
|
||||
|
||||
anchors.fill: parent
|
||||
layer.enabled: true
|
||||
visible: false
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Config.bar.border + 1
|
||||
anchors.topMargin: root.bar.implicitHeight + 1
|
||||
radius: Config.bar.border > 0 ? Config.bar.rounding : 0
|
||||
topLeftRadius: Config.bar.rounding
|
||||
topRightRadius: Config.bar.rounding
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,7 +79,6 @@ Item {
|
||||
|
||||
anchors.centerIn: parent
|
||||
antialiasing: true
|
||||
backer.retainWhileLoading: true
|
||||
color: root.current && !Config.bar.tray.showOnHover ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
|
||||
implicitSize: Config.bar.tray.trayIconSize * root.dpr
|
||||
layer.enabled: Config.bar.tray.recolorIcons
|
||||
|
||||
@@ -134,7 +134,7 @@ Item {
|
||||
node: Audio.source
|
||||
}
|
||||
|
||||
ClipRect {
|
||||
CustomClippingRect {
|
||||
anchors.fill: parent
|
||||
radius: parent.radius
|
||||
|
||||
@@ -249,7 +249,7 @@ Item {
|
||||
node: appBox.modelData
|
||||
}
|
||||
|
||||
ClipRect {
|
||||
CustomClippingRect {
|
||||
anchors.fill: parent
|
||||
radius: parent.radius
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ Item {
|
||||
searchField.forceActiveFocus();
|
||||
}
|
||||
|
||||
ClipRect {
|
||||
CustomClippingRect {
|
||||
id: search
|
||||
|
||||
anchors.left: parent.left
|
||||
@@ -54,7 +54,7 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
ClipRect {
|
||||
CustomClippingRect {
|
||||
id: preview
|
||||
|
||||
anchors.bottom: parent.bottom
|
||||
@@ -228,7 +228,7 @@ Item {
|
||||
anchors.fill: parent
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
ClipRect {
|
||||
CustomClippingRect {
|
||||
id: textRect
|
||||
|
||||
Layout.fillHeight: true
|
||||
|
||||
@@ -14,7 +14,7 @@ Row {
|
||||
padding: 20
|
||||
spacing: 12
|
||||
|
||||
ClipRect {
|
||||
CustomClippingRect {
|
||||
color: Colors.layer(Colors.palette.m3surfaceContainerHigh, 2)
|
||||
implicitHeight: info.implicitHeight
|
||||
implicitWidth: info.implicitHeight
|
||||
|
||||
@@ -16,7 +16,7 @@ Item {
|
||||
implicitWidth: 1000
|
||||
implicitHeight: 320
|
||||
|
||||
ClipRect {
|
||||
CustomClippingRect {
|
||||
anchors.fill: parent
|
||||
radius: root.radius
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ CustomListView {
|
||||
anchors.bottom: parent.bottom
|
||||
spacing: Tokens.spacing.extraSmall / 2
|
||||
|
||||
ClipRect {
|
||||
CustomClippingRect {
|
||||
id: iconClip
|
||||
|
||||
Layout.fillHeight: true
|
||||
|
||||
@@ -25,7 +25,7 @@ Item {
|
||||
onClicked: root.close()
|
||||
}
|
||||
|
||||
ClipRect {
|
||||
CustomClippingRect {
|
||||
id: popupBackground
|
||||
readonly property real padding: 4
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ Item {
|
||||
visible: false
|
||||
z: 999
|
||||
|
||||
ClipRect {
|
||||
CustomClippingRect {
|
||||
id: popupBackground
|
||||
|
||||
readonly property real padding: Tokens.padding.extraSmall
|
||||
|
||||
@@ -19,7 +19,7 @@ Item {
|
||||
implicitHeight: tileHeight + Tokens.padding.large * 2
|
||||
implicitWidth: tileWidth
|
||||
|
||||
ClipRect {
|
||||
CustomClippingRect {
|
||||
id: image
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
@@ -55,7 +55,7 @@ ColumnLayout {
|
||||
text: Time.format("dddd, d MMMM yyyy")
|
||||
}
|
||||
|
||||
ClipRect {
|
||||
CustomClippingRect {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.topMargin: Tokens.spacing.largeIncreased * 2
|
||||
color: Colors.tPalette.m3surfaceContainer
|
||||
|
||||
@@ -5,7 +5,7 @@ import qs.Helpers
|
||||
import ZShell.Config
|
||||
import qs.Services
|
||||
|
||||
ClipRect {
|
||||
CustomClippingRect {
|
||||
id: root
|
||||
|
||||
required property var lock
|
||||
|
||||
@@ -2,12 +2,12 @@ import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import ZShell.Components
|
||||
import ZShell.Config
|
||||
import qs.Components
|
||||
import ZShell.Config
|
||||
import qs.Helpers
|
||||
import qs.Services
|
||||
|
||||
ClipRect {
|
||||
CustomClippingRect {
|
||||
id: root
|
||||
|
||||
required property var lock
|
||||
|
||||
@@ -24,11 +24,12 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
color: Colors.palette.m3outline
|
||||
elide: Text.ElideRight
|
||||
font: Tokens.font.mono.builders.small.weight(Font.Medium).build()
|
||||
font.family: Config.appearance.font.family.mono
|
||||
font.weight: 500
|
||||
text: NotifServer.list.length > 0 ? qsTr("%1 notification%2").arg(NotifServer.list.length).arg(NotifServer.list.length === 1 ? "" : "s") : qsTr("Notifications")
|
||||
}
|
||||
|
||||
ClipRect {
|
||||
ClippingRectangle {
|
||||
id: clipRect
|
||||
|
||||
Layout.fillHeight: true
|
||||
|
||||
@@ -85,7 +85,7 @@ CustomRect {
|
||||
}
|
||||
}
|
||||
|
||||
ClipRect {
|
||||
ClippingRectangle {
|
||||
anchors.fill: parent
|
||||
color: root.urgency === "critical" ? Colors.palette.m3error : root.urgency === "low" ? Colors.layer(Colors.palette.m3surfaceContainerHighest, 3) : Colors.palette.m3secondaryContainer
|
||||
radius: Tokens.rounding.full
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import QtQuick
|
||||
import qs.Components
|
||||
import qs.Paths
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
ClipRect {
|
||||
ClippingRectangle {
|
||||
anchors.fill: parent
|
||||
radius: Tokens.rounding.full
|
||||
|
||||
|
||||
@@ -28,10 +28,7 @@ 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)
|
||||
return 0;
|
||||
|
||||
if (screenState.dashboard && (panels.dashboard.x + panels.dashboard.width >= panels.notifications.x))
|
||||
if (panels.popouts.hasCurrent && (panels.popouts.currentCenter + (panels.popouts.current?.width / 2)) > panels.notifications.x || screenState.dashboard)
|
||||
return 0;
|
||||
|
||||
if (screenState.osd) {
|
||||
|
||||
@@ -108,7 +108,7 @@ CustomRect {
|
||||
visible: root.hasImage || root.hasAppIcon
|
||||
width: Config.notifs.sizes.image
|
||||
|
||||
sourceComponent: ClipRect {
|
||||
sourceComponent: CustomClippingRect {
|
||||
color: root.modelData.urgency === NotificationUrgency.Critical ? Colors.palette.m3error : root.modelData.urgency === NotificationUrgency.Low ? Colors.layer(Colors.palette.m3surfaceContainerHighest, 2) : Colors.palette.m3secondaryContainer
|
||||
implicitHeight: Config.notifs.sizes.image
|
||||
implicitWidth: Config.notifs.sizes.image
|
||||
|
||||
@@ -5,7 +5,7 @@ import ZShell.Config
|
||||
import qs.Components
|
||||
import qs.Services
|
||||
|
||||
ClipRect {
|
||||
CustomClippingRect {
|
||||
id: root
|
||||
|
||||
property real animPerc: UPower.displayDevice.percentage
|
||||
|
||||
@@ -19,7 +19,7 @@ CustomRect {
|
||||
implicitWidth: 450
|
||||
radius: Tokens.rounding.largeIncreased
|
||||
|
||||
ClipRect {
|
||||
CustomClippingRect {
|
||||
anchors.fill: parent
|
||||
radius: parent.radius
|
||||
|
||||
|
||||
@@ -149,7 +149,7 @@ ColumnLayout {
|
||||
}
|
||||
}
|
||||
|
||||
CustomFlickable {
|
||||
Flickable {
|
||||
id: flickable
|
||||
|
||||
Layout.fillHeight: true
|
||||
|
||||
@@ -25,7 +25,7 @@ Item {
|
||||
anchors.fill: parent
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
ClipRect {
|
||||
CustomClippingRect {
|
||||
id: imgWrapper
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
@@ -100,13 +100,16 @@ Item {
|
||||
visible: opacity > 0
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {}
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
Behavior on scale {
|
||||
Anim {}
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
Behavior on y {
|
||||
Anim {}
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
@@ -117,8 +120,9 @@ Item {
|
||||
Qt.callLater(syncCropToScreen);
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: selectorLoader
|
||||
ButtonRow {
|
||||
id: screenSelector
|
||||
|
||||
anchors.bottom: imageSlot.top
|
||||
anchors.bottomMargin: Tokens.spacing.medium
|
||||
anchors.left: parent.left
|
||||
@@ -126,32 +130,25 @@ Item {
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Tokens.padding.extraLarge * 2
|
||||
anchors.top: parent.top
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
active: wrapper.screens.length > 1
|
||||
Repeater {
|
||||
model: wrapper.screens
|
||||
|
||||
sourceComponent: ButtonRow {
|
||||
id: screenSelector
|
||||
delegate: TextButton {
|
||||
required property int index
|
||||
readonly property bool isCurrent: wrapper.selectedScreenIndex === index
|
||||
required property var modelData
|
||||
|
||||
spacing: Tokens.spacing.small
|
||||
fillWidth: true
|
||||
font: Tokens.font.body.builders.large.weight(Font.DemiBold).build()
|
||||
inactiveColor: isCurrent ? Colors.palette.m3primary : Qt.alpha(Colors.palette.m3surfaceContainer, 0.7)
|
||||
inactiveOnColor: isCurrent ? Colors.palette.m3onPrimary : Qt.alpha(Colors.palette.m3onSurface, 0.7)
|
||||
isRound: true
|
||||
shapeMorph: true
|
||||
text: modelData.name
|
||||
|
||||
Repeater {
|
||||
model: wrapper.screens
|
||||
|
||||
delegate: TextButton {
|
||||
required property int index
|
||||
readonly property bool isCurrent: wrapper.selectedScreenIndex === index
|
||||
required property var modelData
|
||||
|
||||
fillWidth: true
|
||||
font: Tokens.font.body.builders.large.weight(Font.DemiBold).build()
|
||||
inactiveColor: isCurrent ? Colors.palette.m3primary : Qt.alpha(Colors.palette.m3surfaceContainer, 0.7)
|
||||
inactiveOnColor: isCurrent ? Colors.palette.m3onPrimary : Qt.alpha(Colors.palette.m3onSurface, 0.7)
|
||||
isRound: true
|
||||
shapeMorph: true
|
||||
text: modelData.name
|
||||
|
||||
onClicked: wrapper.selectScreen(index)
|
||||
}
|
||||
onClicked: wrapper.selectScreen(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -225,9 +222,9 @@ Item {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: selectorLoader.active ? wrapper.buttonRowHeight + Tokens.spacing.medium : Tokens.spacing.medium
|
||||
anchors.topMargin: wrapper.buttonRowHeight + Tokens.spacing.medium
|
||||
|
||||
ClipRect {
|
||||
CustomClippingRect {
|
||||
id: fitBox
|
||||
|
||||
readonly property real aspect: scaledImg.implicitHeight > 0 ? scaledImg.implicitWidth / scaledImg.implicitHeight : 1
|
||||
@@ -360,7 +357,8 @@ Item {
|
||||
width: Math.round(baseWidth / zoom)
|
||||
|
||||
Behavior on color {
|
||||
CAnim {}
|
||||
CAnim {
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
|
||||
@@ -38,7 +38,6 @@ Item {
|
||||
}
|
||||
|
||||
BlobInvertedRect {
|
||||
id: blobRect
|
||||
anchors.fill: parent
|
||||
borderBottom: Tokens.padding.small
|
||||
borderLeft: navPane.width + navPane.anchors.margins * 2
|
||||
@@ -66,7 +65,6 @@ Item {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: navPane.right
|
||||
anchors.leftMargin: navPane.anchors.margins + anchors.margins
|
||||
anchors.bottomMargin: blobRect.borderBottom
|
||||
anchors.margins: Tokens.padding.extraLarge
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
|
||||
@@ -38,6 +38,7 @@ Item {
|
||||
id: container
|
||||
|
||||
anchors.fill: parent
|
||||
layer.enabled: opacity < 1
|
||||
objectName: "PageContainer"
|
||||
|
||||
Component.onCompleted: root.loadPage(root.sState.currentPageIdx)
|
||||
|
||||
@@ -3,7 +3,6 @@ pragma ComponentBehavior: Bound
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import QtQuick.Layouts
|
||||
import ZShell.Services
|
||||
import ZShell.Components
|
||||
import ZShell.Config
|
||||
import ZShell.Llm
|
||||
@@ -91,9 +90,7 @@ Item {
|
||||
signal requestClose
|
||||
|
||||
readonly property bool atNewestEnd: !listLoader.item || listLoader.item.contentY < 4
|
||||
readonly property bool importing: importIds.length > 0
|
||||
|
||||
property var importIds: []
|
||||
property var pendingAttachments: []
|
||||
|
||||
function attachFiles(): void {
|
||||
@@ -439,7 +436,7 @@ Item {
|
||||
width: attachments.cellWidth
|
||||
height: width
|
||||
opacity: modelData ? 1 : 0
|
||||
ClipRect {
|
||||
CustomClippingRect {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Tokens.spacing.extraSmall
|
||||
|
||||
@@ -544,7 +541,7 @@ Item {
|
||||
bg.border.color: Colors.palette.m3outlineVariant
|
||||
bg.color: Colors.tPalette.m3surfaceContainerLowest
|
||||
fastGlow: LlmClient.busy
|
||||
glow.opacity: !ChatState.narrowSidebarExpanded ? (input.activeFocus && (!LlmClient.busy || ChatState.isWindow) ? 1 : 0) : 0
|
||||
glow.opacity: input.activeFocus && (!LlmClient.busy || ChatState.isWindow)
|
||||
font: Tokens.font.body.small
|
||||
implicitHeight: Math.min(root.height / 5, contentHeight + topPadding + bottomPadding)
|
||||
focus: true
|
||||
@@ -581,52 +578,4 @@ Item {
|
||||
root.send(text);
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: DropImporter
|
||||
|
||||
function onImportFinished(id: int, paths: var, errors: var): void {
|
||||
if (!root.importIds.includes(id))
|
||||
return;
|
||||
root.importIds = root.importIds.filter(i => i !== id);
|
||||
|
||||
if (paths.length > 0) {
|
||||
const added = root.chatData.addAttachments(paths);
|
||||
root.pendingAttachments = root.pendingAttachments.concat(added);
|
||||
}
|
||||
for (const e of errors)
|
||||
console.warn("Drop import:", e);
|
||||
}
|
||||
}
|
||||
|
||||
DropArea {
|
||||
id: dropArea
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
onEntered: drag => {
|
||||
drag.accepted = drag.hasUrls || drag.hasText || drag.hasHtml || drag.formats.some(f => f.startsWith("image/"));
|
||||
}
|
||||
|
||||
onDropped: drop => {
|
||||
const formats = drop.formats;
|
||||
const urls = drop.hasUrls ? drop.urls : [];
|
||||
const text = drop.hasText ? drop.text : "";
|
||||
const html = drop.hasHtml ? drop.html : "";
|
||||
|
||||
const imgFormat = formats.find(f => f.startsWith("image/"));
|
||||
const raw = imgFormat ? drop.getDataAsArrayBuffer(imgFormat) : new ArrayBuffer(0);
|
||||
|
||||
switch (DropImporter.decide(urls, text, html, raw.byteLength > 0)) {
|
||||
case DropImporter.Attach:
|
||||
root.importIds = [...root.importIds, DropImporter.importAttachments(urls, html, raw)];
|
||||
break;
|
||||
case DropImporter.InsertText:
|
||||
input.insert(input.cursorPosition, text || urls.map(u => u.toString()).join("\n"));
|
||||
root.focusInput();
|
||||
break;
|
||||
}
|
||||
drop.acceptProposedAction();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,50 +91,6 @@ CustomRect {
|
||||
|
||||
opacity: root.slim ? 0 : 1
|
||||
|
||||
state: titleText.readOnly ? "static" : "editing"
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "static"
|
||||
PropertyChanges {
|
||||
staticText.opacity: 1
|
||||
}
|
||||
PropertyChanges {
|
||||
titleText.opacity: 0
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "editing"
|
||||
PropertyChanges {
|
||||
staticText.opacity: 0
|
||||
}
|
||||
PropertyChanges {
|
||||
titleText.opacity: 1
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
transitions: [
|
||||
// Going static: titleText fades out (animated), staticText snaps in
|
||||
Transition {
|
||||
to: "static"
|
||||
Anim {
|
||||
target: titleText
|
||||
property: "opacity"
|
||||
type: Anim.SlowEffects
|
||||
}
|
||||
},
|
||||
// Going editing: staticText fades out (animated), titleText snaps in
|
||||
Transition {
|
||||
to: "editing"
|
||||
Anim {
|
||||
target: staticText
|
||||
property: "opacity"
|
||||
type: Anim.SlowEffects
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
@@ -150,7 +106,7 @@ CustomRect {
|
||||
color: Colors.layer(Colors.palette.m3surfaceContainerHighest, 1)
|
||||
implicitHeight: titleText.implicitHeight + Tokens.padding.extraSmall * 2
|
||||
radius: Tokens.rounding.full
|
||||
implicitWidth: titleText.width + Tokens.padding.large * 2
|
||||
implicitWidth: titleText.implicitWidth + Tokens.padding.large * 2
|
||||
opacity: titleText.readOnly ? 0 : 1
|
||||
|
||||
Behavior on opacity {
|
||||
@@ -166,6 +122,11 @@ CustomRect {
|
||||
anchors.right: parent.right
|
||||
implicitHeight: titleText.implicitHeight + Tokens.padding.small * 2
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: OpacityMask {
|
||||
maskSource: fadeMask
|
||||
}
|
||||
|
||||
TextFieldBase {
|
||||
id: titleText
|
||||
|
||||
@@ -173,12 +134,10 @@ CustomRect {
|
||||
anchors.leftMargin: readOnly ? 0 : Tokens.padding.large
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: readOnly ? Tokens.padding.small : Tokens.padding.extraSmall * 2
|
||||
width: Math.min(implicitWidth, parent.width - Tokens.padding.large * 2)
|
||||
font: Tokens.font.body.medium
|
||||
text: root.modelData.title || qsTr("New chat")
|
||||
readOnly: true
|
||||
color: Colors.palette.m3onSurface
|
||||
opacity: 0
|
||||
|
||||
Behavior on anchors.leftMargin {
|
||||
Anim {}
|
||||
@@ -187,11 +146,6 @@ CustomRect {
|
||||
Anim {}
|
||||
}
|
||||
|
||||
function cancelTitleEdit(): void {
|
||||
text = root.modelData.title;
|
||||
readOnly = true;
|
||||
}
|
||||
|
||||
onEditingFinished: root.modelData.title = text
|
||||
onReadOnlyChanged: {
|
||||
if (!readOnly) {
|
||||
@@ -202,24 +156,27 @@ CustomRect {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CustomText {
|
||||
id: staticText
|
||||
CustomRect {
|
||||
id: fadeMask
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: titleText.readOnly ? 0 : Tokens.padding.large
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: titleText.readOnly ? Tokens.padding.small : Tokens.padding.extraSmall * 2
|
||||
anchors.right: parent.right
|
||||
font: titleText.font
|
||||
text: titleText.text
|
||||
elide: CustomText.ElideRight
|
||||
anchors.fill: titleWrapper
|
||||
|
||||
Behavior on anchors.leftMargin {
|
||||
Anim {}
|
||||
layer.enabled: true
|
||||
visible: false
|
||||
|
||||
gradient: Gradient {
|
||||
orientation: Gradient.Horizontal
|
||||
|
||||
GradientStop {
|
||||
color: Qt.rgba(1, 1, 1, 1.0)
|
||||
position: 0.85
|
||||
}
|
||||
Behavior on anchors.topMargin {
|
||||
Anim {}
|
||||
|
||||
GradientStop {
|
||||
color: Qt.rgba(1, 1, 1, 0)
|
||||
position: 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -366,13 +323,7 @@ CustomRect {
|
||||
inactiveColor: Colors.layer(Colors.palette.m3surfaceContainerHighest, 3)
|
||||
inactiveOnColor: Colors.palette.m3onSurfaceVariant
|
||||
|
||||
onClicked: {
|
||||
if (!titleText.readOnly) {
|
||||
titleText.cancelTitleEdit();
|
||||
return;
|
||||
}
|
||||
root.remove(root.modelData);
|
||||
}
|
||||
onClicked: root.remove(root.modelData)
|
||||
}
|
||||
|
||||
IconButton {
|
||||
@@ -415,10 +366,7 @@ CustomRect {
|
||||
}
|
||||
|
||||
StateLayer {
|
||||
onClicked: {
|
||||
root.expanded = !root.expanded;
|
||||
titleText.readOnly = true;
|
||||
}
|
||||
onClicked: root.expanded = !root.expanded
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
|
||||
@@ -140,7 +140,7 @@ Item {
|
||||
label.transform: Rotation {
|
||||
origin.y: fabRoot.label.height / 2
|
||||
origin.x: fabRoot.label.width / 2
|
||||
angle: ChatState.fabExpanded && (ChatState.narrowSidebarExpanded || !ChatState.isWide || !ChatState.isWindow) ? 135 : 0
|
||||
angle: ChatState.fabExpanded && (ChatState.narrowSidebarExpanded && !ChatState.isWide || !ChatState.isWindow) ? 135 : 0
|
||||
|
||||
Behavior on angle {
|
||||
Anim {}
|
||||
@@ -148,9 +148,8 @@ Item {
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
if ((root.slim && !ChatState.narrowSidebarExpanded) || ChatState.shrinkSidebarOverride) {
|
||||
if (root.slim && !ChatState.narrowSidebarExpanded) {
|
||||
root.requestExpand();
|
||||
ChatState.shrinkSidebarOverride = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -184,10 +183,6 @@ Item {
|
||||
name: "pop out in window"
|
||||
icon: "open_in_new"
|
||||
}
|
||||
ListElement {
|
||||
name: "shrink sidebar"
|
||||
icon: "left_panel_close"
|
||||
}
|
||||
}
|
||||
|
||||
CustomRect {
|
||||
@@ -201,18 +196,7 @@ Item {
|
||||
|
||||
radius: sLayer.pressed ? Tokens.rounding.small : implicitHeight / 2
|
||||
color: Colors.palette.m3primaryContainer
|
||||
visible: {
|
||||
const n = modelData.name;
|
||||
const w = ChatState.isWindow;
|
||||
const wide = ChatState.isWide;
|
||||
|
||||
if (n === "pop out in window" && w)
|
||||
return false;
|
||||
if (n === "shrink sidebar" && (!w || !wide))
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
visible: !(modelData.name === "pop out in window" && ChatState.isWindow)
|
||||
clip: true
|
||||
|
||||
Behavior on radius {
|
||||
@@ -313,8 +297,6 @@ Item {
|
||||
root.newChatRequest();
|
||||
else if (name === "pop out in window")
|
||||
Detach.create();
|
||||
else if (name === "shrink sidebar")
|
||||
ChatState.shrinkSidebarOverride = true;
|
||||
|
||||
ChatState.fabExpanded = false;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ Singleton {
|
||||
property bool settledSlim: false
|
||||
property bool settledWide: false
|
||||
property bool narrowSidebarExpanded: false
|
||||
property bool shrinkSidebarOverride: false
|
||||
property bool windowIsWide: false
|
||||
property ShellScreen screen
|
||||
property bool sidebarVisible: ShellState.forActive().sidebar
|
||||
|
||||
@@ -28,9 +28,6 @@ Singleton {
|
||||
root.currentIdx = -1;
|
||||
}
|
||||
|
||||
onIsWideChanged: if (isWide)
|
||||
narrowSidebarExpanded = false
|
||||
|
||||
onSidebarVisibleChanged: if (!sidebarVisible)
|
||||
fabExpanded = false
|
||||
}
|
||||
|
||||
@@ -337,7 +337,7 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
ClipRect {
|
||||
CustomClippingRect {
|
||||
id: expandedTool
|
||||
|
||||
anchors.top: collapsedRow.bottom
|
||||
|
||||
@@ -220,8 +220,9 @@ CustomRect {
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
color: root.codeHeaderColor
|
||||
font: Tokens.font.label.builders.small.capitalization(Font.Capitalize).build()
|
||||
font: Tokens.font.label.small
|
||||
text: CodeIcons.name(root.language)
|
||||
visible: root.language.length > 0
|
||||
}
|
||||
|
||||
Item {
|
||||
@@ -251,7 +252,7 @@ CustomRect {
|
||||
}
|
||||
}
|
||||
|
||||
CustomRect {
|
||||
CustomClippingRect {
|
||||
id: codeRect
|
||||
|
||||
anchors.left: parent.left
|
||||
@@ -264,7 +265,6 @@ CustomRect {
|
||||
implicitWidth: codeText.implicitWidth + codeFlick.anchors.margins * 2
|
||||
implicitHeight: codeText.implicitHeight + codeFlick.anchors.margins * 2
|
||||
color: CodeColors.active.bg
|
||||
clip: true
|
||||
|
||||
Flickable {
|
||||
id: codeFlick
|
||||
@@ -272,9 +272,6 @@ CustomRect {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Tokens.padding.small
|
||||
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
contentWidth: codeText.implicitWidth
|
||||
|
||||
CustomScrollBar.horizontal: CustomScrollBar {
|
||||
flickable: codeFlick
|
||||
parent: codeFlick.parent
|
||||
@@ -283,13 +280,16 @@ CustomRect {
|
||||
anchors.right: parent.right
|
||||
}
|
||||
|
||||
CustomText {
|
||||
TextAreaBase.flickable: TextAreaBase {
|
||||
id: codeText
|
||||
|
||||
color: CodeColors.active.normal
|
||||
layer.enabled: true
|
||||
clip: false
|
||||
font: Tokens.font.mono.small
|
||||
textFormat: Text.RichText
|
||||
textFormat: root.colored ? Text.RichText : Text.PlainText
|
||||
text: root.colored ? root.html : root.plainForDisplay(root.code)
|
||||
readOnly: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -125,7 +125,7 @@ Item {
|
||||
current: root.current
|
||||
}
|
||||
|
||||
ClipRect {
|
||||
CustomClippingRect {
|
||||
id: expandedRect
|
||||
|
||||
anchors.left: parent.left
|
||||
|
||||
@@ -21,8 +21,12 @@ Item {
|
||||
property bool chatOpen: ChatState.chatSession !== null
|
||||
|
||||
readonly property bool isWide: root.width >= root.breakpoint
|
||||
readonly property bool wideExpandedSidebar: isWide && !ChatState.shrinkSidebarOverride
|
||||
readonly property bool narrowExpandedSidebar: !isWide && ChatState.narrowSidebarExpanded
|
||||
|
||||
readonly property bool showList: isWide || !chatOpen
|
||||
readonly property bool showContent: !isWide && chatOpen
|
||||
|
||||
function openConversation(conv: ChatSession, index: int): void {
|
||||
}
|
||||
|
||||
BlobGroup {
|
||||
id: blobGroup
|
||||
@@ -50,9 +54,9 @@ Item {
|
||||
anchors.left: parent.left
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.margins: Tokens.padding.medium
|
||||
implicitWidth: root.wideExpandedSidebar || root.narrowExpandedSidebar ? Config.sidebar.sizes.width : root.iconSize
|
||||
implicitWidth: root.isWide || ChatState.narrowSidebarExpanded ? Config.sidebar.sizes.width : root.iconSize
|
||||
highlight: true
|
||||
slim: !root.wideExpandedSidebar && !root.narrowExpandedSidebar
|
||||
slim: !root.isWide && !ChatState.narrowSidebarExpanded
|
||||
z: 1
|
||||
|
||||
Behavior on implicitWidth {
|
||||
@@ -82,13 +86,13 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
CustomClippingRect {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: invertedRect.borderLeft
|
||||
anchors.topMargin: invertedRect.borderTop
|
||||
anchors.bottomMargin: invertedRect.borderBottom
|
||||
anchors.rightMargin: invertedRect.borderRight
|
||||
clip: true
|
||||
radius: invertedRect.radius
|
||||
|
||||
children: [contentArea, dim]
|
||||
}
|
||||
@@ -96,10 +100,9 @@ Item {
|
||||
CustomRect {
|
||||
id: dim
|
||||
anchors.fill: parent
|
||||
radius: invertedRect.radius
|
||||
|
||||
color: Colors.palette.m3shadow
|
||||
opacity: root.narrowExpandedSidebar ? 0.3 : 0
|
||||
opacity: ChatState.narrowSidebarExpanded && !root.isWide ? 0.3 : 0
|
||||
z: 1
|
||||
|
||||
Behavior on opacity {
|
||||
@@ -152,7 +155,7 @@ Item {
|
||||
id: dimArea
|
||||
anchors.fill: parent
|
||||
preventStealing: true
|
||||
enabled: root.narrowExpandedSidebar
|
||||
enabled: !root.isWide && ChatState.narrowSidebarExpanded
|
||||
cursorShape: undefined
|
||||
|
||||
onClicked: {
|
||||
@@ -172,6 +175,12 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Binding {
|
||||
target: ChatState
|
||||
property: "windowIsWide"
|
||||
value: root.showList
|
||||
}
|
||||
|
||||
Binding {
|
||||
target: ChatState
|
||||
property: "isWide"
|
||||
|
||||
@@ -17,6 +17,76 @@ Item {
|
||||
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: flickable.contentHeight
|
||||
layer.enabled: true
|
||||
layer.smooth: true
|
||||
|
||||
layer.effect: Mask {
|
||||
maskSource: gradientMask
|
||||
}
|
||||
|
||||
Item {
|
||||
id: gradientMask
|
||||
|
||||
anchors.fill: parent
|
||||
layer.enabled: true
|
||||
visible: false
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
|
||||
gradient: Gradient {
|
||||
orientation: Gradient.Horizontal
|
||||
|
||||
GradientStop {
|
||||
color: Qt.rgba(0, 0, 0, 0)
|
||||
position: 0
|
||||
}
|
||||
|
||||
GradientStop {
|
||||
color: Qt.rgba(0, 0, 0, 1)
|
||||
position: 0.1
|
||||
}
|
||||
|
||||
GradientStop {
|
||||
color: Qt.rgba(0, 0, 0, 1)
|
||||
position: 0.9
|
||||
}
|
||||
|
||||
GradientStop {
|
||||
color: Qt.rgba(0, 0, 0, 0)
|
||||
position: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
implicitWidth: parent.width / 2
|
||||
opacity: flickable.contentX > 0 ? 0 : 1
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
implicitWidth: parent.width / 2
|
||||
opacity: flickable.contentX < flickable.contentWidth - parent.width ? 0 : 1
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CustomFlickable {
|
||||
id: flickable
|
||||
|
||||
@@ -112,7 +112,7 @@ CustomRect {
|
||||
}
|
||||
}
|
||||
|
||||
ClipRect {
|
||||
CustomClippingRect {
|
||||
anchors.fill: parent
|
||||
color: root.urgency === NotificationUrgency.Critical ? Colors.palette.m3error : root.urgency === NotificationUrgency.Low ? Colors.layer(Colors.palette.m3surfaceContainerHigh, 3) : Colors.palette.m3secondaryContainer
|
||||
radius: Tokens.rounding.full
|
||||
|
||||
@@ -218,7 +218,7 @@ void BlobInvertedRect::outerCornerRadii(float out[4]) const {
|
||||
const auto base = std::min(static_cast<float>(m_outerRadius), maxR);
|
||||
out[0] = std::min(
|
||||
m_outerTopRightRadius >= 0 ? static_cast<float>(m_outerTopRightRadius)
|
||||
: base,
|
||||
: base,
|
||||
maxR);
|
||||
out[1] = std::min(
|
||||
m_outerBottomRightRadius >= 0
|
||||
|
||||
@@ -60,15 +60,11 @@ class BlobInvertedRect : public BlobShape {
|
||||
void setOuterRadius(qreal v);
|
||||
|
||||
// Per-corner outer radii. -1 falls back to outerRadius (mirrors BlobRect).
|
||||
[[nodiscard]] qreal outerTopLeftRadius() const {
|
||||
return m_outerTopLeftRadius;
|
||||
}
|
||||
[[nodiscard]] qreal outerTopLeftRadius() const { return m_outerTopLeftRadius; }
|
||||
|
||||
void setOuterTopLeftRadius(qreal v);
|
||||
|
||||
[[nodiscard]] qreal outerTopRightRadius() const {
|
||||
return m_outerTopRightRadius;
|
||||
}
|
||||
[[nodiscard]] qreal outerTopRightRadius() const { return m_outerTopRightRadius; }
|
||||
|
||||
void setOuterTopRightRadius(qreal v);
|
||||
|
||||
|
||||
@@ -240,7 +240,8 @@ void BlobShape::updatePolish() {
|
||||
m_cachedInvertedRadius = 0;
|
||||
memset(m_cachedInvertedOuter, 0, sizeof(m_cachedInvertedOuter));
|
||||
memset(m_cachedInvertedInner, 0, sizeof(m_cachedInvertedInner));
|
||||
memset(m_cachedInvertedOuterRadii, 0, sizeof(m_cachedInvertedOuterRadii));
|
||||
memset(
|
||||
m_cachedInvertedOuterRadii, 0, sizeof(m_cachedInvertedOuterRadii));
|
||||
|
||||
auto* inv = m_group->invertedRect();
|
||||
if (inv) {
|
||||
|
||||
@@ -52,7 +52,7 @@ FontBuilder FontBuilder::letterSpacing(qreal spacing, bool absolute) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
FontBuilder FontBuilder::capitalization(QFont::Capitalization cap) {
|
||||
FontBuilder FontBuilder::capitalisation(QFont::Capitalization cap) {
|
||||
m_font.setCapitalization(cap);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ class FontBuilder {
|
||||
[[nodiscard]] Q_INVOKABLE FontBuilder
|
||||
letterSpacing(qreal spacing, bool absolute = true);
|
||||
[[nodiscard]] Q_INVOKABLE FontBuilder
|
||||
capitalization(QFont::Capitalization cap);
|
||||
capitalisation(QFont::Capitalization cap);
|
||||
[[nodiscard]] Q_INVOKABLE FontBuilder vaxis(const QString& tag, float value);
|
||||
[[nodiscard]] Q_INVOKABLE FontBuilder vaxes(const QVariantMap& axes);
|
||||
[[nodiscard]] Q_INVOKABLE QFont build() const;
|
||||
|
||||
@@ -662,17 +662,6 @@ void WallpaperService::drawSurface(WallpaperSurface* surface) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Release the context when this scope ends (see FrameService::drawSurface).
|
||||
// Holding it across the draw loop's sleep serializes the two raw-EGL clients on
|
||||
// NVIDIA, which makes eglSwapBuffers fail and starves other processes' contexts.
|
||||
struct ContextRelease {
|
||||
EGLDisplay display;
|
||||
~ContextRelease() {
|
||||
eglMakeCurrent(
|
||||
display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||
}
|
||||
} contextRelease{m_eglDisplay};
|
||||
|
||||
QVariantMap crops;
|
||||
QSize size;
|
||||
bool upload = false;
|
||||
@@ -712,9 +701,7 @@ void WallpaperService::drawSurface(WallpaperSurface* surface) {
|
||||
glDrawArrays(GL_TRIANGLES, 0, 3);
|
||||
|
||||
if (!eglSwapBuffers(m_eglDisplay, target))
|
||||
qWarning()
|
||||
<< "WallpaperService: eglSwapBuffers failed, eglGetError = 0x"
|
||||
<< QString::number(unsigned(eglGetError()), 16);
|
||||
qWarning() << "WallpaperService: eglSwapBuffers failed";
|
||||
}
|
||||
|
||||
void WallpaperService::addSurface(const QString& screen) {
|
||||
|
||||
@@ -22,12 +22,10 @@ qml_module(ZShell-services
|
||||
networkusage.hpp networkusage.cpp
|
||||
lyriccandidate.hpp lyriccandidate.cpp
|
||||
lyrics.hpp lyrics.cpp
|
||||
dropimporter.hpp dropimporter.cpp
|
||||
LIBRARIES
|
||||
Qt6::Core
|
||||
Qt6::Qml
|
||||
Qt6::Concurrent
|
||||
Qt6::Network
|
||||
PkgConfig::Pipewire
|
||||
PkgConfig::Aubio
|
||||
PkgConfig::Cava
|
||||
|
||||
@@ -1,263 +0,0 @@
|
||||
#include "dropimporter.hpp"
|
||||
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QMetaObject>
|
||||
#include <QMimeDatabase>
|
||||
#include <QNetworkReply>
|
||||
#include <QRegularExpression>
|
||||
|
||||
namespace {
|
||||
constexpr qint64 kMaxBytes = 100 * 1024 * 1024;
|
||||
|
||||
bool isHttp(const QUrl& u) {
|
||||
return u.scheme() == QLatin1String("http") ||
|
||||
u.scheme() == QLatin1String("https");
|
||||
}
|
||||
|
||||
QString firstImgSrc(const QString& html) {
|
||||
static const QRegularExpression re(
|
||||
R"(<img\b[^>]*?\bsrc\s*=\s*["']([^"']+)["'])",
|
||||
QRegularExpression::CaseInsensitiveOption);
|
||||
const auto m = re.match(html);
|
||||
if (!m.hasMatch()) return {};
|
||||
return m.captured(1).replace(QLatin1String("&"), QLatin1String("&"));
|
||||
}
|
||||
|
||||
bool looksLikeUrl(const QString& text) {
|
||||
const QString t = text.trimmed();
|
||||
if (t.isEmpty() || t.contains(QRegularExpression(QStringLiteral("\\s"))))
|
||||
return false;
|
||||
const QUrl u(t);
|
||||
return u.isValid() && (isHttp(u) || u.isLocalFile());
|
||||
}
|
||||
|
||||
QString sanitize(QString name) {
|
||||
name = QFileInfo(name).fileName();
|
||||
name.replace(
|
||||
QRegularExpression(QStringLiteral("[\\\\/:*?\"<>|\\x00-\\x1f]")),
|
||||
QStringLiteral("_"));
|
||||
return name.left(120);
|
||||
}
|
||||
|
||||
QString fileNameFor(QNetworkReply* reply, const QUrl& url) {
|
||||
static const QRegularExpression cd(
|
||||
QStringLiteral("filename\\*?=(?:UTF-8'')?\"?([^\";]+)\"?"),
|
||||
QRegularExpression::CaseInsensitiveOption);
|
||||
const auto m =
|
||||
cd.match(QString::fromUtf8(reply->rawHeader("Content-Disposition")));
|
||||
QString name = m.hasMatch()
|
||||
? QUrl::fromPercentEncoding(m.captured(1).toUtf8())
|
||||
: url.fileName();
|
||||
name = sanitize(name);
|
||||
return name.isEmpty() ? QStringLiteral("download") : name;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace ZShell::Services {
|
||||
|
||||
DropImporter::DropImporter(QObject* parent) : QObject(parent) {}
|
||||
|
||||
DropImporter::Decision DropImporter::decide(
|
||||
const QList<QUrl>& urls,
|
||||
const QString& text,
|
||||
const QString& html,
|
||||
bool hasRawImage) const {
|
||||
for (const QUrl& u : urls)
|
||||
if (u.isLocalFile()) return Attach;
|
||||
|
||||
const QString src = firstImgSrc(html);
|
||||
if (hasRawImage ||
|
||||
(!src.isEmpty() && (text.trimmed().isEmpty() || looksLikeUrl(text))))
|
||||
return Attach;
|
||||
|
||||
QMimeDatabase db;
|
||||
for (const QUrl& u : urls) {
|
||||
if (!isHttp(u) || QFileInfo(u.path()).suffix().isEmpty()) continue;
|
||||
const QMimeType mt = db.mimeTypeForUrl(u);
|
||||
if (!mt.isDefault() && !mt.inherits(QStringLiteral("text/html")))
|
||||
return Attach;
|
||||
}
|
||||
|
||||
if (!text.isEmpty() || !urls.isEmpty()) return InsertText;
|
||||
return Ignore;
|
||||
}
|
||||
|
||||
int DropImporter::importAttachments(
|
||||
const QList<QUrl>& urls, const QString& html, const QByteArray& rawImage) {
|
||||
const int id = ++m_nextId;
|
||||
m_requests[id].pending = 1;
|
||||
|
||||
if (!rawImage.isEmpty()) {
|
||||
saveBytes(id, rawImage);
|
||||
} else if (const QString src = firstImgSrc(html); !src.isEmpty()) {
|
||||
if (src.startsWith(QLatin1String("data:"), Qt::CaseInsensitive)) {
|
||||
const int comma = src.indexOf(QLatin1Char(','));
|
||||
if (comma < 0) {
|
||||
failWith(id, tr("Malformed data URI"));
|
||||
} else {
|
||||
const bool b64 = src.left(comma).contains(
|
||||
QLatin1String(";base64"), Qt::CaseInsensitive);
|
||||
const QString payload = src.mid(comma + 1);
|
||||
saveBytes(
|
||||
id,
|
||||
b64 ? QByteArray::fromBase64(payload.toLatin1())
|
||||
: QByteArray::fromPercentEncoding(payload.toUtf8()));
|
||||
}
|
||||
} else {
|
||||
const QUrl u(src);
|
||||
if (u.isLocalFile())
|
||||
addLocal(id, u);
|
||||
else if (isHttp(u))
|
||||
download(id, u);
|
||||
else
|
||||
failWith(id, tr("Unsupported image source"));
|
||||
}
|
||||
} else {
|
||||
for (const QUrl& u : urls) {
|
||||
if (u.isLocalFile())
|
||||
addLocal(id, u);
|
||||
else if (isHttp(u))
|
||||
download(id, u);
|
||||
else
|
||||
failWith(id, tr("Unsupported URL: %1").arg(u.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
QMetaObject::invokeMethod(
|
||||
this, [this, id] { finishUnit(id); }, Qt::QueuedConnection);
|
||||
return id;
|
||||
}
|
||||
|
||||
void DropImporter::addLocal(int id, const QUrl& url) {
|
||||
const QFileInfo fi(url.toLocalFile());
|
||||
if (!fi.exists())
|
||||
failWith(id, tr("File not found: %1").arg(fi.filePath()));
|
||||
else if (fi.isDir())
|
||||
failWith(id, tr("Folders can't be attached: %1").arg(fi.fileName()));
|
||||
else if (fi.size() > kMaxBytes)
|
||||
failWith(id, tr("File too large: %1").arg(fi.fileName()));
|
||||
else
|
||||
succeed(id, fi.absoluteFilePath());
|
||||
}
|
||||
|
||||
void DropImporter::saveBytes(int id, const QByteArray& bytes) {
|
||||
if (bytes.isEmpty() || bytes.size() > kMaxBytes) {
|
||||
failWith(id, tr("Empty or oversized data"));
|
||||
return;
|
||||
}
|
||||
const QString dir = newSubdir();
|
||||
if (dir.isEmpty()) {
|
||||
failWith(id, tr("No temp directory"));
|
||||
return;
|
||||
}
|
||||
|
||||
const QString suffix =
|
||||
QMimeDatabase().mimeTypeForData(bytes).preferredSuffix();
|
||||
const QString path =
|
||||
dir + QStringLiteral("/dropped") +
|
||||
(suffix.isEmpty() ? QString() : QLatin1Char('.') + suffix);
|
||||
|
||||
QFile f(path);
|
||||
if (!f.open(QIODevice::WriteOnly) || f.write(bytes) != bytes.size()) {
|
||||
failWith(id, tr("Could not write temp file"));
|
||||
return;
|
||||
}
|
||||
f.close();
|
||||
succeed(id, path);
|
||||
}
|
||||
|
||||
void DropImporter::download(int id, const QUrl& url) {
|
||||
const QString dir = newSubdir();
|
||||
if (dir.isEmpty()) {
|
||||
failWith(id, tr("No temp directory"));
|
||||
return;
|
||||
}
|
||||
|
||||
++m_requests[id].pending;
|
||||
|
||||
QNetworkRequest req(url);
|
||||
req.setAttribute(
|
||||
QNetworkRequest::RedirectPolicyAttribute,
|
||||
QNetworkRequest::NoLessSafeRedirectPolicy);
|
||||
QNetworkReply* reply = m_nam.get(req);
|
||||
auto* file = new QFile(dir + QStringLiteral("/download.part"), reply);
|
||||
|
||||
connect(reply, &QNetworkReply::readyRead, this, [reply, file] {
|
||||
if (!file->isOpen() && !file->open(QIODevice::WriteOnly)) {
|
||||
reply->abort();
|
||||
return;
|
||||
}
|
||||
file->write(reply->readAll());
|
||||
if (file->size() > kMaxBytes) reply->abort();
|
||||
});
|
||||
|
||||
connect(
|
||||
reply,
|
||||
&QNetworkReply::finished,
|
||||
this,
|
||||
[this, reply, file, id, dir, url] {
|
||||
reply->deleteLater();
|
||||
const bool tooBig = file->size() > kMaxBytes;
|
||||
const bool wrote = file->isOpen();
|
||||
file->close();
|
||||
|
||||
if (tooBig) {
|
||||
failWith(id, tr("Download too large: %1").arg(url.toString()));
|
||||
} else if (reply->error() != QNetworkReply::NoError) {
|
||||
failWith(
|
||||
id,
|
||||
tr("Download failed (%1): %2")
|
||||
.arg(url.toString(), reply->errorString()));
|
||||
} else if (!wrote) {
|
||||
failWith(id, tr("Empty response: %1").arg(url.toString()));
|
||||
} else {
|
||||
const QString name = fileNameFor(reply, url);
|
||||
QString finalPath = dir + QLatin1Char('/') + name;
|
||||
if (QFile::rename(file->fileName(), finalPath)) {
|
||||
if (QFileInfo(name).suffix().isEmpty()) {
|
||||
const QString sfx =
|
||||
QMimeDatabase()
|
||||
.mimeTypeForFile(
|
||||
finalPath, QMimeDatabase::MatchContent)
|
||||
.preferredSuffix();
|
||||
if (!sfx.isEmpty() &&
|
||||
QFile::rename(
|
||||
finalPath, finalPath + QLatin1Char('.') + sfx))
|
||||
finalPath += QLatin1Char('.') + sfx;
|
||||
}
|
||||
succeed(id, finalPath);
|
||||
} else {
|
||||
failWith(id, tr("Could not store download"));
|
||||
}
|
||||
}
|
||||
finishUnit(id);
|
||||
});
|
||||
}
|
||||
|
||||
void DropImporter::succeed(int id, const QString& path) {
|
||||
if (auto it = m_requests.find(id); it != m_requests.end())
|
||||
it->files.append(path);
|
||||
}
|
||||
|
||||
void DropImporter::failWith(int id, const QString& message) {
|
||||
if (auto it = m_requests.find(id); it != m_requests.end())
|
||||
it->errors.append(message);
|
||||
}
|
||||
|
||||
void DropImporter::finishUnit(int id) {
|
||||
auto it = m_requests.find(id);
|
||||
if (it == m_requests.end() || --it->pending > 0) return;
|
||||
const Request r = *it;
|
||||
m_requests.erase(it);
|
||||
emit importFinished(id, r.files, r.errors);
|
||||
}
|
||||
|
||||
QString DropImporter::newSubdir() {
|
||||
if (!m_tempDir.isValid()) return {};
|
||||
const QString p = m_tempDir.filePath(QString::number(++m_counter));
|
||||
return QDir().mkpath(p) ? p : QString();
|
||||
}
|
||||
|
||||
} // namespace ZShell::Services
|
||||
@@ -1,62 +0,0 @@
|
||||
#pragma once
|
||||
#include <QHash>
|
||||
|
||||
#include <QList>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QObject>
|
||||
#include <QQmlEngine>
|
||||
#include <QStringList>
|
||||
#include <QTemporaryDir>
|
||||
#include <QUrl>
|
||||
|
||||
namespace ZShell::Services {
|
||||
|
||||
class DropImporter : public QObject {
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
QML_SINGLETON
|
||||
|
||||
public:
|
||||
enum Decision { Ignore, InsertText, Attach };
|
||||
Q_ENUM(Decision)
|
||||
|
||||
explicit DropImporter(QObject* parent = nullptr);
|
||||
|
||||
Q_INVOKABLE Decision decide(
|
||||
const QList<QUrl>& urls,
|
||||
const QString& text,
|
||||
const QString& html,
|
||||
bool hasRawImage) const;
|
||||
|
||||
Q_INVOKABLE int importAttachments(
|
||||
const QList<QUrl>& urls,
|
||||
const QString& html,
|
||||
const QByteArray& rawImage);
|
||||
|
||||
signals:
|
||||
void importFinished(
|
||||
int id, const QStringList& paths, const QStringList& errors);
|
||||
|
||||
private:
|
||||
struct Request {
|
||||
int pending = 0;
|
||||
QStringList files;
|
||||
QStringList errors;
|
||||
};
|
||||
|
||||
void addLocal(int id, const QUrl& url);
|
||||
void download(int id, const QUrl& url);
|
||||
void saveBytes(int id, const QByteArray& bytes);
|
||||
void succeed(int id, const QString& path);
|
||||
void failWith(int id, const QString& message);
|
||||
void finishUnit(int id);
|
||||
QString newSubdir();
|
||||
|
||||
QNetworkAccessManager m_nam;
|
||||
QTemporaryDir m_tempDir;
|
||||
QHash<int, Request> m_requests;
|
||||
int m_nextId = 0;
|
||||
int m_counter = 0;
|
||||
};
|
||||
|
||||
} // namespace ZShell::Services
|
||||
+2
-2
@@ -3,9 +3,9 @@ FROM archlinux:latest
|
||||
RUN pacman -Syu --noconfirm \
|
||||
&& pacman -S --needed --noconfirm \
|
||||
base-devel cmake ninja clang lld nodejs tree-sitter \
|
||||
jkqtplotter cmark-gfm git wayland-protocols ffmpeg mpv \
|
||||
jkqtplotter cmark-gfm git \
|
||||
qt6-base qt6-declarative qt6-shadertools qt6-5compat \
|
||||
qt6-canvaspainter qt6-wayland qt5-wayland \
|
||||
qt6-canvaspainter qt6-wayland qt5-wayland\
|
||||
libqalculate pipewire aubio cava lm_sensors glib2 \
|
||||
&& pacman -Scc --noconfirm
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
@@ -9,15 +8,6 @@ args = ["qs", "-c", "zshell"]
|
||||
|
||||
app = typer.Typer()
|
||||
|
||||
qt_logging_rules = (
|
||||
"qt.qml.warning=false;"
|
||||
"qt.qpa.wayland.*=false;"
|
||||
"scene=false;"
|
||||
"quickshell.service.sni.watcher=false;"
|
||||
"default.warning=false;"
|
||||
"default.info=false"
|
||||
)
|
||||
|
||||
|
||||
@app.command()
|
||||
def kill():
|
||||
@@ -29,13 +19,8 @@ def kill():
|
||||
|
||||
|
||||
def start_instance(no_daemon: bool = False) -> None:
|
||||
env = os.environ.copy()
|
||||
env["QT_LOGGING_RULES"] = qt_logging_rules
|
||||
|
||||
result = subprocess.run(
|
||||
args + ["-n"] + ([] if no_daemon else ["-d"]),
|
||||
capture_output=True,
|
||||
env=env,
|
||||
args + ["-n"] + ([] if no_daemon else ["-d"]), capture_output=True
|
||||
)
|
||||
stdout = result.stdout.decode().strip()
|
||||
if stdout and "already running" in stdout.lower():
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
BUILD ?= cmake -B build -G Ninja
|
||||
NONUITKA ?= cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DENABLE_MODULES="plugin;shell;m3shapes"
|
||||
NINJA ?= ninja -C build
|
||||
COMPILE ?= $(BUILD) && $(NINJA)
|
||||
INSTALL ?= $(BUILD) && $(NINJA) && sudo ninja -C build install
|
||||
|
||||
CPPFMT ?= find . \( -name '*.cpp' -o -name '*.h' -o -name '*.hpp' \) -not -path './build/*' -exec clang-format -i --style=file {} +
|
||||
CPPLINT ?= run-clang-tidy -p build -j "$$(nproc)" -source-filter="^$$(pwd)/Plugins/" -header-filter=".*Plugins/.*" -removed-arg=-mno-direct-extern-access
|
||||
|
||||
NUITKA ?= nuitka --module --include-package=zshell cli/src/zshell/ --output-dir=cli/
|
||||
|
||||
.DEFAULT_GOAL := help
|
||||
|
||||
# string is misaligned to align when run in terminal
|
||||
help:
|
||||
@echo "build cmake builds into build directory"
|
||||
@echo "install cmake builds, compiles and installs into filesystem"
|
||||
@echo "compile cmake builds and compiles C++ code"
|
||||
@echo "cpp-fmt format C++ code with clang-format"
|
||||
@echo "cpp-lint lint check C++ code with clang-tidy"
|
||||
@echo "cpp-compile similar to make compile, but excludes Nuitka from compiling python"
|
||||
@echo "py-fmt format Python code with ruff"
|
||||
@echo "py-lint lint check Python code with ruff"
|
||||
@echo "py-check lsp and typecheck all Python code using Basedpyright"
|
||||
@echo "py-test run all Python tests using Pytest"
|
||||
@echo "py-compile compiles Python code using Nuitka"
|
||||
@echo "rs-compile compiles Rust code"
|
||||
@echo "rs-format formats Rust code with Rustfmt"
|
||||
@echo "rs-lint lint check Rust code with Clippy"
|
||||
@echo "rs-test runs all Rust tests"
|
||||
@echo "qml-fmt format QML code with qmlformat"
|
||||
@echo "check-cpp runs all C++ checks"
|
||||
@echo "check-py runs all Python checks"
|
||||
@echo "check-rs runs all Rust checks"
|
||||
|
||||
build:
|
||||
$(BUILD)
|
||||
|
||||
install:
|
||||
$(INSTALL)
|
||||
|
||||
compile:
|
||||
$(COMPILE)
|
||||
|
||||
# C++
|
||||
cpp-fmt:
|
||||
$(CPPFMT)
|
||||
|
||||
cpp-lint:
|
||||
$(CPPLINT)
|
||||
|
||||
cpp-compile:
|
||||
$(NONUITKA)
|
||||
|
||||
# Python
|
||||
py-fmt:
|
||||
ruff format --check
|
||||
|
||||
py-lint:
|
||||
ruff check ./cli
|
||||
|
||||
py-check:
|
||||
basedpyright
|
||||
|
||||
py-test:
|
||||
python -m pytest ./cli/tests/ -v
|
||||
|
||||
# Want to use gcc, clang or zig C compiler instead? Output directory?
|
||||
py-compile:
|
||||
$(NUITKA)
|
||||
|
||||
# Rust
|
||||
rs-compile:
|
||||
cd ./zshell-img-tools/ && cargo run
|
||||
|
||||
rs-fmt:
|
||||
cd ./zshell-img-tools/ && cargo fmt && echo "formatted!"
|
||||
|
||||
rs-lint:
|
||||
cd ./zshell-img-tools/ && cargo clippy
|
||||
|
||||
rs-test:
|
||||
cd ./zshell-img-tools/ && cargo test
|
||||
|
||||
# Qml
|
||||
qml-fmt:
|
||||
find -name '*.qml' -exec /usr/lib/qt6/bin/qmlformat -i {} +
|
||||
|
||||
# Full checks
|
||||
check-cpp:
|
||||
$(MAKE) cpp-fmt
|
||||
$(MAKE) cpp-lint
|
||||
$(MAKE) compile
|
||||
|
||||
check-rs:
|
||||
$(MAKE) rs-fmt
|
||||
$(MAKE) rs-lint
|
||||
$(MAKE) rs-test
|
||||
$(MAKE) rs-compile
|
||||
|
||||
check-py:
|
||||
$(MAKE) py-fmt
|
||||
$(MAKE) py-lint
|
||||
$(MAKE) py-test
|
||||
$(MAKE) py-check
|
||||
$(MAKE) py-compile
|
||||
@@ -1,5 +1,7 @@
|
||||
//@ pragma UseQApplication
|
||||
//@ pragma DefaultEnv QSG_RENDER_LOOP=threaded
|
||||
// @ pragma DefaultEnv QSG_RHI_BACKEND=vulkan
|
||||
//@ pragma DefaultEnv QSG_NO_VSYNC=1
|
||||
//@ pragma DefaultEnv QS_NO_RELOAD_POPUP=1
|
||||
//@ pragma DefaultEnv QT_QUICK_FLICKABLE_WHEEL_DECELERATION=10000
|
||||
//@ pragma DropExpensiveFonts
|
||||
|
||||
Reference in New Issue
Block a user