Compare commits
42
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
abdde2450d | ||
|
|
fd05b3d4c4 | ||
|
|
9d8bde1413 | ||
|
|
118b8f482a | ||
|
|
b58b4fdc9c | ||
|
|
a56afd7fac | ||
|
|
b0cb43f75c | ||
|
|
e655ec5bc7 | ||
|
|
dc0f222ace | ||
|
|
de19fd6ac4 | ||
|
|
9edbcbd610 | ||
|
|
cd44d27b74 | ||
|
|
0d0faecb1c | ||
|
|
200a6a5d54 | ||
|
|
2d2231745c | ||
|
|
187e97a081 | ||
|
|
0f54d93675 | ||
|
|
5401cfbc0a | ||
|
|
e35c87b3f2 | ||
|
|
cbd4e6caa7 | ||
|
|
36cddc96c3 | ||
|
|
6781078467 | ||
|
|
d3345906b9 | ||
|
|
08d42e1f8a | ||
|
|
45aa6c9005 | ||
|
|
1763b03287 | ||
|
|
72ff6ad42a | ||
|
|
2a45740020 | ||
|
|
0a503bc27c | ||
|
|
1fe8a3a959 | ||
|
|
ae25f33fc8
|
||
|
|
02267ec9a6
|
||
|
|
4b8d74a7e5
|
||
|
|
1fa3c84a56
|
||
|
|
fc81be922f | ||
|
|
8d54e1ce6f | ||
|
|
bee8cd061b | ||
|
|
b9e818faa9 | ||
|
|
2cabd72fc0 | ||
|
|
255db9e533 | ||
|
|
a4f6aa1d4a | ||
|
|
3883917228 |
@@ -0,0 +1,3 @@
|
||||
|
||||
#omz revision: d2b7f557cceaddb16ba89bbf8200d8a8df35c4d8
|
||||
#omz fpath: /home/zach/.oh-my-zsh/plugins/colored-man-pages /home/zach/.oh-my-zsh/plugins/universalarchive /home/zach/.oh-my-zsh/plugins/tldr /home/zach/.oh-my-zsh/plugins/extract /home/zach/.oh-my-zsh/plugins/copypath /home/zach/.oh-my-zsh/plugins/cp /home/zach/.oh-my-zsh/plugins/conda-env /home/zach/.oh-my-zsh/plugins/command-not-found /home/zach/.oh-my-zsh/plugins/dirhistory /home/zach/.oh-my-zsh/plugins/copybuffer /home/zach/.oh-my-zsh/plugins/copyfile /home/zach/.oh-my-zsh/custom/plugins/fast-syntax-highlighting /home/zach/.oh-my-zsh/custom/plugins/zsh-syntax-highlighting /home/zach/.oh-my-zsh/custom/plugins/zsh-autosuggestions /home/zach/.oh-my-zsh/plugins/archlinux /home/zach/.oh-my-zsh/plugins/web-search /home/zach/.oh-my-zsh/plugins/sudo /home/zach/.oh-my-zsh/plugins/git /home/zach/.oh-my-zsh/functions /home/zach/.oh-my-zsh/completions /home/zach/.oh-my-zsh/custom/functions /home/zach/.oh-my-zsh/custom/completions /home/zach/tools/ripgrep/complete /home/linuxbrew/.linuxbrew/share/zsh/site-functions /home/zach/.oh-my-zsh/plugins/colored-man-pages /home/zach/.oh-my-zsh/plugins/universalarchive /home/zach/.oh-my-zsh/plugins/tldr /home/zach/.oh-my-zsh/plugins/extract /home/zach/.oh-my-zsh/plugins/copypath /home/zach/.oh-my-zsh/plugins/cp /home/zach/.oh-my-zsh/plugins/conda-env /home/zach/.oh-my-zsh/plugins/command-not-found /home/zach/.oh-my-zsh/plugins/dirhistory /home/zach/.oh-my-zsh/plugins/copybuffer /home/zach/.oh-my-zsh/plugins/copyfile /home/zach/.oh-my-zsh/custom/plugins/fast-syntax-highlighting /home/zach/.oh-my-zsh/custom/plugins/zsh-syntax-highlighting /home/zach/.oh-my-zsh/custom/plugins/zsh-autosuggestions /home/zach/.oh-my-zsh/plugins/archlinux /home/zach/.oh-my-zsh/plugins/web-search /home/zach/.oh-my-zsh/plugins/sudo /home/zach/.oh-my-zsh/plugins/git /home/zach/.oh-my-zsh/functions /home/zach/.oh-my-zsh/completions /home/zach/.oh-my-zsh/custom/functions /home/zach/.oh-my-zsh/custom/completions /home/zach/.oh-my-zsh/cache/completions /home/zach/tools/ripgrep/complete /home/linuxbrew/.linuxbrew/share/zsh/site-functions /usr/local/share/zsh/site-functions /usr/share/zsh/site-functions /usr/share/zsh/functions/Calendar /usr/share/zsh/functions/Chpwd /usr/share/zsh/functions/Completion /usr/share/zsh/functions/Completion/Base /usr/share/zsh/functions/Completion/Linux /usr/share/zsh/functions/Completion/Unix /usr/share/zsh/functions/Completion/X /usr/share/zsh/functions/Completion/Zsh /usr/share/zsh/functions/Exceptions /usr/share/zsh/functions/MIME /usr/share/zsh/functions/Math /usr/share/zsh/functions/Misc /usr/share/zsh/functions/Newuser /usr/share/zsh/functions/Prompts /usr/share/zsh/functions/TCP /usr/share/zsh/functions/VCS_Info /usr/share/zsh/functions/VCS_Info/Backends /usr/share/zsh/functions/Zftp /usr/share/zsh/functions/Zle /home/zach/.zfunc
|
||||
@@ -0,0 +1,5 @@
|
||||
CompileFlags:
|
||||
Remove:
|
||||
# GCC-only flags from the system's default spec that clang/clangd cannot
|
||||
# parse. They appear in compile_commands.json but are not set by the project.
|
||||
- -mno-direct-extern-access
|
||||
@@ -38,7 +38,6 @@ body:
|
||||
attributes:
|
||||
label: OS
|
||||
options:
|
||||
- NixOS
|
||||
- Arch
|
||||
- Debian / Ubuntu
|
||||
- Fedora
|
||||
|
||||
@@ -30,6 +30,7 @@ body:
|
||||
- Refactor / style
|
||||
- CI / build / tooling
|
||||
- Documentation
|
||||
- Other (Describe in description box with header "Issue type")
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
||||
+12
-2
@@ -50,6 +50,7 @@ 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")
|
||||
@@ -61,10 +62,12 @@ set(CMAKE_INSTALL_MESSAGE NEVER)
|
||||
|
||||
add_compile_options(
|
||||
-Wall -Wextra -Wpedantic -Wshadow -Wconversion
|
||||
-Wold-style-cast -Wnull-dereference -Wdouble-promotion
|
||||
-Wformat=2 -Wfloat-equal -Woverloaded-virtual
|
||||
-Wnull-dereference -Wdouble-promotion
|
||||
-Wformat=2 -Wfloat-equal
|
||||
-Wsign-conversion -Wredundant-decls -Wswitch
|
||||
-Wunreachable-code
|
||||
$<$<COMPILE_LANGUAGE:CXX>:-Wold-style-cast>
|
||||
$<$<COMPILE_LANGUAGE:CXX>:-Woverloaded-virtual>
|
||||
)
|
||||
|
||||
|
||||
@@ -188,4 +191,11 @@ if("m3shapes" IN_LIST ENABLE_MODULES)
|
||||
if(TARGET m3shapesplugin)
|
||||
set_target_properties(m3shapesplugin PROPERTIES INSTALL_RPATH "$ORIGIN")
|
||||
endif()
|
||||
|
||||
# Silence warnings that come from m3shapes files we do not manage.
|
||||
foreach(m3shapes_target m3shapes_core m3shapes_morph m3shapes_shapes m3shapes m3shapesplugin)
|
||||
if(TARGET ${m3shapes_target})
|
||||
target_compile_options(${m3shapes_target} PRIVATE -Wno-shadow -Wno-float-equal)
|
||||
endif()
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
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: src
|
||||
|
||||
hideSource: true
|
||||
sourceItem: contentItem
|
||||
}
|
||||
|
||||
RoundedMask {
|
||||
anchors.fill: parent
|
||||
source: src
|
||||
topLeftRadius: root.topLeftRadius
|
||||
topRightRadius: root.topRightRadius
|
||||
bottomRightRadius: root.bottomRightRadius
|
||||
bottomLeftRadius: root.bottomLeftRadius
|
||||
color: root.color
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
CAnim {}
|
||||
}
|
||||
}
|
||||
@@ -2,55 +2,57 @@ import QtQuick
|
||||
import ZShell.Config
|
||||
import qs.Services
|
||||
|
||||
CustomClippingRect {
|
||||
Item {
|
||||
id: track
|
||||
|
||||
required property var scrollBar
|
||||
required property var mouseArea
|
||||
|
||||
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
|
||||
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;
|
||||
return 0;
|
||||
}
|
||||
ClipRect {
|
||||
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
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
implicitHeight: handle.implicitHeight
|
||||
|
||||
Behavior on implicitHeight {
|
||||
Anim {}
|
||||
}
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
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;
|
||||
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
|
||||
|
||||
MouseArea {
|
||||
acceptedButtons: Qt.NoButton
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
hoverEnabled: true
|
||||
Behavior on implicitHeight {
|
||||
Anim {}
|
||||
}
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
acceptedButtons: Qt.NoButton
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
hoverEnabled: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+16
-17
@@ -34,7 +34,7 @@ MouseArea {
|
||||
cursorShape: expanded ? Qt.ArrowCursor : undefined
|
||||
enabled: expanded
|
||||
hoverEnabled: expanded
|
||||
layer.enabled: opacity < 1
|
||||
layer.enabled: false
|
||||
opacity: expanded ? 1 : 0
|
||||
parent: {
|
||||
const win = QsWindow.window;
|
||||
@@ -101,8 +101,7 @@ MouseArea {
|
||||
yScale: root.expanded ? 1 : 0.1
|
||||
|
||||
Behavior on yScale {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,7 +123,7 @@ MouseArea {
|
||||
model: root.items
|
||||
|
||||
CustomRect {
|
||||
id: item
|
||||
id: menuItem
|
||||
|
||||
readonly property bool active: modelData === root?.active
|
||||
required property int index
|
||||
@@ -137,18 +136,17 @@ MouseArea {
|
||||
radius: menu.radius - column.anchors.margins
|
||||
|
||||
Behavior on radius {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
|
||||
StateLayer {
|
||||
color: item.active ? Colors.palette.m3onTertiaryContainer : Colors.palette.m3onSurface
|
||||
color: menuItem.active ? Colors.palette.m3onTertiaryContainer : Colors.palette.m3onSurface
|
||||
enabled: root.expanded
|
||||
|
||||
onClicked: {
|
||||
root.itemSelected(item.modelData);
|
||||
root.active = item.modelData;
|
||||
item.modelData.clicked();
|
||||
root.itemSelected(menuItem.modelData);
|
||||
root.active = menuItem.modelData;
|
||||
menuItem.modelData.clicked();
|
||||
root.expanded = false;
|
||||
}
|
||||
}
|
||||
@@ -162,26 +160,27 @@ MouseArea {
|
||||
|
||||
MaterialIcon {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
color: item.active ? Colors.palette.m3onTertiaryContainer : Colors.palette.m3onSurfaceVariant
|
||||
text: item.modelData?.icon ?? ""
|
||||
color: menuItem.active ? Colors.palette.m3onTertiaryContainer : Colors.palette.m3onSurfaceVariant
|
||||
text: menuItem.modelData?.icon ?? ""
|
||||
visible: text !== ""
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.fillWidth: true
|
||||
color: item.active ? Colors.palette.m3onTertiaryContainer : Colors.palette.m3onSurface
|
||||
text: item.modelData?.text ?? ""
|
||||
color: menuItem.active ? Colors.palette.m3onTertiaryContainer : Colors.palette.m3onSurface
|
||||
text: menuItem.modelData?.text ?? ""
|
||||
}
|
||||
|
||||
Loader {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
active: item.modelData?.trailingIcon.length > 0
|
||||
active: menuItem.modelData?.trailingIcon.length > 0
|
||||
asynchronous: true
|
||||
visible: active
|
||||
|
||||
sourceComponent: MaterialIcon {
|
||||
color: item.active ? Colors.palette.m3onTertiaryContainer : Colors.palette.m3onSurfaceVariant
|
||||
text: item.modelData.trailingIcon
|
||||
color: menuItem.active ? Colors.palette.m3onTertiaryContainer : Colors.palette.m3onSurfaceVariant
|
||||
text: menuItem.modelData.trailingIcon
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ TextField {
|
||||
implicitHeight: contentHeight + topPadding + bottomPadding
|
||||
implicitWidth: contentWidth + leftPadding + rightPadding
|
||||
placeholderTextColor: Colors.palette.m3onSurfaceVariant // No anim cause placeholder is custom
|
||||
renderType: echoMode === TextField.Password ? TextField.QtRendering : TextField.NativeRendering
|
||||
renderType: TextField.QtRendering
|
||||
selectedTextColor: color
|
||||
selectionColor: Qt.alpha(Colors.palette.m3primary, 0.4)
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Effects
|
||||
|
||||
CustomFlickable {
|
||||
@@ -9,6 +10,7 @@ CustomFlickable {
|
||||
property real bottomFadeOpacity: fadeShouldBeActive(false) ? 0 : 1
|
||||
property real fadeAmount: 0.1
|
||||
property real topFadeOpacity: fadeShouldBeActive(true) ? 0 : 1
|
||||
readonly property real dpr: Math.ceil((QsWindow.window as QsWindow)?.devicePixelRatio ?? 1)
|
||||
|
||||
function fadeShouldBeActive(isStart: bool): bool {
|
||||
// When content is smaller than flickable size, hide fade when rebound starts
|
||||
@@ -28,6 +30,7 @@ CustomFlickable {
|
||||
type: Anim.SlowEffects
|
||||
}
|
||||
}
|
||||
layer.textureSize: Qt.size(width * dpr, height * dpr)
|
||||
layer.effect: Mask {
|
||||
maskSource: mask
|
||||
|
||||
|
||||
@@ -2,55 +2,57 @@ import QtQuick
|
||||
import ZShell.Config
|
||||
import qs.Services
|
||||
|
||||
CustomClippingRect {
|
||||
Item {
|
||||
id: track
|
||||
|
||||
required property var scrollBar
|
||||
required property var mouseArea
|
||||
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
implicitWidth: handle.implicitWidth
|
||||
radius: Tokens.rounding.full
|
||||
|
||||
CustomRect {
|
||||
id: handle
|
||||
|
||||
ClipRect {
|
||||
anchors.bottom: parent.bottom
|
||||
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;
|
||||
return 0;
|
||||
}
|
||||
anchors.top: parent.top
|
||||
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
|
||||
implicitWidth: handle.implicitWidth
|
||||
|
||||
Behavior on implicitWidth {
|
||||
Anim {}
|
||||
}
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
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;
|
||||
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
|
||||
|
||||
MouseArea {
|
||||
acceptedButtons: Qt.NoButton
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
hoverEnabled: true
|
||||
Behavior on implicitWidth {
|
||||
Anim {}
|
||||
}
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
acceptedButtons: Qt.NoButton
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
hoverEnabled: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,13 +43,6 @@ 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
|
||||
|
||||
@@ -59,7 +52,6 @@ Scope {
|
||||
name: "Bar-Exclusion"
|
||||
screen: root.screen
|
||||
|
||||
mask: Region {
|
||||
}
|
||||
mask: Region {}
|
||||
}
|
||||
}
|
||||
|
||||
+9
-8
@@ -3,8 +3,8 @@ import QtQuick
|
||||
import qs.Components
|
||||
import qs.Modules.Bar.Popouts as Popouts
|
||||
import qs.Modules.Notifications as Notifications
|
||||
import qs.Modules.Notifications.Sidebar as Sidebar
|
||||
import qs.Modules.Notifications.Sidebar.Utils as Utils
|
||||
import qs.Modules.Sidebar as Sidebar
|
||||
import qs.Modules.Sidebar.Utils as Utils
|
||||
import qs.Modules.Dashboard as Dashboard
|
||||
import qs.Modules.Osd as Osd
|
||||
import qs.Components.Toast as Toasts
|
||||
@@ -19,15 +19,11 @@ 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
|
||||
@@ -36,14 +32,19 @@ 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)
|
||||
|
||||
@@ -246,6 +246,7 @@ CustomWindow {
|
||||
PanelBg {
|
||||
id: osdBg
|
||||
|
||||
atRestOverride: panels.osd.offsetScale < 0.99
|
||||
deformAmount: 0.1
|
||||
implicitHeight: panels.osd.height
|
||||
implicitWidth: panels.osd.width
|
||||
@@ -257,6 +258,7 @@ CustomWindow {
|
||||
PanelBg {
|
||||
id: notifsBg
|
||||
|
||||
atRestOverride: panel.implicitHeight > 0
|
||||
panel: panels.notifications
|
||||
radius: Tokens.rounding.large
|
||||
}
|
||||
@@ -293,6 +295,7 @@ CustomWindow {
|
||||
|
||||
property real extraExtent: 0.2
|
||||
|
||||
atRestOverride: panels.resources.offsetScale < 0.99
|
||||
deformAmount: 0.05
|
||||
implicitHeight: panels.resources.height * (1 + extraExtent)
|
||||
implicitWidth: panels.resources.width * (1 + extraExtent)
|
||||
@@ -446,6 +449,7 @@ CustomWindow {
|
||||
property real deformAmount: 0.15
|
||||
required property Item panel
|
||||
|
||||
atRestOverride: panel?.offsetScale < 0.99 ?? false
|
||||
damping: 28.3
|
||||
deformScale: (deformAmount * Config.appearance.deform.scale) / 10000
|
||||
group: blobGroup
|
||||
|
||||
+12
-28
@@ -27,27 +27,21 @@ ColumnLayout {
|
||||
CustomText {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
color: Colors.palette.m3secondary
|
||||
font.bold: true
|
||||
font.family: Config.appearance.font.family.clock
|
||||
font.pointSize: Math.floor(Tokens.font.size.extraLarge * 3 * root.centerScale)
|
||||
font: Tokens.font.headline.builders.large.scale(3 * root.centerScale).weight(Font.Bold).width(30).build()
|
||||
text: Time.hourStr
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
color: Colors.palette.m3primary
|
||||
font.bold: true
|
||||
font.family: Config.appearance.font.family.clock
|
||||
font.pointSize: Math.floor(Tokens.font.size.extraLarge * 3 * root.centerScale)
|
||||
font: Tokens.font.headline.builders.large.scale(3 * root.centerScale).weight(Font.Bold).width(30).build()
|
||||
text: ":"
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
color: Colors.palette.m3secondary
|
||||
font.bold: true
|
||||
font.family: Config.appearance.font.family.clock
|
||||
font.pointSize: Math.floor(Tokens.font.size.extraLarge * 3 * root.centerScale)
|
||||
font: Tokens.font.headline.builders.large.scale(3 * root.centerScale).weight(Font.Bold).width(30).build()
|
||||
text: Time.minuteStr
|
||||
}
|
||||
}
|
||||
@@ -56,13 +50,11 @@ ColumnLayout {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.topMargin: -Tokens.padding.large * 2
|
||||
color: Colors.palette.m3tertiary
|
||||
font.bold: true
|
||||
font.family: Config.appearance.font.family.mono
|
||||
font.pointSize: Math.floor(Tokens.font.size.extraLarge * root.centerScale)
|
||||
font: Tokens.font.title.builders.medium.weight(Font.DemiBold).build()
|
||||
text: Time.format("dddd, d MMMM yyyy")
|
||||
}
|
||||
|
||||
CustomClippingRect {
|
||||
ClipRect {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.topMargin: Tokens.spacing.largeIncreased * 2
|
||||
color: Colors.tPalette.m3surfaceContainer
|
||||
@@ -73,7 +65,7 @@ ColumnLayout {
|
||||
MaterialIcon {
|
||||
anchors.centerIn: parent
|
||||
color: Colors.palette.m3onSurfaceVariant
|
||||
font.pointSize: Math.floor(root.centerWidth / 4)
|
||||
fontStyle: Tokens.font.icon.size(root.centerWidth / 4).build()
|
||||
text: "person"
|
||||
}
|
||||
|
||||
@@ -88,9 +80,7 @@ ColumnLayout {
|
||||
CustomText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
color: Colors.palette.m3onSurfaceVariant
|
||||
font.family: Config.appearance.font.family.mono
|
||||
font.pointSize: Tokens.font.size.normal
|
||||
font.weight: 600
|
||||
font: Tokens.font.body.large
|
||||
text: root.greeter.username
|
||||
visible: text.length > 0
|
||||
}
|
||||
@@ -155,8 +145,7 @@ ColumnLayout {
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,7 +180,6 @@ 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"
|
||||
}
|
||||
}
|
||||
@@ -204,8 +192,7 @@ ColumnLayout {
|
||||
implicitHeight: Math.max(message.implicitHeight, stateMessage.implicitHeight)
|
||||
|
||||
Behavior on implicitHeight {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
|
||||
CustomText {
|
||||
@@ -245,12 +232,10 @@ ColumnLayout {
|
||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
Behavior on scale {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
|
||||
onMsgChanged: {
|
||||
@@ -292,8 +277,7 @@ ColumnLayout {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
color: isError ? Colors.palette.m3error : Colors.palette.m3primary
|
||||
font.family: Config.appearance.font.family.mono
|
||||
font.pointSize: Tokens.font.size.small
|
||||
font: Tokens.font.mono.medium
|
||||
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.size.normal * 3
|
||||
property real implicitSize: Tokens.font.body.medium.pointSize * 3
|
||||
property real internalStrokeWidth: strokeWidth
|
||||
readonly property alias progress: manager.progress
|
||||
property real strokeWidth: Tokens.padding.extraSmall * 0.8
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
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,13 +1,36 @@
|
||||
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,13 +1,36 @@
|
||||
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,6 +4,7 @@ MouseArea {
|
||||
property int scrollAccumulatedY: 0
|
||||
|
||||
function onWheel(event: WheelEvent): void {
|
||||
event.accepted = false;
|
||||
}
|
||||
|
||||
onWheel: event => {
|
||||
|
||||
@@ -6,7 +6,6 @@ Rectangle {
|
||||
color: "transparent"
|
||||
|
||||
Behavior on color {
|
||||
CAnim {
|
||||
}
|
||||
CAnim {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,10 +14,9 @@ Text {
|
||||
property real animateTo: 1
|
||||
|
||||
color: Colors.palette.m3onSurface
|
||||
font.family: Config.appearance.font.family.sans
|
||||
font.pointSize: Tokens.font.size.normal
|
||||
font: Tokens.font.body.small
|
||||
linkColor: Colors.palette.m3onPrimaryFixedVariant
|
||||
renderType: Text.NativeRendering
|
||||
renderType: Text.QtRendering
|
||||
textFormat: Text.PlainText
|
||||
|
||||
Behavior on color {
|
||||
@@ -28,22 +27,25 @@ Text {
|
||||
enabled: root.animate
|
||||
|
||||
SequentialAnimation {
|
||||
Anim {
|
||||
TAnim {
|
||||
target: root
|
||||
to: root.animateFrom
|
||||
type: Anim.FastEffects
|
||||
}
|
||||
|
||||
PropertyAction {
|
||||
}
|
||||
|
||||
Anim {
|
||||
TAnim {
|
||||
target: root
|
||||
to: root.animateTo
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component Anim: NumberAnimation {
|
||||
component TAnim: Anim {
|
||||
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,76 +1,305 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Shapes
|
||||
import ZShell.Config
|
||||
import qs.Services
|
||||
|
||||
TextField {
|
||||
TextFieldBase {
|
||||
id: root
|
||||
|
||||
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
|
||||
enum TextFieldType {
|
||||
Outlined,
|
||||
Filled
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
CAnim {
|
||||
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
|
||||
}
|
||||
}
|
||||
cursorDelegate: CustomRect {
|
||||
id: cursor
|
||||
|
||||
property bool disableBlink
|
||||
onEditingFinished: {
|
||||
if (!valid)
|
||||
isError = true;
|
||||
}
|
||||
onPressed: {
|
||||
if (stateLayer.enabled)
|
||||
stateLayer.press(stateLayer.mouseX, stateLayer.mouseY);
|
||||
}
|
||||
onTextEdited: {
|
||||
if (isError)
|
||||
isError = false;
|
||||
}
|
||||
|
||||
color: Colors.palette.m3primary
|
||||
implicitWidth: 2
|
||||
radius: Tokens.rounding.medium
|
||||
Item {
|
||||
id: contentWrapper
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
duration: Tokens.anim.durations.small
|
||||
}
|
||||
}
|
||||
anchors.bottomMargin: root.supportingTextOffset
|
||||
anchors.fill: parent
|
||||
|
||||
Connections {
|
||||
function onCursorPositionChanged(): void {
|
||||
if (root.activeFocus && root.cursorVisible) {
|
||||
cursor.opacity = 1;
|
||||
cursor.disableBlink = true;
|
||||
enableBlink.restart();
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
target: root
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: enableBlink
|
||||
Loader {
|
||||
id: leadingIconLoader
|
||||
|
||||
interval: 100
|
||||
active: root.leadingIcon
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Tokens.padding.small
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
onTriggered: cursor.disableBlink = false
|
||||
sourceComponent: MaterialIcon {
|
||||
color: Colors.palette.m3onSurfaceVariant
|
||||
text: root.leadingIcon
|
||||
fontStyle: Tokens.font.icon.builders.medium.scale(0.9).build()
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 500
|
||||
repeat: true
|
||||
running: root.activeFocus && root.cursorVisible && !cursor.disableBlink
|
||||
triggeredOnStart: true
|
||||
Loader {
|
||||
id: trailingIconLoader
|
||||
|
||||
onTriggered: parent.opacity = parent.opacity === 1 ? 0 : 1
|
||||
}
|
||||
active: root.trailingIcon
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Tokens.padding.small
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Binding {
|
||||
cursor.opacity: 0
|
||||
when: !root.activeFocus || !root.cursorVisible
|
||||
sourceComponent: MaterialIcon {
|
||||
color: Colors.palette.m3onSurfaceVariant
|
||||
text: root.trailingIcon
|
||||
fontStyle: Tokens.font.icon.builders.medium.scale(0.9).build()
|
||||
}
|
||||
}
|
||||
}
|
||||
Behavior on placeholderTextColor {
|
||||
CAnim {
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import ZShell.Config
|
||||
import qs.Services
|
||||
|
||||
TextInput {
|
||||
renderType: Text.NativeRendering
|
||||
selectedTextColor: Colors.palette.m3onSecondaryContainer
|
||||
selectionColor: Colors.tPalette.m3onSecondaryContainer
|
||||
selectionColor: Colors.tPalette.colSecondaryContainer
|
||||
|
||||
font {
|
||||
family: Config.appearance?.font.family.sans ?? "sans-serif"
|
||||
hintingPreference: Font.PreferFullHinting
|
||||
pixelSize: Tokens?.font.size.normal ?? 15
|
||||
pixelSize: Config.appearance?.font.size.normal ?? 15
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
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()
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
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,16 +1,14 @@
|
||||
import QtQuick
|
||||
import ZShell.Config
|
||||
import qs.Services
|
||||
|
||||
CustomText {
|
||||
property real fill
|
||||
property int grade: Colors.light ? 0 : -25
|
||||
id: root
|
||||
|
||||
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
|
||||
})
|
||||
property real fill: 0
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import QtQuick
|
||||
import QtQuick.Shapes
|
||||
import ZShell
|
||||
import ZShell.Components
|
||||
import qs.Helpers
|
||||
import ZShell.Config
|
||||
import qs.Services
|
||||
|
||||
@@ -13,7 +11,6 @@ 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);
|
||||
@@ -22,6 +19,7 @@ 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
|
||||
@@ -29,7 +27,7 @@ MouseArea {
|
||||
readonly property alias rect: base
|
||||
property bool shapeMorph
|
||||
property bool showHoverBackground: true
|
||||
property real stateOpacity: containsMouse ? 0.08 : 0
|
||||
property real stateOpacity: containsMouse || manualHoverOverride ? 0.08 : 0
|
||||
property alias topLeftRadius: base.topLeftRadius
|
||||
property alias topRightRadius: base.topRightRadius
|
||||
|
||||
@@ -52,8 +50,8 @@ MouseArea {
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
cursorShape: disabled ? undefined : Qt.PointingHandCursor
|
||||
enabled: !disabled
|
||||
cursorShape: !enabled ? undefined : Qt.PointingHandCursor
|
||||
enabled: parent.enabled
|
||||
hoverEnabled: true
|
||||
|
||||
Behavior on stateOpacity {
|
||||
@@ -100,13 +98,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
|
||||
|
||||
+16
-39
@@ -13,34 +13,16 @@ RowLayout {
|
||||
|
||||
spacing: Tokens.spacing.largeIncreased * 2
|
||||
|
||||
ColumnLayout {
|
||||
CustomRect {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
spacing: Tokens.spacing.medium
|
||||
topLeftRadius: Tokens.rounding.largeIncreased
|
||||
bottomLeftRadius: Tokens.rounding.largeIncreased
|
||||
color: Colors.tPalette.m3surfaceContainer
|
||||
radius: Tokens.rounding.small
|
||||
|
||||
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
|
||||
}
|
||||
UserDock {
|
||||
greeter: root.greeter
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,21 +31,16 @@ RowLayout {
|
||||
screenHeight: root.screenHeight
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
CustomRect {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
spacing: Tokens.spacing.medium
|
||||
bottomRightRadius: Tokens.rounding.largeIncreased
|
||||
color: Colors.tPalette.m3surfaceContainer
|
||||
radius: Tokens.rounding.small
|
||||
topRightRadius: Tokens.rounding.largeIncreased
|
||||
|
||||
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
|
||||
}
|
||||
SessionDock {
|
||||
greeter: root.greeter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,8 +134,7 @@ Scope {
|
||||
id: lockIcon
|
||||
|
||||
anchors.centerIn: parent
|
||||
font.bold: true
|
||||
font.pointSize: Tokens.font.size.extraLarge * 4
|
||||
fontStyle: Tokens.font.icon.builders.extraLarge.size(Tokens.font.icon.extraLarge.pointSize * 3).build()
|
||||
text: "lock"
|
||||
}
|
||||
|
||||
|
||||
+8
-15
@@ -39,8 +39,7 @@ Item {
|
||||
anchors.centerIn: parent
|
||||
animate: true
|
||||
color: root.greeter.launching ? Colors.palette.m3secondary : Colors.palette.m3outline
|
||||
font.family: Config.appearance.font.family.mono
|
||||
font.pointSize: Tokens.font.size.normal
|
||||
font: Tokens.font.mono.medium
|
||||
opacity: root.buffer ? 0 : 1
|
||||
text: {
|
||||
if (root.greeter.launching)
|
||||
@@ -51,8 +50,7 @@ Item {
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,15 +62,13 @@ Item {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: Colors.palette.m3onSurface
|
||||
elide: Text.ElideLeft
|
||||
font.family: Config.appearance.font.family.mono
|
||||
font.pointSize: Tokens.font.size.normal
|
||||
font: Tokens.font.mono.medium
|
||||
horizontalAlignment: Qt.AlignHCenter
|
||||
opacity: root.greeter.echoResponse && root.buffer ? 1 : 0
|
||||
text: root.buffer
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +85,7 @@ Item {
|
||||
|
||||
anchors.centerIn: parent
|
||||
anchors.horizontalCenterOffset: implicitWidth > root.width ? -(implicitWidth - root.width) / 2 : 0
|
||||
implicitHeight: Tokens.font.size.normal
|
||||
implicitHeight: Tokens.font.body.medium
|
||||
implicitWidth: fullWidth
|
||||
interactive: false
|
||||
opacity: root.greeter.echoResponse ? 0 : 1
|
||||
@@ -107,8 +103,7 @@ Item {
|
||||
scale: 0
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
Behavior on scale {
|
||||
Anim {
|
||||
@@ -156,15 +151,13 @@ Item {
|
||||
Behavior on implicitWidth {
|
||||
id: imWidthBehavior
|
||||
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
model: ScriptModel {
|
||||
values: root.buffer.split("")
|
||||
}
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+11
-17
@@ -13,22 +13,19 @@ ColumnLayout {
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Tokens.padding.large
|
||||
spacing: Tokens.spacing.small
|
||||
spacing: Tokens.spacing.large
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
color: Colors.palette.m3outline
|
||||
elide: Text.ElideRight
|
||||
font.family: Config.appearance.font.family.mono
|
||||
font.weight: 500
|
||||
font: Tokens.font.mono.builders.small.weight(Font.Medium).build()
|
||||
text: root.greeter.sessions.length > 0 ? qsTr("%1 session%2").arg(root.greeter.sessions.length).arg(root.greeter.sessions.length === 1 ? "" : "s") : qsTr("Sessions")
|
||||
}
|
||||
|
||||
CustomClippingRect {
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
color: "transparent"
|
||||
radius: Tokens.rounding.small
|
||||
|
||||
Loader {
|
||||
active: opacity > 0
|
||||
@@ -46,16 +43,14 @@ ColumnLayout {
|
||||
MaterialIcon {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
color: Colors.palette.m3outlineVariant
|
||||
font.pointSize: Tokens.font.size.extraLarge * 2
|
||||
fontStyle: Tokens.font.icon.large
|
||||
text: "desktop_windows"
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
color: Colors.palette.m3outlineVariant
|
||||
font.family: Config.appearance.font.family.mono
|
||||
font.pointSize: Tokens.font.size.large
|
||||
font.weight: 500
|
||||
font: Tokens.font.headline.builders.small.width(90).build()
|
||||
text: qsTr("No Sessions Found")
|
||||
}
|
||||
}
|
||||
@@ -68,6 +63,7 @@ ColumnLayout {
|
||||
clip: true
|
||||
currentIndex: root.greeter.sessionIndex
|
||||
highlightFollowsCurrentItem: false
|
||||
boundsBehavior: ListView.StopAtBounds
|
||||
model: root.greeter.sessions
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
@@ -80,7 +76,7 @@ ColumnLayout {
|
||||
anchors.left: parent?.left
|
||||
anchors.right: parent?.right
|
||||
implicitHeight: row.implicitHeight + Tokens.padding.small * 2
|
||||
radius: Tokens.rounding.medium - Tokens.padding.small
|
||||
radius: Tokens.rounding.large
|
||||
|
||||
StateLayer {
|
||||
onClicked: {
|
||||
@@ -97,7 +93,7 @@ ColumnLayout {
|
||||
|
||||
MaterialIcon {
|
||||
color: session.index === sessions.currentIndex ? Colors.palette.m3onPrimary : Colors.palette.m3onSurfaceVariant
|
||||
font.pointSize: Tokens.font.size.extraLarge
|
||||
fontStyle: Tokens.font.icon.extraLarge
|
||||
text: modelData.kind === "x11" ? "tv" : "desktop_windows"
|
||||
}
|
||||
|
||||
@@ -109,8 +105,7 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
color: session.index === sessions.currentIndex ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
|
||||
elide: Text.ElideRight
|
||||
font.pointSize: Tokens.font.size.normal
|
||||
font.weight: 600
|
||||
font: Tokens.font.body.builders.medium.weight(Font.Bold).build()
|
||||
text: modelData.name
|
||||
}
|
||||
|
||||
@@ -118,8 +113,7 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
color: session.index === sessions.currentIndex ? Colors.palette.m3onPrimaryFixedVariant : Colors.palette.m3onSurfaceVariant
|
||||
elide: Text.ElideRight
|
||||
font.family: Config.appearance.font.family.mono
|
||||
font.pointSize: Tokens.font.size.small
|
||||
font: Tokens.font.mono.small
|
||||
text: modelData.kind
|
||||
}
|
||||
}
|
||||
@@ -129,7 +123,7 @@ ColumnLayout {
|
||||
color: Colors.palette.m3primary
|
||||
implicitHeight: sessions.currentItem?.implicitHeight ?? 0
|
||||
implicitWidth: sessions.width
|
||||
radius: Tokens.rounding.medium - Tokens.padding.small
|
||||
radius: Tokens.rounding.large
|
||||
y: sessions.currentItem?.y ?? 0
|
||||
|
||||
Behavior on y {
|
||||
|
||||
+11
-16
@@ -14,22 +14,19 @@ ColumnLayout {
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Tokens.padding.large
|
||||
spacing: Tokens.spacing.small
|
||||
spacing: Tokens.spacing.large
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
color: Colors.palette.m3outline
|
||||
elide: Text.ElideRight
|
||||
font.family: Config.appearance.font.family.mono
|
||||
font.weight: 500
|
||||
font: Tokens.font.mono.builders.small.weight(Font.Medium).build()
|
||||
text: root.greeter.users.length > 0 ? qsTr("%1 user%2").arg(root.greeter.users.length).arg(root.greeter.users.length === 1 ? "" : "s") : qsTr("Users")
|
||||
}
|
||||
|
||||
CustomClippingRect {
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
color: "transparent"
|
||||
radius: Tokens.rounding.small
|
||||
|
||||
Loader {
|
||||
active: opacity > 0
|
||||
@@ -48,16 +45,14 @@ ColumnLayout {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
color: Colors.palette.m3outlineVariant
|
||||
fill: 1
|
||||
font.pointSize: Tokens.font.size.extraLarge * 2
|
||||
fontStyle: Tokens.font.icon.large
|
||||
text: "account_circle"
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
color: Colors.palette.m3outlineVariant
|
||||
font.family: Config.appearance.font.family.mono
|
||||
font.pointSize: Tokens.font.size.large
|
||||
font.weight: 500
|
||||
font: Tokens.font.headline.builders.small.width(90).build()
|
||||
text: qsTr("No Users Found")
|
||||
}
|
||||
}
|
||||
@@ -70,6 +65,7 @@ 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
|
||||
|
||||
@@ -82,7 +78,7 @@ ColumnLayout {
|
||||
anchors.left: parent?.left
|
||||
anchors.right: parent?.right
|
||||
implicitHeight: row.implicitHeight + Tokens.padding.small * 2
|
||||
radius: Tokens.rounding.medium - Tokens.padding.small
|
||||
radius: Tokens.rounding.large
|
||||
|
||||
StateLayer {
|
||||
onClicked: {
|
||||
@@ -97,7 +93,7 @@ ColumnLayout {
|
||||
anchors.margins: Tokens.padding.small
|
||||
spacing: Tokens.spacing.medium
|
||||
|
||||
CustomClippingRect {
|
||||
ClipRect {
|
||||
Layout.preferredHeight: 50
|
||||
Layout.preferredWidth: 50
|
||||
radius: Tokens.rounding.full
|
||||
@@ -106,7 +102,7 @@ ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
color: user.index === users.currentIndex ? Colors.palette.m3onPrimary : Colors.palette.m3onSurfaceVariant
|
||||
fill: 1
|
||||
font.pointSize: Tokens.font.size.extraLarge
|
||||
fontStyle: Tokens.font.icon.extraLarge
|
||||
text: modelData.kind === "x11" ? "tv" : "account_circle"
|
||||
}
|
||||
|
||||
@@ -124,8 +120,7 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
color: user.index === users.currentIndex ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
|
||||
elide: Text.ElideRight
|
||||
font.pointSize: Tokens.font.size.normal
|
||||
font.weight: 600
|
||||
font: Tokens.font.body.builders.medium.weight(Font.Bold).build()
|
||||
text: modelData.username
|
||||
}
|
||||
}
|
||||
@@ -135,7 +130,7 @@ ColumnLayout {
|
||||
color: Colors.palette.m3primary
|
||||
implicitHeight: users.currentItem?.implicitHeight ?? 0
|
||||
implicitWidth: users.width
|
||||
radius: Tokens.rounding.medium - Tokens.padding.small
|
||||
radius: Tokens.rounding.large
|
||||
y: users.currentItem?.y ?? 0
|
||||
|
||||
Behavior on y {
|
||||
|
||||
@@ -1,171 +0,0 @@
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ Singleton {
|
||||
|
||||
function toggleNightLight(): void {
|
||||
service.manualToggle = true;
|
||||
service.toggle();
|
||||
service.toggle(true);
|
||||
}
|
||||
|
||||
NightlightManager {
|
||||
|
||||
+105
-43
@@ -5,7 +5,7 @@ import Quickshell
|
||||
import Quickshell.Io
|
||||
import QtQuick
|
||||
import ZShell.Models
|
||||
import ZShell.Internal
|
||||
import ZShell.Wallpaper
|
||||
import ZShell.Config
|
||||
import qs.Components
|
||||
import qs.Services
|
||||
@@ -16,24 +16,48 @@ Searcher {
|
||||
id: root
|
||||
|
||||
property string actualCurrent: WallpaperPath.currentWallpaperPath
|
||||
property alias crops: adapter.monitorCrops
|
||||
readonly property string current: showPreview ? previewPath : actualCurrent
|
||||
property alias monitorCrops: monitorCrops
|
||||
readonly property string currentThumbnail: thumbnailFor(current)
|
||||
readonly property bool currentIsVideo: isVideoPath(current)
|
||||
property bool pendingPreviewClear
|
||||
property bool previewColorLock
|
||||
property string previewPath
|
||||
property bool recentlyChanged
|
||||
property bool showPreview: false
|
||||
property int cropVersion: 0
|
||||
|
||||
function getCategoryFor(w: FileSystemEntry): string {
|
||||
function getCategoryFor(w: WallpaperEntry): string {
|
||||
let category = w.parentDir.slice(Paths.wallsdir.length + 1);
|
||||
if (category.includes("/"))
|
||||
category = category.slice(0, category.indexOf("/"));
|
||||
return category;
|
||||
}
|
||||
|
||||
readonly property var videoExts: ["mp4", "mkv", "webm", "avi", "mov", "wmv", "flv", "m4v", "mpg", "mpeg", "3gp", "ogv", "ts", "vob"]
|
||||
|
||||
function isVideoPath(path: string): bool {
|
||||
if (!path)
|
||||
return false;
|
||||
const dot = path.lastIndexOf(".");
|
||||
if (dot < 0)
|
||||
return false;
|
||||
return videoExts.includes(path.slice(dot + 1).toLowerCase());
|
||||
}
|
||||
|
||||
function thumbnailFor(path: string): string {
|
||||
if (!path)
|
||||
return "";
|
||||
|
||||
for (const entry of root.list) {
|
||||
if (entry.path === path)
|
||||
return entry.thumbnailPath || path;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
function getCrop(screen: string): var {
|
||||
return root.crops[screen];
|
||||
return wallpapers.crop(root.current, screen);
|
||||
}
|
||||
|
||||
function preview(path: string): void {
|
||||
@@ -50,19 +74,18 @@ Searcher {
|
||||
else if (zoom > 5.0)
|
||||
zoom = 5.0;
|
||||
|
||||
root.crops[screen] = {
|
||||
wallpapers.setCrop(root.current, screen, {
|
||||
x: rect.x,
|
||||
y: rect.y,
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
zoom: zoom
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function setWallpaper(path: string): void {
|
||||
actualCurrent = path;
|
||||
WallpaperPath.currentWallpaperPath = path;
|
||||
Quickshell.screens.forEach(n => setCrop(n.name, Qt.rect(0, 0, 1, 1), 1.0));
|
||||
Quickshell.execDetached(["zshell-cli", "wallpaper", "lockscreen", "--input-image", `${root.actualCurrent}`, "--output-path", `${Paths.state}/lockscreen_bg.png`, "--blur-amount", `${Config.lock.blurAmount}`]);
|
||||
if (Config.colors.schemeGen)
|
||||
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--image-path", `${root.actualCurrent}`, "--scheme", `${Config.colors.schemeType}`, "--mode", `${Config.colors.mode}`]);
|
||||
@@ -96,38 +119,13 @@ Searcher {
|
||||
target: "wallpaper"
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: monitorCrops
|
||||
|
||||
path: `${Paths.state}/wallpaper-crops.json`
|
||||
watchChanges: true
|
||||
|
||||
onAdapterUpdated: cropWriteDelay.restart()
|
||||
onFileChanged: reload()
|
||||
|
||||
JsonAdapter {
|
||||
id: adapter
|
||||
|
||||
property var monitorCrops: ({})
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: cropWriteDelay
|
||||
|
||||
interval: 100
|
||||
repeat: false
|
||||
running: false
|
||||
|
||||
onTriggered: monitorCrops.writeAdapter()
|
||||
}
|
||||
|
||||
FileSystemModel {
|
||||
WallpaperDb {
|
||||
id: wallpapers
|
||||
|
||||
filter: FileSystemModel.Images
|
||||
dbPath: `${Paths.state}/wallpapers.sqlite`
|
||||
cacheSize: wallpaper.largestBufferSize
|
||||
requireHardwareCodec: true
|
||||
path: Config.general.wallpaperPath
|
||||
recursive: true
|
||||
}
|
||||
|
||||
Process {
|
||||
@@ -146,36 +144,100 @@ Searcher {
|
||||
WallpaperService {
|
||||
id: wallpaper
|
||||
|
||||
dbPath: wallpapers.dbPath
|
||||
path: root.current
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: wallpapers
|
||||
|
||||
function onCacheStored(path: string): void {
|
||||
if (path === root.current)
|
||||
wallpaper.invalidateCache();
|
||||
}
|
||||
|
||||
function onCropsChanged(path: string): void {
|
||||
if (path === root.current)
|
||||
root.cropVersion++;
|
||||
}
|
||||
}
|
||||
|
||||
Variants {
|
||||
id: screenVariants
|
||||
model: Quickshell.screens
|
||||
|
||||
WallpaperObject {
|
||||
readonly property var crops: root.getCrop(modelData.name)
|
||||
id: obj
|
||||
required property var modelData
|
||||
|
||||
cropHeight: crops?.height ?? 1.0
|
||||
cropWidth: crops?.width ?? 1.0
|
||||
cropX: crops?.x ?? 0.0
|
||||
cropY: crops?.y ?? 0.0
|
||||
// Crop animation only for edits, never for wallpaper swaps.
|
||||
property bool animateCrop: false
|
||||
|
||||
screen: modelData.name
|
||||
service: wallpaper
|
||||
zoom: crops?.zoom ?? 1.0
|
||||
|
||||
// Apply the wallpaper's own crop. Driven off the service's pathChanged
|
||||
// (which fires after applyPath has snapshotted the outgoing frame with
|
||||
// the outgoing crop) and off crop edits, never as a reactive binding on
|
||||
// the wallpaper path — so a swap applies the incoming crop atomically
|
||||
// after the base is captured.
|
||||
function seedCrop(): void {
|
||||
// Load the current wallpaper's crop without pushing: the service
|
||||
// already read it from the database. This only seeds the values so
|
||||
// a later edit animates from the right starting point.
|
||||
obj.syncCrop(root.getCrop(modelData.name));
|
||||
}
|
||||
|
||||
// Apply an edited crop: assign via the setters so the Behaviors animate
|
||||
// the numbers and push each intermediate value to the service.
|
||||
function editCrop(): void {
|
||||
const c = root.getCrop(modelData.name);
|
||||
obj.cropX = c?.x ?? 0.0;
|
||||
obj.cropY = c?.y ?? 0.0;
|
||||
obj.cropWidth = c?.width ?? 1.0;
|
||||
obj.cropHeight = c?.height ?? 1.0;
|
||||
obj.zoom = c?.zoom ?? 1.0;
|
||||
}
|
||||
|
||||
Component.onCompleted: seedCrop()
|
||||
onScreenChanged: seedCrop()
|
||||
|
||||
Behavior on cropHeight {
|
||||
enabled: obj.animateCrop
|
||||
Anim {}
|
||||
}
|
||||
Behavior on cropWidth {
|
||||
enabled: obj.animateCrop
|
||||
Anim {}
|
||||
}
|
||||
Behavior on cropX {
|
||||
enabled: obj.animateCrop
|
||||
Anim {}
|
||||
}
|
||||
Behavior on cropY {
|
||||
enabled: obj.animateCrop
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: wallpaper
|
||||
function onPathChanged(): void {
|
||||
for (const obj of screenVariants.instances) {
|
||||
obj.animateCrop = false;
|
||||
obj.seedCrop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
function onCropVersionChanged(): void {
|
||||
for (const obj of screenVariants.instances) {
|
||||
obj.animateCrop = true;
|
||||
obj.editCrop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -17,12 +17,12 @@ GridLayout {
|
||||
required property Wrapper popouts
|
||||
required property ClipWrapper popoutsWrapper
|
||||
required property ShellScreen screen
|
||||
readonly property int vPadding: 6
|
||||
required property ScreenState screenState
|
||||
readonly property int vPadding: 6
|
||||
|
||||
function axisCenterOf(item: Item): real {
|
||||
const c = item.mapToItem(root, item.implicitWidth / 2, item.implicitHeight / 2);
|
||||
return horizontal ? c.x : c.y;
|
||||
return horizontal ? Math.floor(c.x) : Math.floor(c.y);
|
||||
}
|
||||
|
||||
function checkPopout(pos: real): void {
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,9 @@ WidgetBase {
|
||||
}
|
||||
required property GridLayout loader
|
||||
required property Wrapper popouts
|
||||
readonly property real size: horizontal ? timeText.contentWidth + Tokens.padding.medium * 2 : verticalColumn.implicitHeight + Tokens.padding.medium * 2
|
||||
readonly property int roundedWidth: Math.round(timeText.implicitWidth)
|
||||
readonly property int evenWidth: roundedWidth % 2 === 0 ? roundedWidth : roundedWidth + 1
|
||||
readonly property real size: horizontal ? evenWidth + Tokens.padding.medium * 2 : verticalColumn.implicitHeight + Tokens.padding.medium * 2
|
||||
required property ScreenState screenState
|
||||
|
||||
implicitHeight: horizontal ? baseSize : size
|
||||
|
||||
@@ -24,7 +24,7 @@ Row {
|
||||
color: Battery.getColors(root.percentage, root.devState).bg
|
||||
height: root.batHeight
|
||||
radius: root.radius
|
||||
width: root.batWidth
|
||||
width: root.batWidth - 1
|
||||
|
||||
CustomText {
|
||||
color: Battery.getColors(root.percentage, root.devState).fg
|
||||
|
||||
@@ -13,7 +13,7 @@ WidgetBase {
|
||||
readonly property int textWidth: Math.min(metrics.width, 200)
|
||||
|
||||
implicitHeight: horizontal ? baseSize : layout.implicitHeight + Tokens.padding.small * 2
|
||||
implicitWidth: horizontal ? layout.implicitWidth + Tokens.padding.small * 2 : baseSize
|
||||
implicitWidth: horizontal ? (layout.implicitWidth % 2 === 0 ? layout.implicitWidth : layout.implicitWidth + 1) + Tokens.padding.small * 2 : baseSize
|
||||
radius: Tokens.rounding.full
|
||||
|
||||
Behavior on implicitHeight {
|
||||
|
||||
@@ -12,7 +12,6 @@ GridLayout {
|
||||
property real animatedPercentage: 0
|
||||
readonly property real arcStartAngle: 0.75 * Math.PI
|
||||
readonly property real arcSweep: 1.5 * Math.PI
|
||||
readonly property real dpr: (QsWindow.window as QsWindow)?.devicePixelRatio ?? 1
|
||||
required property bool horizontal
|
||||
property string icon
|
||||
required property color iconColor
|
||||
@@ -23,17 +22,12 @@ GridLayout {
|
||||
property bool warning: percentage * 100 >= warningThreshold
|
||||
property int warningThreshold: 80
|
||||
|
||||
function snap(v) {
|
||||
return Math.round(v * dpr) / dpr;
|
||||
}
|
||||
|
||||
columnSpacing: Tokens.spacing.medium
|
||||
columns: horizontal ? -1 : 1
|
||||
percentage: 0
|
||||
|
||||
Behavior on animatedPercentage {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
|
||||
Component.onCompleted: animatedPercentage = percentage
|
||||
@@ -59,7 +53,7 @@ GridLayout {
|
||||
Layout.preferredHeight: root.horizontal ? icon.implicitHeight - Tokens.padding.extraSmall : 4
|
||||
Layout.preferredWidth: root.horizontal ? 4 : icon.implicitWidth
|
||||
color: Colors.layer(Colors.palette.m3surfaceContainerHigh, 2)
|
||||
radius: Math.min(width, height) / 2
|
||||
radius: Tokens.rounding.full
|
||||
|
||||
Item {
|
||||
id: clipper
|
||||
@@ -69,18 +63,20 @@ GridLayout {
|
||||
anchors.left: root.horizontal ? undefined : parent.left
|
||||
anchors.verticalCenter: root.horizontal ? undefined : parent.verticalCenter
|
||||
clip: true
|
||||
height: root.horizontal ? root.snap(root.percentage * track.height) : track.height * 2
|
||||
width: root.horizontal ? track.width * 2 : root.snap(root.percentage * track.width)
|
||||
implicitHeight: root.horizontal ? root.percentage * track.height : track.height * 2
|
||||
implicitWidth: root.horizontal ? track.width * 2 : root.percentage * track.width
|
||||
|
||||
CustomRect {
|
||||
anchors.bottom: root.horizontal ? parent.bottom : undefined
|
||||
anchors.horizontalCenter: root.horizontal ? parent.horizontalCenter : undefined
|
||||
anchors.left: root.horizontal ? undefined : parent.left
|
||||
anchors.verticalCenter: root.horizontal ? undefined : parent.verticalCenter
|
||||
|
||||
color: root.mainColor
|
||||
height: track.height
|
||||
radius: track.radius
|
||||
width: track.width
|
||||
|
||||
implicitHeight: track.height
|
||||
implicitWidth: track.width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ WidgetBase {
|
||||
anchors.horizontalCenterOffset: root.horizontal ? 0 : 1
|
||||
anchors.verticalCenterOffset: root.horizontal ? 0 : -3
|
||||
columnSpacing: Tokens.spacing.small
|
||||
rowSpacing: 0
|
||||
columns: root.horizontal ? -1 : 1
|
||||
|
||||
ServiceRef {
|
||||
@@ -51,6 +52,7 @@ WidgetBase {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.fillHeight: root.horizontal
|
||||
Layout.fillWidth: !root.horizontal
|
||||
|
||||
horizontal: root.horizontal
|
||||
icon: "memory"
|
||||
iconColor: root.screenState.resources ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
|
||||
|
||||
@@ -31,7 +31,7 @@ WidgetBase {
|
||||
return -1;
|
||||
}
|
||||
readonly property real size: horizontal ? grid.implicitWidth + grid.anchors.leftMargin + grid.anchors.rightMargin : grid.implicitHeight + grid.anchors.bottomMargin + grid.anchors.topMargin
|
||||
readonly property int spacing: Tokens.spacing.medium / 2
|
||||
readonly property int spacing: Tokens.spacing.extraSmall
|
||||
|
||||
// Entries that can shrink to nothing, spacing included
|
||||
function collapsed(entry: var): bool {
|
||||
@@ -51,14 +51,12 @@ WidgetBase {
|
||||
Behavior on implicitHeight {
|
||||
enabled: !root.horizontal
|
||||
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
Behavior on implicitWidth {
|
||||
enabled: root.horizontal
|
||||
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
@@ -70,6 +68,8 @@ WidgetBase {
|
||||
anchors.rightMargin: root.horizontal ? Tokens.padding.extraSmall : 0
|
||||
anchors.topMargin: root.horizontal ? 0 : Tokens.padding.extraSmall
|
||||
columns: root.horizontal ? -1 : 1
|
||||
columnSpacing: root.spacing
|
||||
rowSpacing: root.spacing
|
||||
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
@@ -111,8 +111,7 @@ WidgetBase {
|
||||
delegate: EntryWrapper {
|
||||
name: "upower"
|
||||
|
||||
UPowerWidget {
|
||||
}
|
||||
UPowerWidget {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -135,8 +134,8 @@ WidgetBase {
|
||||
Layout.rightMargin: root.horizontal ? Math.round(rightGap) : 0
|
||||
Layout.topMargin: root.horizontal ? 0 : Math.round(leftGap)
|
||||
children: item
|
||||
implicitHeight: item?.implicitHeight ?? 0
|
||||
implicitWidth: item?.implicitWidth ?? 0
|
||||
implicitHeight: item?.height ?? item?.implicitHeight ?? 0
|
||||
implicitWidth: item?.width ?? item?.implicitWidth ?? 0
|
||||
|
||||
Behavior on leftGap {
|
||||
Anim {
|
||||
|
||||
@@ -6,7 +6,7 @@ import qs.Components
|
||||
import qs.Services
|
||||
|
||||
MaterialIcon {
|
||||
id: speaker
|
||||
id: root
|
||||
|
||||
required property bool horizontal
|
||||
|
||||
@@ -15,6 +15,8 @@ MaterialIcon {
|
||||
fill: 1
|
||||
fontStyle: Tokens.font.icon.small
|
||||
text: Audio.muted ? "volume_off" : "volume_up"
|
||||
width: Math.round(implicitWidth)
|
||||
height: Math.round(implicitHeight)
|
||||
|
||||
Behavior on Layout.maximumWidth {
|
||||
Anim {}
|
||||
|
||||
@@ -6,7 +6,7 @@ import qs.Components
|
||||
import qs.Services
|
||||
|
||||
MaterialIcon {
|
||||
id: mic
|
||||
id: root
|
||||
|
||||
required property bool horizontal
|
||||
|
||||
@@ -15,6 +15,8 @@ MaterialIcon {
|
||||
fill: 1
|
||||
fontStyle: Tokens.font.icon.small
|
||||
text: Audio.sourceMuted ? "mic_off" : "mic"
|
||||
width: Math.round(implicitWidth)
|
||||
height: Math.round(implicitHeight)
|
||||
|
||||
Behavior on Layout.maximumWidth {
|
||||
Anim {}
|
||||
|
||||
@@ -19,7 +19,7 @@ Item {
|
||||
required property Wrapper popouts
|
||||
|
||||
function resolveIcon(app: string, icon: string): string {
|
||||
if (app === "chrome_status_icon_1") {
|
||||
if (app === "discord_status_icon_1" && Config.bar.tray.replaceIcons) {
|
||||
return Quickshell.iconPath("discord-tray");
|
||||
} else if (app === "AyuGramDesktop") {
|
||||
if (icon === Quickshell.iconPath("com.ayugram.desktop-attention-symbolic"))
|
||||
@@ -50,6 +50,8 @@ Item {
|
||||
radius: Tokens.rounding.full
|
||||
|
||||
StateLayer {
|
||||
id: layer
|
||||
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
anchors.fill: parent
|
||||
color: icon.layer.enabled && root.current ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
|
||||
@@ -77,6 +79,7 @@ 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
|
||||
|
||||
@@ -38,6 +38,7 @@ WidgetBase {
|
||||
anchors.topMargin: root.horizontal ? 0 : Tokens.padding.extraSmall
|
||||
columns: root.horizontal ? -1 : 1
|
||||
rowSpacing: -2
|
||||
columnSpacing: Tokens.spacing.extraSmall
|
||||
|
||||
Repeater {
|
||||
id: repeater
|
||||
|
||||
@@ -12,13 +12,14 @@ import qs.Services
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property real activeWorkspaceMargin: Math.ceil(Tokens.padding.medium / 4)
|
||||
property real activeWorkspaceMargin: Tokens.padding.extraSmall / 2
|
||||
readonly property int spacing: Tokens.spacing.extraSmall / 2
|
||||
readonly property int effectiveActiveWorkspaceId: monitor?.activeWorkspace?.id ?? 1
|
||||
required property bool horizontal
|
||||
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.screen)
|
||||
required property ShellScreen screen
|
||||
readonly property real shortSize: Math.max(Config.bar.height, Tokens.sizes.bar.innerSize)
|
||||
readonly property real size: (workspaceButtonWidth * workspacesShown) + activeWorkspaceMargin * 2
|
||||
readonly property real size: (workspaceButtonWidth * workspacesShown) + (spacing * Math.max(0, workspacesShown - 1)) + activeWorkspaceMargin * 2
|
||||
property int workspaceButtonWidth: (horizontal ? bgRect.implicitHeight : bgRect.implicitWidth) - root.activeWorkspaceMargin * 2
|
||||
property int workspaceIndexInGroup: (effectiveActiveWorkspaceId - 1) % root.workspacesShown
|
||||
readonly property list<var> workspaces: Hyprland.workspaces.values.filter(w => w.monitor === root.monitor)
|
||||
@@ -59,15 +60,17 @@ Item {
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: root.activeWorkspaceMargin
|
||||
columnSpacing: 0
|
||||
columnSpacing: root.spacing
|
||||
columns: root.horizontal ? root.workspacesShown : 1
|
||||
rowSpacing: 0
|
||||
rowSpacing: root.spacing
|
||||
rows: root.horizontal ? 1 : root.workspacesShown
|
||||
layer.enabled: true
|
||||
layer.mipmap: true
|
||||
|
||||
Repeater {
|
||||
model: root.workspaces
|
||||
|
||||
Button {
|
||||
CustomRect {
|
||||
id: button
|
||||
|
||||
required property int index
|
||||
@@ -75,27 +78,19 @@ Item {
|
||||
|
||||
implicitHeight: indicator.indicatorThickness
|
||||
implicitWidth: indicator.indicatorThickness
|
||||
width: root.workspaceButtonWidth
|
||||
radius: Tokens.rounding.full
|
||||
|
||||
background: Item {
|
||||
id: workspaceButtonBackground
|
||||
|
||||
implicitHeight: root.workspaceButtonWidth
|
||||
implicitWidth: root.workspaceButtonWidth
|
||||
|
||||
CustomText {
|
||||
anchors.centerIn: parent
|
||||
color: Colors.palette.m3onSecondaryContainer
|
||||
elide: Text.ElideRight
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: button.modelData?.name ?? ""
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
CustomText {
|
||||
anchors.centerIn: parent
|
||||
color: Colors.palette.m3onSecondaryContainer
|
||||
text: button.modelData?.name ?? ""
|
||||
}
|
||||
|
||||
onPressed: {
|
||||
const ws = button.modelData.name;
|
||||
Hyprland.dispatch(Hyprland.usingLua ? `hl.dsp.focus({ workspace= "${ws}"})` : `workspace ${ws}`);
|
||||
StateLayer {
|
||||
onPressed: {
|
||||
const ws = button.modelData.name;
|
||||
Hyprland.dispatch(Hyprland.usingLua ? `hl.dsp.focus({ workspace= "${ws}"})` : `workspace ${ws}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -104,8 +99,10 @@ Item {
|
||||
CustomRect {
|
||||
id: indicator
|
||||
|
||||
property real indicatorLength: (Math.abs(idxPair.idx1 - idxPair.idx2) + 1) * root.workspaceButtonWidth
|
||||
property real indicatorPosition: Math.min(idxPair.idx1, idxPair.idx2) * root.workspaceButtonWidth + root.activeWorkspaceMargin
|
||||
readonly property real step: root.workspaceButtonWidth + root.spacing
|
||||
|
||||
property real indicatorLength: (Math.abs(idxPair.idx1 - idxPair.idx2) + 1) * root.workspaceButtonWidth + Math.abs(idxPair.idx1 - idxPair.idx2) * root.spacing
|
||||
property real indicatorPosition: Math.min(idxPair.idx1, idxPair.idx2) * step + root.activeWorkspaceMargin
|
||||
property real indicatorThickness: root.workspaceButtonWidth
|
||||
|
||||
anchors.horizontalCenter: root.horizontal ? undefined : parent.horizontalCenter
|
||||
@@ -129,9 +126,10 @@ Item {
|
||||
implicitHeight: grid.height
|
||||
implicitWidth: grid.width
|
||||
source: grid
|
||||
autoPaddingEnabled: false
|
||||
sourceColor: Colors.palette.m3onSurface
|
||||
x: root.horizontal ? -indicator.x + 3 : 0
|
||||
y: root.horizontal ? 0 : -indicator.y + 3
|
||||
x: root.horizontal ? -indicator.x + root.activeWorkspaceMargin : 0
|
||||
y: root.horizontal ? 0 : -indicator.y + root.activeWorkspaceMargin
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,8 +33,7 @@ Item {
|
||||
y: parent.y
|
||||
|
||||
Behavior on implicitHeight {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,8 +100,7 @@ Item {
|
||||
value: Audio.volume
|
||||
|
||||
Behavior on value {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
|
||||
onInteraction: value => Audio.setVolume(value)
|
||||
@@ -123,7 +121,7 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
CustomClippingRect {
|
||||
CustomRect {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 65 + Tokens.spacing.small * 2
|
||||
Layout.topMargin: root.topMargin
|
||||
@@ -136,17 +134,21 @@ Item {
|
||||
node: Audio.source
|
||||
}
|
||||
|
||||
CustomRect {
|
||||
id: sourcePeakFill
|
||||
ClipRect {
|
||||
anchors.fill: parent
|
||||
radius: parent.radius
|
||||
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
color: Qt.alpha(Colors.palette.m3primary, 0.15)
|
||||
implicitWidth: parent.width * sourcePeak.peak
|
||||
CustomRect {
|
||||
id: sourcePeakFill
|
||||
|
||||
Behavior on implicitWidth {
|
||||
Anim {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
color: Qt.alpha(Colors.palette.m3primary, 0.15)
|
||||
implicitWidth: parent.width * sourcePeak.peak
|
||||
|
||||
Behavior on implicitWidth {
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -195,8 +197,7 @@ Item {
|
||||
value: Audio.sourceVolume
|
||||
|
||||
Behavior on value {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
|
||||
onInteraction: value => Audio.setSourceVolume(value)
|
||||
@@ -230,7 +231,7 @@ Item {
|
||||
|
||||
model: Audio.streams.filter(s => s.isSink)
|
||||
|
||||
CustomClippingRect {
|
||||
CustomRect {
|
||||
id: appBox
|
||||
|
||||
required property int index
|
||||
@@ -248,17 +249,21 @@ Item {
|
||||
node: appBox.modelData
|
||||
}
|
||||
|
||||
CustomRect {
|
||||
id: peakFill
|
||||
ClipRect {
|
||||
anchors.fill: parent
|
||||
radius: parent.radius
|
||||
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
color: Qt.alpha(Colors.palette.m3primary, 0.15)
|
||||
implicitWidth: parent.width * peak.peak
|
||||
CustomRect {
|
||||
id: peakFill
|
||||
|
||||
Behavior on implicitWidth {
|
||||
Anim {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
color: Qt.alpha(Colors.palette.m3primary, 0.15)
|
||||
implicitWidth: parent.width * peak.peak
|
||||
|
||||
Behavior on implicitWidth {
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -316,8 +321,7 @@ Item {
|
||||
value: appBox.modelData.audio.volume
|
||||
|
||||
Behavior on value {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
|
||||
onInteraction: value => Audio.setStreamVolume(appBox.modelData, value)
|
||||
|
||||
@@ -9,7 +9,7 @@ import qs.Modules
|
||||
import qs.Helpers
|
||||
import qs.Services
|
||||
|
||||
CustomClippingRect {
|
||||
CustomRect {
|
||||
id: root
|
||||
|
||||
readonly property bool hasUpdates: Object.keys(Updates.updates)?.length > 0
|
||||
@@ -73,6 +73,7 @@ CustomClippingRect {
|
||||
implicitHeight: Math.min(contentHeight, (root.itemHeight + spacing) * 5 - spacing)
|
||||
implicitWidth: contentWidth
|
||||
spacing: Tokens.spacing.medium
|
||||
clip: true
|
||||
|
||||
delegate: CustomRect {
|
||||
id: update
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Hyprland
|
||||
import QtQuick
|
||||
import qs.Components
|
||||
import ZShell.Config
|
||||
|
||||
@@ -25,7 +25,7 @@ Item {
|
||||
searchField.forceActiveFocus();
|
||||
}
|
||||
|
||||
CustomClippingRect {
|
||||
ClipRect {
|
||||
id: search
|
||||
|
||||
anchors.left: parent.left
|
||||
@@ -54,7 +54,7 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
CustomClippingRect {
|
||||
CustomRect {
|
||||
id: preview
|
||||
|
||||
anchors.bottom: parent.bottom
|
||||
@@ -64,6 +64,7 @@ Item {
|
||||
color: Colors.tPalette.m3surfaceContainer
|
||||
implicitWidth: ClipHistory.previewIsImage ? Math.max(Math.min(imagePreview.sourceSize.width + Tokens.padding.large * 2, Config.clipboard.sizes.previewWidth), Config.clipboard.sizes.minPreviewWidth) : Math.max(Math.min(textPreviewColumn.width + textPreviewColumn.anchors.margins * 2, Config.clipboard.sizes.previewWidth), Config.clipboard.sizes.minPreviewWidth)
|
||||
radius: 25
|
||||
clip: true
|
||||
|
||||
Behavior on implicitWidth {
|
||||
Anim {}
|
||||
@@ -228,11 +229,12 @@ Item {
|
||||
anchors.fill: parent
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
CustomClippingRect {
|
||||
Item {
|
||||
id: textRect
|
||||
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
clip: true
|
||||
|
||||
Item {
|
||||
id: textWrapper
|
||||
|
||||
@@ -14,7 +14,7 @@ Row {
|
||||
padding: 20
|
||||
spacing: 12
|
||||
|
||||
CustomClippingRect {
|
||||
ClipRect {
|
||||
color: Colors.layer(Colors.palette.m3surfaceContainerHigh, 2)
|
||||
implicitHeight: info.implicitHeight
|
||||
implicitWidth: info.implicitHeight
|
||||
|
||||
@@ -16,7 +16,7 @@ Item {
|
||||
implicitWidth: 1000
|
||||
implicitHeight: 320
|
||||
|
||||
CustomClippingRect {
|
||||
ClipRect {
|
||||
anchors.fill: parent
|
||||
radius: root.radius
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ CustomListView {
|
||||
anchors.bottom: parent.bottom
|
||||
spacing: Tokens.spacing.extraSmall / 2
|
||||
|
||||
CustomClippingRect {
|
||||
ClipRect {
|
||||
id: iconClip
|
||||
|
||||
Layout.fillHeight: true
|
||||
|
||||
@@ -25,7 +25,7 @@ Item {
|
||||
onClicked: root.close()
|
||||
}
|
||||
|
||||
CustomClippingRect {
|
||||
ClipRect {
|
||||
id: popupBackground
|
||||
readonly property real padding: 4
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ Item {
|
||||
visible: false
|
||||
z: 999
|
||||
|
||||
CustomClippingRect {
|
||||
ClipRect {
|
||||
id: popupBackground
|
||||
|
||||
readonly property real padding: Tokens.padding.extraSmall
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
import ZShell.Config
|
||||
@@ -9,7 +11,7 @@ Item {
|
||||
id: root
|
||||
|
||||
required property bool isCurrent
|
||||
required property var modelData // FileSystemEntry-like {path, relativePath}
|
||||
required property var modelData
|
||||
required property real tileHeight
|
||||
required property real tileWidth
|
||||
required property ScreenState screenState
|
||||
@@ -19,7 +21,7 @@ Item {
|
||||
implicitHeight: tileHeight + Tokens.padding.large * 2
|
||||
implicitWidth: tileWidth
|
||||
|
||||
CustomClippingRect {
|
||||
ClipRect {
|
||||
id: image
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
@@ -29,25 +31,75 @@ Item {
|
||||
radius: Tokens.rounding.small
|
||||
y: Tokens.padding.large
|
||||
|
||||
MaterialIcon {
|
||||
Loader {
|
||||
active: opacity > 0
|
||||
anchors.centerIn: parent
|
||||
color: Colors.tPalette.m3outline
|
||||
fontStyle: Tokens.font.icon.builders.extraLarge.scale(2).weight(Font.DemiBold).build()
|
||||
text: "image"
|
||||
opacity: img.status === Image.Ready ? 0 : 1
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {}
|
||||
}
|
||||
sourceComponent: CustomRect {
|
||||
color: Colors.palette.m3primaryContainer
|
||||
implicitHeight: loadingIndicator.implicitSize + Tokens.padding.large * 2
|
||||
implicitWidth: loadingIndicator.implicitSize + Tokens.padding.large * 2
|
||||
radius: Tokens.rounding.full
|
||||
|
||||
LoadingIndicator {
|
||||
id: loadingIndicator
|
||||
|
||||
anchors.centerIn: parent
|
||||
containsIcon: true
|
||||
implicitSize: Math.min(image.width, image.height) * 0.3
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CachingImage {
|
||||
Image {
|
||||
id: img
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
cache: true
|
||||
asynchronous: true
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
height: root.tileHeight
|
||||
path: root.modelData ? root.modelData.path : ""
|
||||
source: root.modelData ? root.modelData.thumbnailPath : ""
|
||||
smooth: root.isCurrent
|
||||
sourceSize.height: root.tileHeight
|
||||
sourceSize.width: root.tileHeight * (16 / 9)
|
||||
width: root.tileHeight * (16 / 9)
|
||||
|
||||
opacity: status === Image.Ready ? 1 : 0
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
type: Anim.SlowEffects
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
opacity: root.modelData.isVideo && img.status === Image.Ready ? 1 : 0
|
||||
active: opacity > 0
|
||||
anchors.centerIn: parent
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
type: Anim.SlowEffects
|
||||
}
|
||||
}
|
||||
|
||||
sourceComponent: CustomRect {
|
||||
implicitWidth: videoIcon.implicitWidth + Tokens.padding.extraLarge * 2
|
||||
implicitHeight: implicitWidth
|
||||
radius: Tokens.rounding.full
|
||||
color: Colors.tPalette.m3surface
|
||||
|
||||
MaterialIcon {
|
||||
id: videoIcon
|
||||
anchors.centerIn: parent
|
||||
text: "video_library"
|
||||
fontStyle: Tokens.font.icon.extraLarge
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StateLayer {
|
||||
|
||||
@@ -55,7 +55,7 @@ ColumnLayout {
|
||||
text: Time.format("dddd, d MMMM yyyy")
|
||||
}
|
||||
|
||||
CustomClippingRect {
|
||||
ClipRect {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.topMargin: Tokens.spacing.largeIncreased * 2
|
||||
color: Colors.tPalette.m3surfaceContainer
|
||||
|
||||
@@ -5,14 +5,13 @@ import qs.Helpers
|
||||
import ZShell.Config
|
||||
import qs.Services
|
||||
|
||||
CustomClippingRect {
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property var lock
|
||||
|
||||
implicitHeight: layout.implicitHeight
|
||||
implicitWidth: layout.implicitWidth
|
||||
radius: Tokens.rounding.largeIncreased
|
||||
|
||||
RowLayout {
|
||||
id: layout
|
||||
@@ -50,22 +49,10 @@ CustomClippingRect {
|
||||
lock: root.lock
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
NotifDock {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
spacing: Tokens.spacing.medium
|
||||
|
||||
CustomRect {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
bottomRightRadius: Tokens.rounding.largeIncreased
|
||||
color: Colors.tPalette.m3surfaceContainer
|
||||
radius: Tokens.rounding.small
|
||||
topRightRadius: Tokens.rounding.largeIncreased
|
||||
|
||||
NotifDock {
|
||||
lock: root.lock
|
||||
}
|
||||
}
|
||||
lock: root.lock
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,9 +221,9 @@ WlSessionLockSurface {
|
||||
|
||||
anchors.fill: parent
|
||||
color: Colors.palette.m3surface
|
||||
layer.enabled: true
|
||||
opacity: Colors.transparency.enabled ? Colors.transparency.base : 1
|
||||
radius: lockContent.radius
|
||||
layer.enabled: true
|
||||
|
||||
layer.effect: MultiEffect {
|
||||
blurMax: 15
|
||||
|
||||
+32
-27
@@ -2,44 +2,49 @@ import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import ZShell.Components
|
||||
import qs.Components
|
||||
import ZShell.Config
|
||||
import qs.Components
|
||||
import qs.Helpers
|
||||
import qs.Services
|
||||
|
||||
CustomClippingRect {
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property var lock
|
||||
|
||||
color: Colors.tPalette.m3surfaceContainer
|
||||
implicitHeight: layout.implicitHeight + layout.anchors.margins * 2
|
||||
radius: Tokens.rounding.small
|
||||
|
||||
FadeImage {
|
||||
id: image
|
||||
|
||||
ClipRect {
|
||||
anchors.fill: parent
|
||||
asynchronous: true
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
layer.enabled: true
|
||||
opacity: status === Image.Ready ? 1 : 0
|
||||
source: Players.getArtUrl(Players.active)
|
||||
sourceSize: {
|
||||
const dpr = (QsWindow.window as QsWindow)?.devicePixelRatio ?? 1;
|
||||
return Qt.size(width * dpr, height * dpr);
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
type: Anim.StandardExtraLarge
|
||||
}
|
||||
}
|
||||
color: Colors.tPalette.m3surfaceContainer
|
||||
radius: Tokens.rounding.small
|
||||
bottomLeftRadius: Tokens.rounding.largeIncreased
|
||||
|
||||
FadeImage {
|
||||
id: image
|
||||
|
||||
CustomRect {
|
||||
anchors.fill: parent
|
||||
color: Colors.palette.m3surface
|
||||
opacity: 0.7
|
||||
asynchronous: true
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
opacity: status === Image.Ready ? 1 : 0
|
||||
source: Players.getArtUrl(Players.active)
|
||||
sourceSize: {
|
||||
const dpr = (QsWindow.window as QsWindow)?.devicePixelRatio ?? 1;
|
||||
return Qt.size(width * dpr, height * dpr);
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
type: Anim.StandardExtraLarge
|
||||
}
|
||||
}
|
||||
|
||||
CustomRect {
|
||||
anchors.fill: parent
|
||||
color: Colors.palette.m3surface
|
||||
opacity: 0.7
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +82,7 @@ CustomClippingRect {
|
||||
spacing: Tokens.spacing.extraSmall
|
||||
|
||||
IconButton {
|
||||
enabled: Players.active?.canGoPrevious
|
||||
enabled: Players.active?.canGoPrevious ?? false
|
||||
icon: "skip_previous"
|
||||
isRound: true
|
||||
shapeMorph: true
|
||||
@@ -88,7 +93,7 @@ CustomClippingRect {
|
||||
|
||||
IconButton {
|
||||
checked: Players.active?.isPlaying ?? false
|
||||
enabled: Players.active?.canTogglePlaying
|
||||
enabled: Players.active?.canTogglePlaying ?? false
|
||||
icon: Players.active?.isPlaying ? "pause" : "play_arrow"
|
||||
implicitWidth: implicitHeight + Tokens.padding.largeIncreased * 2
|
||||
isRound: true
|
||||
@@ -98,7 +103,7 @@ CustomClippingRect {
|
||||
}
|
||||
|
||||
IconButton {
|
||||
enabled: Players.active?.canGoNext
|
||||
enabled: Players.active?.canGoNext ?? false
|
||||
icon: "skip_next"
|
||||
isRound: true
|
||||
shapeMorph: true
|
||||
|
||||
+94
-105
@@ -11,127 +11,116 @@ import ZShell.Config
|
||||
import qs.Daemons
|
||||
import qs.Services
|
||||
|
||||
ColumnLayout {
|
||||
CustomRect {
|
||||
id: root
|
||||
|
||||
required property var lock
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Tokens.padding.large
|
||||
spacing: Tokens.spacing.small
|
||||
bottomRightRadius: Tokens.rounding.largeIncreased
|
||||
color: Colors.tPalette.m3surfaceContainer
|
||||
radius: Tokens.rounding.small
|
||||
topRightRadius: Tokens.rounding.largeIncreased
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
color: Colors.palette.m3outline
|
||||
elide: Text.ElideRight
|
||||
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")
|
||||
}
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Tokens.padding.large
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
ClippingRectangle {
|
||||
id: clipRect
|
||||
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
color: "transparent"
|
||||
radius: Tokens.rounding.small
|
||||
|
||||
Loader {
|
||||
active: opacity > 0
|
||||
anchors.centerIn: parent
|
||||
opacity: NotifServer.list.length > 0 ? 0 : 1
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
duration: Tokens.anim.durations.extraLarge
|
||||
}
|
||||
}
|
||||
sourceComponent: ColumnLayout {
|
||||
spacing: Tokens.spacing.largeIncreased
|
||||
|
||||
Image {
|
||||
asynchronous: true
|
||||
fillMode: Image.PreserveAspectFit
|
||||
layer.enabled: true
|
||||
source: Qt.resolvedUrl(`${Quickshell.shellDir}/assets/dino.png`)
|
||||
sourceSize.width: clipRect.width * 0.8
|
||||
|
||||
layer.effect: Coloriser {
|
||||
brightness: 1
|
||||
colorizationColor: Colors.palette.m3outlineVariant
|
||||
}
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
color: Colors.palette.m3outlineVariant
|
||||
font: Tokens.font.headline.builders.small.width(90).build()
|
||||
text: qsTr("No Notifications")
|
||||
}
|
||||
}
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
color: Colors.palette.m3outline
|
||||
elide: Text.ElideRight
|
||||
font: Tokens.font.mono.builders.small.weight(Font.Medium).build()
|
||||
text: NotifServer.list.length > 0 ? qsTr("%1 notification%2").arg(NotifServer.list.length).arg(NotifServer.list.length === 1 ? "" : "s") : qsTr("Notifications")
|
||||
}
|
||||
|
||||
CustomListView {
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
spacing: Tokens.spacing.small
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
add: Transition {
|
||||
Anim {
|
||||
from: 0
|
||||
property: "opacity"
|
||||
to: 1
|
||||
Loader {
|
||||
active: opacity > 0
|
||||
anchors.centerIn: parent
|
||||
opacity: NotifServer.list.length > 0 ? 0 : 1
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
duration: Tokens.anim.durations.extraLarge
|
||||
}
|
||||
}
|
||||
sourceComponent: ColumnLayout {
|
||||
spacing: Tokens.spacing.largeIncreased
|
||||
|
||||
Anim {
|
||||
duration: Tokens.anim.durations.expressiveDefaultSpatial
|
||||
easing: Tokens.anim.expressiveDefaultSpatial
|
||||
from: 0
|
||||
property: "scale"
|
||||
to: 1
|
||||
CustomText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
color: Colors.palette.m3outlineVariant
|
||||
font: Tokens.font.headline.builders.small.width(90).build()
|
||||
text: qsTr("No Notifications")
|
||||
}
|
||||
}
|
||||
}
|
||||
delegate: NotifGroup {}
|
||||
displaced: Transition {
|
||||
Anim {
|
||||
properties: "opacity,scale"
|
||||
to: 1
|
||||
}
|
||||
|
||||
Anim {
|
||||
duration: Tokens.anim.durations.expressiveDefaultSpatial
|
||||
easing: Tokens.anim.expressiveDefaultSpatial
|
||||
property: "y"
|
||||
}
|
||||
}
|
||||
model: ScriptModel {
|
||||
values: {
|
||||
const list = NotifServer.notClosed.map(n => [n.appName, null]);
|
||||
return [...new Map(list).keys()];
|
||||
}
|
||||
}
|
||||
move: Transition {
|
||||
Anim {
|
||||
properties: "opacity,scale"
|
||||
to: 1
|
||||
}
|
||||
CustomListView {
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
Anim {
|
||||
duration: Tokens.anim.durations.expressiveDefaultSpatial
|
||||
easing: Tokens.anim.expressiveDefaultSpatial
|
||||
property: "y"
|
||||
}
|
||||
}
|
||||
remove: Transition {
|
||||
Anim {
|
||||
property: "opacity"
|
||||
to: 0
|
||||
}
|
||||
add: Transition {
|
||||
Anim {
|
||||
from: 0
|
||||
property: "opacity"
|
||||
to: 1
|
||||
}
|
||||
|
||||
Anim {
|
||||
property: "scale"
|
||||
to: 0.6
|
||||
Anim {
|
||||
duration: Tokens.anim.durations.expressiveDefaultSpatial
|
||||
easing: Tokens.anim.expressiveDefaultSpatial
|
||||
from: 0
|
||||
property: "scale"
|
||||
to: 1
|
||||
}
|
||||
}
|
||||
delegate: NotifGroup {}
|
||||
displaced: Transition {
|
||||
Anim {
|
||||
properties: "opacity,scale"
|
||||
to: 1
|
||||
}
|
||||
|
||||
Anim {
|
||||
duration: Tokens.anim.durations.expressiveDefaultSpatial
|
||||
easing: Tokens.anim.expressiveDefaultSpatial
|
||||
property: "y"
|
||||
}
|
||||
}
|
||||
model: ScriptModel {
|
||||
values: {
|
||||
const list = NotifServer.notClosed.map(n => [n.appName, null]);
|
||||
return [...new Map(list).keys()];
|
||||
}
|
||||
}
|
||||
move: Transition {
|
||||
Anim {
|
||||
properties: "opacity,scale"
|
||||
to: 1
|
||||
}
|
||||
|
||||
Anim {
|
||||
duration: Tokens.anim.durations.expressiveDefaultSpatial
|
||||
easing: Tokens.anim.expressiveDefaultSpatial
|
||||
property: "y"
|
||||
}
|
||||
}
|
||||
remove: Transition {
|
||||
Anim {
|
||||
property: "opacity"
|
||||
to: 0
|
||||
}
|
||||
|
||||
Anim {
|
||||
property: "scale"
|
||||
to: 0.6
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ CustomRect {
|
||||
}
|
||||
}
|
||||
|
||||
ClippingRectangle {
|
||||
ClipRect {
|
||||
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,12 +1,13 @@
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import QtQuick
|
||||
import qs.Components
|
||||
import qs.Paths
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
ClippingRectangle {
|
||||
ClipRect {
|
||||
anchors.fill: parent
|
||||
radius: Tokens.rounding.full
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ ColumnLayout {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
animate: true
|
||||
color: Colors.palette.m3onSurfaceVariant
|
||||
font: Tokens.font.title.large
|
||||
font: Tokens.font.body.large
|
||||
text: Weather.description
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ ColumnLayout {
|
||||
|
||||
animate: true
|
||||
color: Colors.palette.m3primary
|
||||
font: Tokens.font.body.large
|
||||
font: Tokens.font.title.large
|
||||
text: Weather.temp
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ ColumnLayout {
|
||||
animate: true
|
||||
color: Colors.palette.m3secondary
|
||||
text: Weather.icon
|
||||
fontStyle: Tokens.font.icon.large
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +54,7 @@ ColumnLayout {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
animate: true
|
||||
color: Colors.palette.m3onSurfaceVariant
|
||||
font: Tokens.font.body.normal
|
||||
font: Tokens.font.body.medium
|
||||
text: {
|
||||
const today = Weather.forecast[0];
|
||||
return qsTr("High %1 • Low %2").arg(Weather.formatTemp(today?.maxTempC)).arg(Weather.formatTemp(today?.minTempC));
|
||||
|
||||
@@ -36,7 +36,7 @@ CustomRect {
|
||||
}
|
||||
}
|
||||
|
||||
VerticalFadeListView {
|
||||
CustomListView {
|
||||
id: layout
|
||||
|
||||
anchors.bottom: parent.bottom
|
||||
@@ -45,13 +45,20 @@ CustomRect {
|
||||
anchors.margins: Tokens.padding.large
|
||||
anchors.right: parent.right
|
||||
implicitHeight: contentItem.childrenRect.height
|
||||
model: Weather.hourlyForecast
|
||||
currentIndex: Weather.currentHourIndex
|
||||
// highlightFollowsCurrentItem: true
|
||||
// preferredHighlightEnd: currentItem.implicitWidth
|
||||
// preferredHighlightBegin: 0
|
||||
// highlightRangeMode: ListView.ApplyRange
|
||||
clip: true
|
||||
model: Weather.hourlyForecast.slice(Weather.currentHourIndex - 1)
|
||||
orientation: VerticalFadeListView.Horizontal
|
||||
spacing: Tokens.spacing.medium
|
||||
|
||||
delegate: ColumnLayout {
|
||||
id: hour
|
||||
|
||||
readonly property bool current: ListView.view.currentItem === this
|
||||
readonly property var cond: modelData
|
||||
required property int index
|
||||
required property var modelData
|
||||
@@ -60,7 +67,7 @@ CustomRect {
|
||||
|
||||
MaterialShape {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
color: Qt.alpha(Colors.palette.m3primary, hour.index === Weather.currentHourIndex ? 1 : 0)
|
||||
color: Qt.alpha(Colors.palette.m3primary, hour.current ? 1 : 0)
|
||||
implicitSize: temp.implicitHeight + Tokens.padding.small * 2
|
||||
shape: MaterialShape.Cookie4Sided
|
||||
|
||||
@@ -72,7 +79,7 @@ CustomRect {
|
||||
id: temp
|
||||
|
||||
anchors.centerIn: parent
|
||||
color: hour.index === Weather.currentHourIndex ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
|
||||
color: hour.current ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
|
||||
font: Tokens.font.body.medium
|
||||
text: Weather.formatTemp(hour.cond.tempC).slice(0, -1) // Remove C/F
|
||||
}
|
||||
@@ -96,7 +103,7 @@ CustomRect {
|
||||
Layout.topMargin: Tokens.spacing.extraSmall
|
||||
color: Colors.palette.m3onSurfaceVariant
|
||||
font: Tokens.font.body.small
|
||||
text: hour.index === 0 ? qsTr("Now") : Qt.formatDateTime(new Date(hour.cond.timestamp.replace("T", " ")), Config.services.useTwelveHourClock ? "ha" : "hh:00")
|
||||
text: hour.current ? qsTr("Now") : Qt.formatDateTime(new Date(hour.cond.timestamp.replace("T", " ")), Config.services.useTwelveHourClock ? "ha" : "hh:00")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ CustomRect {
|
||||
return base + brief.anchors.topMargin;
|
||||
}
|
||||
radius: Tokens.rounding.small
|
||||
topLeftRadius: Tokens.rounding.largeIncreased
|
||||
|
||||
Timer {
|
||||
interval: 900000 // 15 minutes
|
||||
@@ -48,7 +49,6 @@ CustomRect {
|
||||
anchors.right: parent.right
|
||||
asynchronous: true
|
||||
|
||||
sourceComponent: Forecast {
|
||||
}
|
||||
sourceComponent: Forecast {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,9 @@ pragma ComponentBehavior: Bound
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import QtQuick
|
||||
import qs.Components
|
||||
import ZShell.Config
|
||||
import ZShell.Components
|
||||
import qs.Components
|
||||
import qs.Daemons
|
||||
import qs.Helpers
|
||||
|
||||
@@ -30,31 +31,29 @@ Item {
|
||||
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))
|
||||
return 0;
|
||||
|
||||
if (screenState.osd) {
|
||||
const h = panels.osd.y - Tokens.spacing.small * 2 - padding * 2;
|
||||
if (height > h)
|
||||
height = h;
|
||||
}
|
||||
|
||||
if (screenState.session) {
|
||||
const h = panels.session.y - Tokens.spacing.small * 2 - padding * 2;
|
||||
if (height > h)
|
||||
height = h;
|
||||
}
|
||||
|
||||
return Math.min(((QsWindow.window as QsWindow)?.screen?.height ?? 0) - 1 * 2, height + padding * 2);
|
||||
}
|
||||
|
||||
implicitWidth: Config.notifs.sizes.width + padding * 2
|
||||
|
||||
Behavior on implicitHeight {
|
||||
Anim {}
|
||||
}
|
||||
|
||||
ClippingWrapperRectangle {
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
anchors.margins: root.padding
|
||||
color: "transparent"
|
||||
radius: Tokens.rounding.large - root.padding
|
||||
clip: true
|
||||
// radius: Tokens.rounding.large - root.padding
|
||||
|
||||
CustomListView {
|
||||
id: list
|
||||
|
||||
@@ -108,7 +108,7 @@ CustomRect {
|
||||
visible: root.hasImage || root.hasAppIcon
|
||||
width: Config.notifs.sizes.image
|
||||
|
||||
sourceComponent: CustomClippingRect {
|
||||
sourceComponent: ClipRect {
|
||||
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
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,32 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import ZShell.Config
|
||||
import qs.Components
|
||||
import qs.Services
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
color: Colors.tPalette.m3outlineVariant
|
||||
font.pointSize: 36
|
||||
text: "chat"
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
color: Colors.tPalette.m3onSurfaceVariant
|
||||
text: qsTr("Send a message to get started")
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
}
|
||||
@@ -1,619 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import ZShell.Blobs
|
||||
import ZShell.Config
|
||||
import ZShell.Components
|
||||
import ZShell.Llm
|
||||
import qs.Components
|
||||
import qs.Services
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property int index
|
||||
required property var modelData
|
||||
required property var blocks
|
||||
readonly property var segments: modelData.segments
|
||||
required property Item rootParent
|
||||
required property ChatGeneration current
|
||||
readonly property bool isActive: index === blocks.length - 1
|
||||
property bool expanded: false
|
||||
readonly property LlmSegment lastSegment: root.segments[root.segments.length - 1]
|
||||
readonly property real totalElapsedMs: root.segments.reduce((sum, s) => sum + (s.elapsedMs ?? 0), 0)
|
||||
readonly property bool pendingApproval: current.toolApprovalPending && segments.filter(s => s.type === LlmSegment.Type.ToolCall).some(s => current.pendingToolCalls.includes(s))
|
||||
|
||||
implicitHeight: {
|
||||
const expand = expandedRect.implicitHeight + layout.implicitHeight + expandedRect.anchors.topMargin;
|
||||
const collapsed = layout.implicitHeight;
|
||||
const approval = layout.implicitHeight + approvalPrompt.implicitHeight + approvalPrompt.anchors.topMargin;
|
||||
|
||||
if (expanded)
|
||||
return expand;
|
||||
else if (pendingApproval)
|
||||
return approval;
|
||||
else
|
||||
return collapsed;
|
||||
}
|
||||
|
||||
onCurrentChanged: expanded = false
|
||||
|
||||
Behavior on implicitHeight {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: layout
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
Item {
|
||||
implicitHeight: expandBtn.implicitHeight
|
||||
implicitWidth: expandBtn.implicitWidth
|
||||
|
||||
LoadingIndicator {
|
||||
id: spinnerReasoning
|
||||
|
||||
implicitSize: collapsedText.implicitHeight
|
||||
anchors.centerIn: parent
|
||||
opacity: root.isActive ? 1 : 0
|
||||
visible: opacity > 0
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
|
||||
IconButton {
|
||||
id: expandBtn
|
||||
|
||||
font: Tokens.font.icon.medium
|
||||
icon: "keyboard_arrow_down"
|
||||
inactiveOnColor: hovered ? Colors.palette.m3onSurface : Colors.palette.m3outline
|
||||
anchors.centerIn: parent
|
||||
opacity: root.isActive ? 0 : 1
|
||||
enabled: opacity > 0
|
||||
rotation: root.expanded ? 180 : 0
|
||||
type: IconButton.Text
|
||||
|
||||
Behavior on rotation {
|
||||
Anim {}
|
||||
}
|
||||
|
||||
onClicked: root.expanded = !root.expanded
|
||||
}
|
||||
}
|
||||
|
||||
CustomText {
|
||||
id: collapsedText
|
||||
|
||||
color: Colors.palette.m3outline
|
||||
font: Tokens.font.label.small
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Tokens.spacing.medium
|
||||
text: {
|
||||
if (root.isActive) {
|
||||
if (root.pendingApproval)
|
||||
return qsTr("Waiting for approval...");
|
||||
|
||||
if (root.lastSegment.type === LlmSegment.Type.ToolCall)
|
||||
return qsTr("Using %1...").arg(root.lastSegment.name);
|
||||
return qsTr("Thinking...");
|
||||
}
|
||||
return qsTr("Worked for %1s").arg((root.totalElapsedMs / 1000).toFixed(1));
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: approvalPrompt
|
||||
anchors.top: layout.bottom
|
||||
anchors.topMargin: Tokens.spacing.small
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
implicitHeight: approvalLayout.implicitHeight + approvalLayout.anchors.margins * 2
|
||||
visible: root.pendingApproval
|
||||
|
||||
CustomRect {
|
||||
id: approvalBg
|
||||
|
||||
anchors.fill: parent
|
||||
radius: Tokens.rounding.medium
|
||||
color: Colors.palette.m3surface
|
||||
border.width: 1
|
||||
border.color: Colors.palette.m3outlineVariant
|
||||
|
||||
ColumnLayout {
|
||||
id: approvalLayout
|
||||
anchors.fill: parent
|
||||
anchors.margins: Tokens.padding.medium
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
CustomText {
|
||||
id: requestHeader
|
||||
text: qsTr("Tool call request")
|
||||
font: Tokens.font.headline.medium
|
||||
color: Colors.palette.m3onSurface
|
||||
}
|
||||
|
||||
Item {
|
||||
id: toolsBg
|
||||
|
||||
property bool open: false
|
||||
property Item rootParent: root.rootParent
|
||||
property int maxHeight: rootParent.height - Tokens.padding.medium * 2
|
||||
property int openHeight: Math.min(toolsContent.item?.implicitHeight, maxHeight)
|
||||
property int openWidth: toolsBg.rootParent.width - Tokens.padding.medium * 2
|
||||
property real openHeightProg: openHeight / maxHeight
|
||||
property int collapsedY: 0
|
||||
|
||||
implicitHeight: toolList.implicitHeight + toolList.anchors.margins * 2
|
||||
z: open || dialogTransition.running ? 2 : 0
|
||||
Layout.fillWidth: true
|
||||
|
||||
function reparentWrapper(): void {
|
||||
const newParent = open ? toolsBg.rootParent : toolsBg;
|
||||
const c = toolsWrapper.parent.mapToItem(newParent, toolsWrapper.x + toolsWrapper.width / 2, toolsWrapper.y + toolsWrapper.height / 2);
|
||||
toolsBg.collapsedY = toolsWrapper.y;
|
||||
toolsWrapper.parent = newParent;
|
||||
toolsWrapper.x = c.x - toolsWrapper.width / 2;
|
||||
toolsWrapper.y = c.y - toolsWrapper.height / 2;
|
||||
}
|
||||
|
||||
onOpenChanged: {
|
||||
if (open)
|
||||
toolsBg.rootParent.interactive = false;
|
||||
else
|
||||
toolsBg.rootParent.interactive = true;
|
||||
}
|
||||
|
||||
BlobGroup {
|
||||
id: blobGroup
|
||||
|
||||
color: toolsBg.open ? Colors.palette.m3surfaceContainerHighest : Colors.tPalette.m3primaryContainer
|
||||
|
||||
Behavior on color {
|
||||
CAnim {}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: backdrop
|
||||
|
||||
anchors.fill: parent
|
||||
enabled: false
|
||||
hoverEnabled: enabled
|
||||
preventStealing: true
|
||||
parent: toolsBg.open ? toolsBg.rootParent : toolsBg
|
||||
|
||||
onClicked: toolsBg.open = false
|
||||
}
|
||||
|
||||
Item {
|
||||
id: toolsWrapper
|
||||
|
||||
width: toolsBg.width
|
||||
height: toolBox.implicitHeight
|
||||
rotation: parent === toolsBg.rootParent ? 180 : 0
|
||||
z: 1
|
||||
|
||||
states: State {
|
||||
name: "open"
|
||||
when: toolsBg.open
|
||||
|
||||
PropertyChanges {
|
||||
backdrop.enabled: true
|
||||
toolsBackground.bottomLeftRadius: Tokens.rounding.largeIncreased
|
||||
toolsBackground.bottomRightRadius: Tokens.rounding.largeIncreased
|
||||
toolsBackground.topRightRadius: Tokens.rounding.largeIncreased
|
||||
toolsBackground.topLeftRadius: Tokens.rounding.largeIncreased
|
||||
toolsContent.opacity: 1
|
||||
toolsWrapper.height: toolsBg.openHeight
|
||||
toolsWrapper.width: toolsBg.openWidth
|
||||
toolsWrapper.x: (toolsBg.rootParent.width - toolsBg.openWidth) / 2
|
||||
toolsWrapper.y: (confirmationRow.implicitHeight + Tokens.spacing.small + Tokens.padding.medium) * (1 - toolsBg.openHeightProg) + Tokens.padding.medium
|
||||
elevation.opacity: 1
|
||||
toolBox.opacity: 0
|
||||
}
|
||||
}
|
||||
transitions: Transition {
|
||||
id: dialogTransition
|
||||
|
||||
SequentialAnimation {
|
||||
ScriptAction {
|
||||
script: toolsBg.reparentWrapper()
|
||||
}
|
||||
|
||||
Anim {
|
||||
properties: "x,y"
|
||||
}
|
||||
}
|
||||
|
||||
PropertyAction {
|
||||
property: "enabled"
|
||||
}
|
||||
|
||||
Anim {
|
||||
properties: "opacity,topLeftRadius,topRightRadius,bottomLeftRadius,bottomRightRadius"
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
|
||||
Anim {
|
||||
properties: "width,height"
|
||||
}
|
||||
}
|
||||
|
||||
Elevation {
|
||||
id: elevation
|
||||
|
||||
anchors.fill: parent
|
||||
bottomLeftRadius: toolsBackground.bottomLeftRadius
|
||||
bottomRightRadius: toolsBackground.bottomRightRadius
|
||||
level: 4
|
||||
opacity: 0
|
||||
radius: toolsBackground.radius
|
||||
|
||||
transform: Matrix4x4 {
|
||||
matrix: toolsBackground.deformMatrix
|
||||
}
|
||||
}
|
||||
|
||||
BlobRect {
|
||||
id: toolsBackground
|
||||
|
||||
anchors.fill: parent
|
||||
bottomLeftRadius: toolsBg.open ? Tokens.rounding.largeIncreased : Tokens.rounding.small
|
||||
bottomRightRadius: toolsBg.open ? Tokens.rounding.largeIncreased : Tokens.rounding.small
|
||||
topRightRadius: toolsBg.open ? Tokens.rounding.largeIncreased : Tokens.rounding.small
|
||||
topLeftRadius: toolsBg.open ? Tokens.rounding.largeIncreased : Tokens.rounding.small
|
||||
deformScale: 0
|
||||
group: blobGroup
|
||||
opacity: blobGroup.color.a * (root.enabled ? 1 : 0.5)
|
||||
}
|
||||
|
||||
CustomRect {
|
||||
id: toolBox
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: Math.min(implicitHeight, parent.height)
|
||||
implicitHeight: toolList.implicitHeight + Tokens.padding.medium * 2
|
||||
radius: Tokens.rounding.small
|
||||
|
||||
StateLayer {
|
||||
onClicked: {
|
||||
toolsBg.open = true;
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: toolList
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.margins: Tokens.padding.medium
|
||||
|
||||
Repeater {
|
||||
id: repeater
|
||||
|
||||
model: root.current.pendingToolCalls?.slice(0, 4) ?? []
|
||||
|
||||
delegate: RowLayout {
|
||||
id: toolText
|
||||
|
||||
required property LlmSegment modelData
|
||||
required property int index
|
||||
|
||||
spacing: Tokens.spacing.medium
|
||||
|
||||
function toolTarget(seg: LlmSegment): string {
|
||||
var a = null;
|
||||
try {
|
||||
a = JSON.parse(seg.arguments);
|
||||
} catch (e) {}
|
||||
if (a && typeof a === "object") {
|
||||
const v = a.url ?? a.path;
|
||||
if (v !== undefined && v !== null)
|
||||
return String(v);
|
||||
}
|
||||
return String(seg.arguments);
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
color: Colors.palette.m3onPrimaryContainer
|
||||
text: toolText.modelData.name === "webfetch" ? "web" : "text_snippet"
|
||||
fill: 1
|
||||
}
|
||||
|
||||
CustomText {
|
||||
color: Colors.palette.m3onPrimaryContainer
|
||||
text: qsTr("%1").arg(toolText.modelData.name)
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
color: Colors.palette.m3onPrimaryContainer
|
||||
text: "arrow_right_alt"
|
||||
}
|
||||
|
||||
CustomText {
|
||||
color: Colors.palette.m3onPrimaryContainer
|
||||
text: qsTr("%1").arg(toolText.toolTarget(toolText.modelData))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CustomText {
|
||||
color: Colors.palette.m3onPrimaryContainer
|
||||
text: qsTr("And %1 more").arg(root.current.pendingToolCalls.slice(4).length)
|
||||
visible: repeater.count < root.current.pendingToolCalls.length
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: toolsContent
|
||||
|
||||
active: opacity > 0
|
||||
anchors.fill: parent
|
||||
asynchronous: false
|
||||
opacity: 0
|
||||
|
||||
sourceComponent: MouseArea {
|
||||
onWheel: event => event.accepted = true
|
||||
preventStealing: true
|
||||
implicitHeight: innerLayout.contentHeight + innerLayout.anchors.margins * 2
|
||||
|
||||
VerticalFadeListView {
|
||||
id: innerLayout
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Tokens.padding.small
|
||||
cacheBuffer: height * 10
|
||||
fadeAmount: 0.1
|
||||
fadeEnabled: interactive
|
||||
spacing: 0
|
||||
clip: true
|
||||
interactive: contentHeight > toolsBg.maxHeight
|
||||
|
||||
model: root.current.pendingToolCalls ?? []
|
||||
|
||||
delegate: Item {
|
||||
id: toolCall
|
||||
|
||||
required property LlmSegment modelData
|
||||
property bool expanded: false
|
||||
|
||||
implicitWidth: CustomListView.view.width
|
||||
implicitHeight: collapsedRow.implicitHeight + collapsedRow.anchors.margins + (expanded ? expandedTool.implicitHeight + expandedTool.anchors.margins + expandedTool.anchors.topMargin : collapsedRow.anchors.margins)
|
||||
|
||||
function toolTarget(seg: LlmSegment): string {
|
||||
var a = null;
|
||||
try {
|
||||
a = JSON.parse(seg.arguments);
|
||||
} catch (e) {}
|
||||
if (a && typeof a === "object") {
|
||||
const v = a.url ?? a.path;
|
||||
if (v !== undefined && v !== null)
|
||||
return String(v);
|
||||
}
|
||||
return String(seg.arguments);
|
||||
}
|
||||
|
||||
Behavior on implicitHeight {
|
||||
Anim {}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: collapsedRow
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: Tokens.padding.medium
|
||||
|
||||
IconButton {
|
||||
id: toolExpandBtn
|
||||
|
||||
font: Tokens.font.icon.medium
|
||||
icon: "keyboard_arrow_down"
|
||||
inactiveOnColor: hovered ? Colors.palette.m3onSurface : Colors.palette.m3outline
|
||||
rotation: toolCall.expanded ? 180 : 0
|
||||
type: IconButton.Text
|
||||
|
||||
Behavior on rotation {
|
||||
Anim {}
|
||||
}
|
||||
|
||||
onClicked: toolCall.expanded = !toolCall.expanded
|
||||
}
|
||||
|
||||
CustomText {
|
||||
id: preview
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: toolCall.modelData.name
|
||||
}
|
||||
}
|
||||
|
||||
CustomClippingRect {
|
||||
id: expandedTool
|
||||
|
||||
anchors.top: collapsedRow.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.margins: Tokens.padding.medium
|
||||
anchors.topMargin: Tokens.spacing.medium
|
||||
|
||||
implicitHeight: fullToolCall.implicitHeight + fullToolCall.anchors.margins * 2
|
||||
radius: Tokens.rounding.medium
|
||||
color: Colors.palette.m3surfaceContainerLow
|
||||
|
||||
CustomText {
|
||||
id: fullToolCall
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Tokens.padding.medium
|
||||
|
||||
text: toolCall.toolTarget(toolCall.modelData)
|
||||
wrapMode: CustomText.WrapAtWordBoundaryOrAnywhere
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ButtonRow {
|
||||
id: confirmationRow
|
||||
Layout.alignment: Qt.AlignRight
|
||||
spacing: Tokens.spacing.medium
|
||||
|
||||
IconTextButton {
|
||||
icon: "check_circle"
|
||||
text: qsTr("Allow")
|
||||
horizontalPadding: Tokens.padding.large
|
||||
verticalPadding: Tokens.padding.small
|
||||
isRound: true
|
||||
shapeMorph: true
|
||||
onClicked: root.current.approveTools()
|
||||
}
|
||||
|
||||
IconTextButton {
|
||||
icon: "cancel"
|
||||
text: qsTr("Deny")
|
||||
isRound: true
|
||||
shapeMorph: true
|
||||
inactiveColor: Colors.palette.m3error
|
||||
inactiveOnColor: Colors.palette.m3onError
|
||||
horizontalPadding: Tokens.padding.large
|
||||
verticalPadding: Tokens.padding.small
|
||||
onClicked: root.current.denyTools()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CustomClippingRect {
|
||||
id: expandedRect
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: layout.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.topMargin: Tokens.spacing.small
|
||||
color: Colors.palette.m3surfaceContainerLow
|
||||
implicitHeight: expandedContent.contentHeight + expandedContent.anchors.margins * 2
|
||||
opacity: root.expanded ? 1 : 0
|
||||
radius: Tokens.rounding.medium
|
||||
visible: opacity > 0
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {}
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: expandedContent
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Tokens.padding.medium
|
||||
model: root.segments
|
||||
spacing: Tokens.spacing.medium
|
||||
|
||||
delegate: Item {
|
||||
id: segment
|
||||
|
||||
required property LlmSegment modelData
|
||||
required property int index
|
||||
|
||||
implicitWidth: ListView.view.width
|
||||
implicitHeight: header.implicitHeight + content.implicitHeight + content.anchors.topMargin
|
||||
|
||||
RowLayout {
|
||||
id: header
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
MaterialIcon {
|
||||
text: segment.modelData.type === LlmSegment.Type.Reasoning ? "cognition" : "build"
|
||||
fill: 1
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
text: segment.modelData.type === LlmSegment.Type.Reasoning ? qsTr("Thought for %1s").arg((segment.modelData.elapsedMs / 1000).toFixed(1)) : qsTr("Used %1 for %2s").arg(segment.modelData.name).arg((segment.modelData.elapsedMs / 1000).toFixed(1))
|
||||
}
|
||||
}
|
||||
|
||||
CustomText {
|
||||
id: content
|
||||
|
||||
function toolTarget(seg: LlmSegment): string {
|
||||
var a = null;
|
||||
try {
|
||||
a = JSON.parse(seg.arguments);
|
||||
} catch (e) {}
|
||||
if (a && typeof a === "object") {
|
||||
const v = a.url ?? a.path;
|
||||
if (v !== undefined && v !== null)
|
||||
return String(v);
|
||||
}
|
||||
return String(seg.arguments);
|
||||
}
|
||||
|
||||
function prettyTool(seg: LlmSegment): string {
|
||||
if (seg.name === "webfetch") {
|
||||
switch (seg.status) {
|
||||
case LlmSegment.Status.Running:
|
||||
return qsTr("Fetching");
|
||||
case LlmSegment.Status.Pending:
|
||||
return qsTr("Pending fetch");
|
||||
case LlmSegment.Status.Success:
|
||||
return qsTr("Fetched");
|
||||
case LlmSegment.Status.Error:
|
||||
return qsTr("Failed to fetch");
|
||||
default:
|
||||
return qsTr("Fetched website");
|
||||
}
|
||||
} else if (seg.name === "readfile") {
|
||||
switch (seg.status) {
|
||||
case LlmSegment.Status.Running:
|
||||
return qsTr("Reading");
|
||||
case LlmSegment.Status.Pending:
|
||||
return qsTr("Pending read");
|
||||
case LlmSegment.Status.Success:
|
||||
return qsTr("Read");
|
||||
case LlmSegment.Status.Error:
|
||||
return qsTr("Failed to read");
|
||||
default:
|
||||
return qsTr("Read file");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Tokens.padding.small
|
||||
anchors.top: header.bottom
|
||||
anchors.topMargin: Tokens.spacing.small
|
||||
anchors.right: parent.right
|
||||
color: Colors.palette.m3outline
|
||||
font: Tokens.font.body.small
|
||||
wrapMode: CustomText.WrapAtWordBoundaryOrAnywhere
|
||||
text: segment.modelData.type === LlmSegment.Type.Reasoning ? segment.modelData.text : qsTr("%1 %2").arg(content.prettyTool(segment.modelData)).arg(content.toolTarget(segment.modelData))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import ZShell.Config
|
||||
import qs.Components
|
||||
import qs.Services
|
||||
|
||||
CustomClippingRect {
|
||||
ClipRect {
|
||||
id: root
|
||||
|
||||
property real animPerc: UPower.displayDevice.percentage
|
||||
|
||||
@@ -4,7 +4,7 @@ import ZShell.Config
|
||||
import qs.Components
|
||||
import qs.Services
|
||||
|
||||
CustomClippingRect {
|
||||
CustomRect {
|
||||
id: root
|
||||
|
||||
required property color accent
|
||||
@@ -19,15 +19,20 @@ CustomClippingRect {
|
||||
implicitWidth: 450
|
||||
radius: Tokens.rounding.largeIncreased
|
||||
|
||||
CustomRect {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
color: Qt.alpha(root.accent, 0.05)
|
||||
implicitWidth: parent.width * root.usage
|
||||
ClipRect {
|
||||
anchors.fill: parent
|
||||
radius: parent.radius
|
||||
|
||||
Behavior on implicitWidth {
|
||||
Anim {}
|
||||
CustomRect {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
color: Qt.alpha(root.accent, 0.05)
|
||||
implicitWidth: parent.width * root.usage
|
||||
|
||||
Behavior on implicitWidth {
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,8 @@ CustomRect {
|
||||
value: Memory.percentage
|
||||
|
||||
Behavior on clampedVal {
|
||||
Anim {}
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
|
||||
@@ -121,6 +121,6 @@ CustomRect {
|
||||
|
||||
activeIcon: "storage"
|
||||
icon: modelData === Storage.primaryDisk ? "check" : ""
|
||||
text: modelData.mount
|
||||
text: modelData.name
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,7 +149,7 @@ ColumnLayout {
|
||||
}
|
||||
}
|
||||
|
||||
VerticalFadeFlickable {
|
||||
CustomFlickable {
|
||||
id: flickable
|
||||
|
||||
Layout.fillHeight: true
|
||||
@@ -158,7 +158,8 @@ ColumnLayout {
|
||||
|
||||
bottomMargin: Tokens.padding.extraLarge
|
||||
topMargin: Tokens.padding.large
|
||||
fadeAmount: 0.1
|
||||
clip: true
|
||||
// fadeAmount: 0.1
|
||||
|
||||
contentHeight: root.contentChild?.implicitHeight ?? 0
|
||||
contentItem.children: [root.contentChild]
|
||||
|
||||
@@ -25,7 +25,7 @@ Item {
|
||||
anchors.fill: parent
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
CustomClippingRect {
|
||||
ClipRect {
|
||||
id: imgWrapper
|
||||
|
||||
Layout.fillWidth: true
|
||||
@@ -39,8 +39,7 @@ Item {
|
||||
opacity: img.status === Image.Ready ? 0 : 1
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
sourceComponent: CustomRect {
|
||||
color: Colors.palette.m3primaryContainer
|
||||
@@ -64,18 +63,43 @@ Item {
|
||||
anchors.fill: parent
|
||||
asynchronous: true
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
opacity: status === Image.Ready ? 1 : 0
|
||||
retainWhileLoading: true
|
||||
sourceSize: {
|
||||
const dpr = (QsWindow.window as QsWindow)?.devicePixelRatio ?? 1;
|
||||
return Qt.size(width * dpr, height * dpr);
|
||||
}
|
||||
|
||||
opacity: status === Image.Ready ? 1 : 0
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
type: Anim.SlowEffects
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
opacity: root.modelData.isVideo && img.status === Image.Ready ? 1 : 0
|
||||
active: opacity > 0
|
||||
anchors.centerIn: parent
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
type: Anim.SlowEffects
|
||||
}
|
||||
}
|
||||
|
||||
sourceComponent: CustomRect {
|
||||
implicitWidth: videoIcon.implicitWidth + Tokens.padding.extraLarge * 2
|
||||
implicitHeight: implicitWidth
|
||||
radius: Tokens.rounding.full
|
||||
color: Colors.tPalette.m3surface
|
||||
|
||||
MaterialIcon {
|
||||
id: videoIcon
|
||||
anchors.centerIn: parent
|
||||
text: "video_library"
|
||||
fontStyle: Tokens.font.icon.extraLarge
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,9 +13,14 @@ import qs.Services
|
||||
Item {
|
||||
id: wrapper
|
||||
|
||||
property int buttonRowHeight: Tokens.padding.large * 3
|
||||
readonly property bool isVideo: Wallpapers.currentIsVideo
|
||||
readonly property string currentWallpaper: Wallpapers.current
|
||||
readonly property var currentScreen: screens.length > selectedScreenIndex ? screens[selectedScreenIndex] : null
|
||||
readonly property real maxZoom: 5.0
|
||||
readonly property bool changesMade: {
|
||||
cropRevision;
|
||||
Wallpapers.cropVersion;
|
||||
wrapper.currentWallpaper;
|
||||
|
||||
const r = cropRectLoader.item;
|
||||
if (!r || !currentScreen || scaledImg.status !== Image.Ready)
|
||||
@@ -25,9 +30,9 @@ Item {
|
||||
|
||||
return Math.abs(r.x - t.x) > 0.5 || Math.abs(r.y - t.y) > 0.5 || Math.abs(r.zoom - t.zoom) > 0.001;
|
||||
}
|
||||
|
||||
property int buttonRowHeight: Tokens.padding.large * 3
|
||||
property int cropRevision: 0
|
||||
readonly property var currentScreen: screens.length > selectedScreenIndex ? screens[selectedScreenIndex] : null
|
||||
readonly property real maxZoom: 5.0
|
||||
property var screens: []
|
||||
property int selectedScreenIndex: 0
|
||||
property bool shouldBeActive: true
|
||||
@@ -100,29 +105,28 @@ Item {
|
||||
visible: opacity > 0
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
Behavior on scale {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
Behavior on y {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
refreshScreens();
|
||||
Qt.callLater(syncCropToScreen);
|
||||
}
|
||||
onCurrentWallpaperChanged: {
|
||||
Qt.callLater(syncCropToScreen);
|
||||
}
|
||||
onSelectedScreenIndexChanged: {
|
||||
Qt.callLater(syncCropToScreen);
|
||||
}
|
||||
|
||||
ButtonRow {
|
||||
id: screenSelector
|
||||
|
||||
Loader {
|
||||
id: selectorLoader
|
||||
anchors.bottom: imageSlot.top
|
||||
anchors.bottomMargin: Tokens.spacing.medium
|
||||
anchors.left: parent.left
|
||||
@@ -130,25 +134,32 @@ Item {
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Tokens.padding.extraLarge * 2
|
||||
anchors.top: parent.top
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
Repeater {
|
||||
model: wrapper.screens
|
||||
active: wrapper.screens.length > 1 && !wrapper.isVideo
|
||||
|
||||
delegate: TextButton {
|
||||
required property int index
|
||||
readonly property bool isCurrent: wrapper.selectedScreenIndex === index
|
||||
required property var modelData
|
||||
sourceComponent: ButtonRow {
|
||||
id: screenSelector
|
||||
|
||||
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
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
onClicked: wrapper.selectScreen(index)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -160,6 +171,7 @@ Item {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
implicitHeight: 30
|
||||
enabled: !wrapper.isVideo
|
||||
|
||||
CustomSlider {
|
||||
id: zoomSlider
|
||||
@@ -222,9 +234,9 @@ Item {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: wrapper.buttonRowHeight + Tokens.spacing.medium
|
||||
anchors.topMargin: selectorLoader.active ? wrapper.buttonRowHeight + Tokens.spacing.medium : Tokens.spacing.medium
|
||||
|
||||
CustomClippingRect {
|
||||
ClipRect {
|
||||
id: fitBox
|
||||
|
||||
readonly property real aspect: scaledImg.implicitHeight > 0 ? scaledImg.implicitWidth / scaledImg.implicitHeight : 1
|
||||
@@ -271,7 +283,7 @@ Item {
|
||||
opacity: status === Image.Ready ? 1 : 0
|
||||
retainWhileLoading: true
|
||||
smooth: true
|
||||
source: Wallpapers.current
|
||||
source: Wallpapers.currentThumbnail
|
||||
sourceSize.height: {
|
||||
const dpr = (QsWindow.window as QsWindow)?.devicePixelRatio ?? 1;
|
||||
return imageSlot.height * dpr;
|
||||
@@ -282,12 +294,37 @@ Item {
|
||||
type: Anim.SlowEffects
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
opacity: wrapper.isVideo && scaledImg.status === Image.Ready ? 1 : 0
|
||||
active: opacity > 0
|
||||
anchors.centerIn: parent
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
type: Anim.SlowEffects
|
||||
}
|
||||
}
|
||||
|
||||
sourceComponent: CustomRect {
|
||||
implicitWidth: videoIcon.implicitWidth + Tokens.padding.extraLarge * 2
|
||||
implicitHeight: implicitWidth
|
||||
radius: Tokens.rounding.full
|
||||
color: Colors.tPalette.m3surface
|
||||
|
||||
MaterialIcon {
|
||||
id: videoIcon
|
||||
anchors.centerIn: parent
|
||||
text: "video_library"
|
||||
fontStyle: Tokens.font.icon.extraLarge
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: cropRectLoader
|
||||
|
||||
active: scaledImg.status === Image.Ready
|
||||
active: scaledImg.status === Image.Ready && !wrapper.isVideo
|
||||
|
||||
sourceComponent: Component {
|
||||
CustomRect {
|
||||
@@ -330,7 +367,6 @@ Item {
|
||||
y = t.y;
|
||||
}
|
||||
|
||||
// Where the rect should be according to saved data (same rules as a restore).
|
||||
function savedTarget() {
|
||||
const data = Wallpapers.getCrop(wrapper.currentScreen.name);
|
||||
const hasCrop = data && (Math.abs(data.x) > 0.001 || Math.abs(data.y) > 0.001 || Math.abs(data.width - 1.0) > 0.001 || Math.abs(data.height - 1.0) > 0.001);
|
||||
@@ -357,8 +393,7 @@ Item {
|
||||
width: Math.round(baseWidth / zoom)
|
||||
|
||||
Behavior on color {
|
||||
CAnim {
|
||||
}
|
||||
CAnim {}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
@@ -434,9 +469,10 @@ Item {
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
cursorShape: pressed ? Qt.ClosedHandCursor : insideCropRect(mouseX, mouseY) ? Qt.OpenHandCursor : Qt.PointingHandCursor
|
||||
cursorShape: wrapper.isVideo ? Qt.ArrowCursor : pressed ? Qt.ClosedHandCursor : insideCropRect(mouseX, mouseY) ? Qt.OpenHandCursor : Qt.PointingHandCursor
|
||||
hoverEnabled: true
|
||||
preventStealing: true
|
||||
enabled: !wrapper.isVideo
|
||||
|
||||
onPositionChanged: mouse => {
|
||||
if (pressed) {
|
||||
|
||||
@@ -6,7 +6,7 @@ import qs.Components
|
||||
import ZShell.Config
|
||||
import qs.Services
|
||||
|
||||
CustomClippingRect {
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property color blobColor: Colors.tPalette.m3surfaceContainerLow
|
||||
@@ -21,7 +21,6 @@ CustomClippingRect {
|
||||
|
||||
implicitHeight: sState.screen.height * 0.7
|
||||
implicitWidth: implicitHeight * ratio
|
||||
radius: Tokens.rounding.largeIncreased + Tokens.padding.small
|
||||
|
||||
Behavior on blobColor {
|
||||
CAnim {}
|
||||
@@ -39,6 +38,7 @@ CustomClippingRect {
|
||||
}
|
||||
|
||||
BlobInvertedRect {
|
||||
id: blobRect
|
||||
anchors.fill: parent
|
||||
borderBottom: Tokens.padding.small
|
||||
borderLeft: navPane.width + navPane.anchors.margins * 2
|
||||
@@ -46,6 +46,8 @@ CustomClippingRect {
|
||||
borderTop: Tokens.padding.small
|
||||
group: blobGroup
|
||||
opacity: root.blobColor.a
|
||||
// outerRadius: 10
|
||||
outerRadius: Tokens.rounding.largeIncreased + Tokens.padding.small
|
||||
radius: Tokens.rounding.largeIncreased
|
||||
}
|
||||
|
||||
@@ -64,6 +66,7 @@ CustomClippingRect {
|
||||
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,7 +38,6 @@ Item {
|
||||
id: container
|
||||
|
||||
anchors.fill: parent
|
||||
layer.enabled: opacity < 1
|
||||
objectName: "PageContainer"
|
||||
|
||||
Component.onCompleted: root.loadPage(root.sState.currentPageIdx)
|
||||
|
||||
@@ -88,6 +88,7 @@ PageBase {
|
||||
id: selectScheme
|
||||
|
||||
acceptLabel: qsTr("Confirm")
|
||||
settingAnchor: "panels-sidebar-llm-scheme"
|
||||
enabled: Object.keys(model).length > 0
|
||||
header: qsTr("Select scheme")
|
||||
first: true
|
||||
|
||||
@@ -50,11 +50,12 @@ PageBase {
|
||||
}
|
||||
|
||||
WallItem {
|
||||
required property FileSystemEntry modelData
|
||||
id: wallItem
|
||||
required property WallpaperEntry modelData
|
||||
|
||||
enabled: modelData
|
||||
opacity: modelData ? 1 : 0
|
||||
source: String(modelData?.path ?? "")
|
||||
source: String(modelData?.thumbnailPath ?? "")
|
||||
|
||||
onClicked: {
|
||||
Wallpapers.setWallpaper(modelData.path);
|
||||
|
||||
@@ -13,6 +13,42 @@ import qs.Services
|
||||
PageBase {
|
||||
id: root
|
||||
|
||||
readonly property list<MenuItem> wallTransitions: [
|
||||
MenuItem {
|
||||
text: "Bowtie"
|
||||
},
|
||||
MenuItem {
|
||||
text: "Fade"
|
||||
},
|
||||
MenuItem {
|
||||
text: "LinearBlur"
|
||||
},
|
||||
MenuItem {
|
||||
text: "WindowSlices"
|
||||
},
|
||||
MenuItem {
|
||||
text: "WipeRight"
|
||||
},
|
||||
MenuItem {
|
||||
text: "WipeDown"
|
||||
},
|
||||
MenuItem {
|
||||
text: "TilesWave"
|
||||
},
|
||||
MenuItem {
|
||||
text: "SplitSlideInHorizontal"
|
||||
},
|
||||
MenuItem {
|
||||
text: "Hexagonalize"
|
||||
},
|
||||
MenuItem {
|
||||
text: "DoomMelt"
|
||||
},
|
||||
MenuItem {
|
||||
text: "Random"
|
||||
}
|
||||
]
|
||||
|
||||
title: qsTr("Wallpaper & style")
|
||||
|
||||
ColumnLayout {
|
||||
@@ -73,6 +109,18 @@ PageBase {
|
||||
onToggled: Config.background.enabled = checked
|
||||
}
|
||||
|
||||
SelectRow {
|
||||
Layout.topMargin: Tokens.spacing.extraSmall / 2 - parent.spacing
|
||||
active: root.wallTransitions[Config.background.wallTransition]
|
||||
menuItems: root.wallTransitions
|
||||
menuOnTop: true
|
||||
settingAnchor: "style-wallpaper-transition"
|
||||
subtext: qsTr("Currently selected: %1").arg(active.text)
|
||||
text: qsTr("Wallpaper transition")
|
||||
|
||||
onSelected: item => Config.background.wallTransition = root.wallTransitions.indexOf(item)
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.topMargin: Tokens.spacing.extraSmall / 2 - parent.spacing
|
||||
checked: Colors.transparency.enabled
|
||||
@@ -199,7 +247,7 @@ PageBase {
|
||||
stepSize: 1
|
||||
subtext: qsTr("How long it takes for the gradual nightlight to finish (s)")
|
||||
text: qsTr("Fade duration")
|
||||
to: 60 * 5
|
||||
to: 60 * 20
|
||||
value: Config.display.nightlight.fadeDuration / 1000
|
||||
|
||||
onMoved: v => Config.display.nightlight.fadeDuration = v * 1000
|
||||
|
||||
@@ -2,8 +2,9 @@ pragma ComponentBehavior: Bound
|
||||
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
import qs.Components
|
||||
import ZShell.Config
|
||||
import ZShell.Components
|
||||
import qs.Components
|
||||
|
||||
Item {
|
||||
id: root
|
||||
@@ -27,21 +28,17 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
CustomClippingRect {
|
||||
anchors.fill: parent
|
||||
Loader {
|
||||
id: content
|
||||
|
||||
Loader {
|
||||
id: content
|
||||
active: root.shouldBeActive || root.visible
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
active: root.shouldBeActive || root.visible
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
sourceComponent: Content {
|
||||
sState.animatingContainer: content.opacity < 1
|
||||
sState.currentPageIdx: ["wallpaper"].indexOf(root.queuedMode)
|
||||
sState.screen: root.screen
|
||||
}
|
||||
sourceComponent: Content {
|
||||
sState.animatingContainer: content.opacity < 1
|
||||
sState.currentPageIdx: ["wallpaper"].indexOf(root.queuedMode)
|
||||
sState.screen: root.screen
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+61
-10
@@ -3,10 +3,11 @@ pragma ComponentBehavior: Bound
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import QtQuick.Layouts
|
||||
import ZShell.Services
|
||||
import ZShell.Components
|
||||
import ZShell.Config
|
||||
import ZShell.Llm
|
||||
import qs.Modules.Notifications.Sidebar.Chat.Content
|
||||
import qs.Modules.Sidebar.Chat.Content
|
||||
import qs.Components
|
||||
import qs.Services
|
||||
|
||||
@@ -90,7 +91,9 @@ 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 {
|
||||
@@ -171,7 +174,7 @@ Item {
|
||||
Layout.alignment: ChatState.isWindow ? Qt.AlignCenter : Qt.AlignLeft | Qt.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
font: Tokens.font.title.medium
|
||||
text: qsTr(root.chatData.title)
|
||||
text: qsTr(root.chatData?.title || ChatState.title)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -436,7 +439,7 @@ Item {
|
||||
width: attachments.cellWidth
|
||||
height: width
|
||||
opacity: modelData ? 1 : 0
|
||||
CustomClippingRect {
|
||||
ClipRect {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Tokens.spacing.extraSmall
|
||||
|
||||
@@ -540,17 +543,17 @@ Item {
|
||||
anchors.margins: Tokens.padding.medium
|
||||
bg.border.color: Colors.palette.m3outlineVariant
|
||||
bg.color: Colors.tPalette.m3surfaceContainerLowest
|
||||
fastGlow: Chat.busy
|
||||
glow.opacity: input.activeFocus && (!Chat.busy || ChatState.isWindow)
|
||||
fastGlow: LlmClient.busy
|
||||
glow.opacity: !ChatState.narrowSidebarExpanded ? (input.activeFocus && (!LlmClient.busy || ChatState.isWindow) ? 1 : 0) : 0
|
||||
font: Tokens.font.body.small
|
||||
implicitHeight: Math.min(root.height / 5, contentHeight + topPadding + bottomPadding)
|
||||
focus: true
|
||||
placeholderText: qsTr("Send a message")
|
||||
sendIcon.font: Tokens.font.icon.medium
|
||||
sendIcon.icon: Chat.busy ? "stop" : "arrow_upward"
|
||||
sendIcon.icon: LlmClient.busy ? "stop" : "arrow_upward"
|
||||
sendIcon.padding: Tokens.padding.extraSmall
|
||||
sendIcon.opacity: text || Chat.busy ? 1.0 : 0.5
|
||||
sendIcon.enabled: text || Chat.busy
|
||||
sendIcon.opacity: text || LlmClient.busy ? 1.0 : 0.5
|
||||
sendIcon.enabled: text || LlmClient.busy
|
||||
|
||||
Component.onCompleted: {
|
||||
root.focusInput();
|
||||
@@ -572,10 +575,58 @@ Item {
|
||||
}
|
||||
}
|
||||
onButtonPressed: {
|
||||
if (Chat.busy)
|
||||
Chat.stop();
|
||||
if (LlmClient.busy)
|
||||
LlmClient.stop();
|
||||
else
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+80
-32
@@ -6,7 +6,7 @@ import qs.Effects
|
||||
import qs.Components
|
||||
import qs.Services
|
||||
|
||||
CustomClippingRect {
|
||||
CustomRect {
|
||||
id: root
|
||||
|
||||
property bool expanded: false
|
||||
@@ -91,6 +91,50 @@ CustomClippingRect {
|
||||
|
||||
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
|
||||
@@ -106,7 +150,7 @@ CustomClippingRect {
|
||||
color: Colors.layer(Colors.palette.m3surfaceContainerHighest, 1)
|
||||
implicitHeight: titleText.implicitHeight + Tokens.padding.extraSmall * 2
|
||||
radius: Tokens.rounding.full
|
||||
implicitWidth: titleText.implicitWidth + Tokens.padding.large * 2
|
||||
implicitWidth: titleText.width + Tokens.padding.large * 2
|
||||
opacity: titleText.readOnly ? 0 : 1
|
||||
|
||||
Behavior on opacity {
|
||||
@@ -122,11 +166,6 @@ CustomClippingRect {
|
||||
anchors.right: parent.right
|
||||
implicitHeight: titleText.implicitHeight + Tokens.padding.small * 2
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: OpacityMask {
|
||||
maskSource: fadeMask
|
||||
}
|
||||
|
||||
TextFieldBase {
|
||||
id: titleText
|
||||
|
||||
@@ -134,10 +173,12 @@ CustomClippingRect {
|
||||
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 {}
|
||||
@@ -146,6 +187,11 @@ CustomClippingRect {
|
||||
Anim {}
|
||||
}
|
||||
|
||||
function cancelTitleEdit(): void {
|
||||
text = root.modelData.title;
|
||||
readOnly = true;
|
||||
}
|
||||
|
||||
onEditingFinished: root.modelData.title = text
|
||||
onReadOnlyChanged: {
|
||||
if (!readOnly) {
|
||||
@@ -156,31 +202,24 @@ CustomClippingRect {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CustomRect {
|
||||
id: fadeMask
|
||||
CustomText {
|
||||
id: staticText
|
||||
|
||||
// anchors.top: titleText.top
|
||||
// anchors.bottom: titleText.bottom
|
||||
// anchors.left: titleText.left
|
||||
// anchors.right: parent.right
|
||||
anchors.fill: titleWrapper
|
||||
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
|
||||
|
||||
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.leftMargin {
|
||||
Anim {}
|
||||
}
|
||||
|
||||
GradientStop {
|
||||
color: Qt.rgba(1, 1, 1, 0)
|
||||
position: 1.0
|
||||
Behavior on anchors.topMargin {
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -259,7 +298,7 @@ CustomClippingRect {
|
||||
font: Tokens.font.body.small
|
||||
maximumLineCount: 1
|
||||
opacity: !root.expanded && !root.slim ? 1 : 0
|
||||
text: root.modelData.messagesModel.lastMessage?.activeGeneration.content.replace(/\s+/g, " ").trim() ?? qsTr("No messages yet")
|
||||
text: root.modelData.messagesModel.lastMessage?.content.replace(/\s+/g, " ").trim() ?? qsTr("No messages yet")
|
||||
|
||||
Behavior on y {
|
||||
Anim {}
|
||||
@@ -284,7 +323,7 @@ CustomClippingRect {
|
||||
color: Colors.palette.m3onSurfaceVariant
|
||||
maximumLineCount: 5
|
||||
opacity: root.expanded ? 1 : 0
|
||||
text: root.modelData.messagesModel.lastMessage?.activeGeneration.content ?? qsTr("No messages yet")
|
||||
text: root.modelData.messagesModel.lastMessage?.content ?? qsTr("No messages yet")
|
||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||
|
||||
Behavior on y {
|
||||
@@ -327,7 +366,13 @@ CustomClippingRect {
|
||||
inactiveColor: Colors.layer(Colors.palette.m3surfaceContainerHighest, 3)
|
||||
inactiveOnColor: Colors.palette.m3onSurfaceVariant
|
||||
|
||||
onClicked: root.remove(root.modelData)
|
||||
onClicked: {
|
||||
if (!titleText.readOnly) {
|
||||
titleText.cancelTitleEdit();
|
||||
return;
|
||||
}
|
||||
root.remove(root.modelData);
|
||||
}
|
||||
}
|
||||
|
||||
IconButton {
|
||||
@@ -370,7 +415,10 @@ CustomClippingRect {
|
||||
}
|
||||
|
||||
StateLayer {
|
||||
onClicked: root.expanded = !root.expanded
|
||||
onClicked: {
|
||||
root.expanded = !root.expanded;
|
||||
titleText.readOnly = true;
|
||||
}
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
+39
-155
@@ -5,6 +5,7 @@ import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import ZShell.Llm
|
||||
import ZShell.Config
|
||||
import qs.Modules.Sidebar.Chat.Content
|
||||
import qs.Services
|
||||
import qs.Components
|
||||
|
||||
@@ -21,8 +22,13 @@ Item {
|
||||
signal loadChatRequest(content: ChatSession, index: int)
|
||||
signal newChatRequest
|
||||
|
||||
onSlimChanged: {
|
||||
if (slim)
|
||||
modelsContainer.open = false;
|
||||
}
|
||||
|
||||
CustomText {
|
||||
id: header
|
||||
id: chatHeader
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.margins: Tokens.padding.extraSmall
|
||||
@@ -41,7 +47,7 @@ Item {
|
||||
anchors.bottom: modelsContainer.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: header.bottom
|
||||
anchors.top: chatHeader.bottom
|
||||
anchors.topMargin: Tokens.spacing.medium
|
||||
clip: true
|
||||
spacing: Tokens.spacing.medium
|
||||
@@ -98,156 +104,24 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
CustomClippingRect {
|
||||
ModelsContainer {
|
||||
id: modelsContainer
|
||||
|
||||
property bool expanded: false
|
||||
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: fabRoot.left
|
||||
anchors.margins: Tokens.padding.small
|
||||
anchors.rightMargin: Tokens.spacing.small
|
||||
|
||||
implicitHeight: expanded ? 400 : fabRoot.implicitHeight
|
||||
radius: modelLayer.pressed ? Tokens.rounding.small : modelName.implicitHeight / 2
|
||||
rootParent: root
|
||||
fabRoot: fabRoot
|
||||
|
||||
function prettyModelName(): string {
|
||||
if (modelsList.count < 1)
|
||||
return qsTr("No models found");
|
||||
opacity: root.slim ? 0 : 1
|
||||
visible: opacity > 0
|
||||
|
||||
const pathList = Chat.model.split("\/");
|
||||
const name = pathList[pathList.length - 1];
|
||||
|
||||
if (!name)
|
||||
return qsTr("No model selected");
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
Behavior on implicitHeight {
|
||||
Anim {}
|
||||
}
|
||||
|
||||
Behavior on radius {
|
||||
Anim {}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: modelName
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
implicitHeight: fabRoot.implicitHeight
|
||||
|
||||
CustomText {
|
||||
id: label
|
||||
text: modelsContainer.prettyModelName()
|
||||
color: Colors.palette.m3onSurfaceVariant
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Tokens.padding.large
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
color: Colors.palette.m3onSurfaceVariant
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Tokens.padding.large
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: modelsContainer.expanded ? "unfold_less" : "unfold_more"
|
||||
animate: true
|
||||
fontStyle: Tokens.font.icon.large
|
||||
visible: modelsList.count > 0
|
||||
}
|
||||
|
||||
StateLayer {
|
||||
id: modelLayer
|
||||
enabled: modelsList.count > 0
|
||||
|
||||
onClicked: {
|
||||
ChatState.fabExpanded = false;
|
||||
modelsContainer.expanded = !modelsContainer.expanded;
|
||||
}
|
||||
radius: modelsContainer.radius
|
||||
}
|
||||
}
|
||||
|
||||
VerticalFadeListView {
|
||||
id: modelsList
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.top: modelName.bottom
|
||||
anchors.margins: Tokens.padding.medium
|
||||
anchors.topMargin: Tokens.spacing.medium
|
||||
model: Chat.availableModels
|
||||
opacity: modelsContainer.expanded ? 1 : 0
|
||||
currentIndex: Chat.availableModels.indexOf(Chat.model)
|
||||
visible: opacity > 0
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {}
|
||||
}
|
||||
|
||||
CustomScrollBar.vertical: CustomScrollBar {
|
||||
flickable: modelsList
|
||||
}
|
||||
|
||||
delegate: CustomRect {
|
||||
id: model
|
||||
|
||||
required property var modelData
|
||||
required property int index
|
||||
readonly property bool selected: model.ListView.view.currentIndex === model.index
|
||||
|
||||
implicitWidth: ListView.view.width
|
||||
implicitHeight: layout.implicitHeight + Tokens.padding.small * 2
|
||||
radius: stateLayer.pressed ? Tokens.rounding.extraSmall : selected ? Tokens.rounding.largeIncreased : Tokens.rounding.medium
|
||||
color: Qt.alpha(Colors.palette.m3tertiaryContainer, selected ? 1 : 0)
|
||||
|
||||
Behavior on radius {
|
||||
Anim {
|
||||
type: Anim.SlowEffects
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: layout
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Tokens.padding.small
|
||||
anchors.leftMargin: Tokens.padding.large
|
||||
anchors.rightMargin: Tokens.padding.large
|
||||
|
||||
CustomText {
|
||||
text: model.modelData
|
||||
color: model.selected ? Colors.palette.m3onTertiaryContainer : Colors.palette.m3onSurface
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
visible: model.selected
|
||||
color: Colors.palette.m3onTertiaryContainer
|
||||
fontStyle: Tokens.font.icon.normal
|
||||
opacity: model.selected ? 1 : 0
|
||||
text: "check"
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
type: Anim.SlowEffects
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StateLayer {
|
||||
id: stateLayer
|
||||
|
||||
onClicked: {
|
||||
Chat.selectModel(model.modelData);
|
||||
}
|
||||
}
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -262,19 +136,11 @@ Item {
|
||||
font.pointSize: Math.round(18 * 1.2)
|
||||
icon: root.slim ? "left_panel_open" : "add"
|
||||
isRound: ChatState.fabExpanded
|
||||
// opacity: root.slim ? 0 : 1
|
||||
// visible: opacity > 0
|
||||
//
|
||||
// Behavior on opacity {
|
||||
// Anim {
|
||||
// type: Anim.DefaultEffects
|
||||
// }
|
||||
// }
|
||||
|
||||
label.transform: Rotation {
|
||||
origin.y: fabRoot.label.height / 2
|
||||
origin.x: fabRoot.label.width / 2
|
||||
angle: ChatState.fabExpanded ? 135 : 0
|
||||
angle: ChatState.fabExpanded && (ChatState.narrowSidebarExpanded || !ChatState.isWide || !ChatState.isWindow) ? 135 : 0
|
||||
|
||||
Behavior on angle {
|
||||
Anim {}
|
||||
@@ -282,12 +148,12 @@ Item {
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
if (root.slim && !ChatState.narrowSidebarExpanded) {
|
||||
if ((root.slim && !ChatState.narrowSidebarExpanded) || ChatState.shrinkSidebarOverride) {
|
||||
root.requestExpand();
|
||||
ChatState.shrinkSidebarOverride = false;
|
||||
return;
|
||||
}
|
||||
|
||||
modelsContainer.expanded = false;
|
||||
ChatState.fabExpanded = !ChatState.fabExpanded;
|
||||
}
|
||||
|
||||
@@ -318,9 +184,13 @@ Item {
|
||||
name: "pop out in window"
|
||||
icon: "open_in_new"
|
||||
}
|
||||
ListElement {
|
||||
name: "shrink sidebar"
|
||||
icon: "left_panel_close"
|
||||
}
|
||||
}
|
||||
|
||||
CustomClippingRect {
|
||||
CustomRect {
|
||||
id: fabMenuItem
|
||||
|
||||
required property var modelData
|
||||
@@ -331,7 +201,19 @@ Item {
|
||||
|
||||
radius: sLayer.pressed ? Tokens.rounding.small : implicitHeight / 2
|
||||
color: Colors.palette.m3primaryContainer
|
||||
visible: !(modelData.name === "pop out in window" && ChatState.isWindow)
|
||||
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;
|
||||
}
|
||||
clip: true
|
||||
|
||||
Behavior on radius {
|
||||
Anim {
|
||||
@@ -431,6 +313,8 @@ Item {
|
||||
root.newChatRequest();
|
||||
else if (name === "pop out in window")
|
||||
Detach.create();
|
||||
else if (name === "shrink sidebar")
|
||||
ChatState.shrinkSidebarOverride = true;
|
||||
|
||||
ChatState.fabExpanded = false;
|
||||
}
|
||||
+27
-17
@@ -6,7 +6,7 @@ import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import qs.Helpers
|
||||
import qs.Modules.Notifications.Sidebar
|
||||
import qs.Modules.Sidebar
|
||||
import qs.Components
|
||||
import qs.Services
|
||||
|
||||
@@ -17,15 +17,26 @@ Item {
|
||||
anchors.margins: Tokens.padding.small
|
||||
|
||||
Component.onCompleted: {
|
||||
if (ChatState.firstOpen) {
|
||||
ChatState.openDraft();
|
||||
stack.push(chatList);
|
||||
stack.push(chatContent);
|
||||
ChatState.firstOpen = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (ChatState.inChat) {
|
||||
stack.push(chatList);
|
||||
stack.push(chatContent, {
|
||||
"chatData": ChatState.chatSession
|
||||
});
|
||||
stack.push(chatContent);
|
||||
} else
|
||||
stack.push(chatList);
|
||||
}
|
||||
|
||||
function openChat(): void {
|
||||
if (stack.depth < 2)
|
||||
stack.push(chatContent);
|
||||
}
|
||||
|
||||
StackView {
|
||||
id: stack
|
||||
anchors.fill: parent
|
||||
@@ -35,27 +46,23 @@ Item {
|
||||
id: chatList
|
||||
|
||||
ChatList {
|
||||
model: ScriptModel {
|
||||
values: Chat.chats.values
|
||||
}
|
||||
model: LlmClient.chats
|
||||
|
||||
onDeleteChatRequest: chat => {
|
||||
Chat.chats.remove(chat);
|
||||
const wasCurrent = chat === ChatState.chatSession;
|
||||
LlmClient.chats.remove(chat);
|
||||
if (wasCurrent)
|
||||
ChatState.openDraft();
|
||||
}
|
||||
onLoadChatRequest: (chat, index) => {
|
||||
stack.push(chatContent, {
|
||||
"chatData": chat
|
||||
});
|
||||
ChatState.inChat = true;
|
||||
ChatState.chatSession = chat;
|
||||
openChat();
|
||||
ChatState.inChat = true;
|
||||
}
|
||||
onNewChatRequest: {
|
||||
const data = Chat.chats.insert();
|
||||
stack.push(chatContent, {
|
||||
"chatData": data
|
||||
});
|
||||
ChatState.openDraft();
|
||||
openChat();
|
||||
ChatState.inChat = true;
|
||||
ChatState.chatSession = data;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,9 +71,12 @@ Item {
|
||||
id: chatContent
|
||||
|
||||
ChatContent {
|
||||
chatData: ChatState.chatSession
|
||||
|
||||
onRequestClose: {
|
||||
stack.pop();
|
||||
ChatState.inChat = false;
|
||||
ChatState.openDraft();
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
-3
@@ -8,9 +8,10 @@ import qs.Services
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property ChatSession chatSession
|
||||
readonly property string title: chatSession ? chatSession.title : "Chat"
|
||||
property ChatSession chatSession: LlmClient.chats.draft()
|
||||
readonly property string title: chatSession?.title || "Chat"
|
||||
property int currentIdx: -1
|
||||
property bool firstOpen: true
|
||||
property bool inChat: false
|
||||
property bool fabExpanded: false
|
||||
property bool isWindow: false
|
||||
@@ -18,10 +19,18 @@ Singleton {
|
||||
property bool settledSlim: false
|
||||
property bool settledWide: false
|
||||
property bool narrowSidebarExpanded: false
|
||||
property bool windowIsWide: false
|
||||
property bool shrinkSidebarOverride: false
|
||||
property ShellScreen screen
|
||||
property bool sidebarVisible: ShellState.forActive().sidebar
|
||||
|
||||
function openDraft(): void {
|
||||
root.chatSession = LlmClient.chats.draft();
|
||||
root.currentIdx = -1;
|
||||
}
|
||||
|
||||
onIsWideChanged: if (isWide)
|
||||
narrowSidebarExpanded = false
|
||||
|
||||
onSidebarVisibleChanged: if (!sidebarVisible)
|
||||
fabExpanded = false
|
||||
}
|
||||
+3
-4
@@ -9,12 +9,11 @@ import qs.Services
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property ChatGeneration current
|
||||
required property TextEdit edit
|
||||
required property bool hovered
|
||||
required property bool isUser
|
||||
required property ChatMessage message
|
||||
required property LlmSegment segment
|
||||
required property bool running
|
||||
|
||||
implicitHeight: editButton.implicitHeight
|
||||
implicitWidth: generationTools.implicitWidth + editTools.implicitWidth + Tokens.spacing.small
|
||||
@@ -63,7 +62,7 @@ Item {
|
||||
id: editTools
|
||||
|
||||
anchors.right: parent.right
|
||||
opacity: !root.current.streaming && root.hovered ? 1 : 0
|
||||
opacity: !root.running && root.hovered ? 1 : 0
|
||||
spacing: Tokens.spacing.small
|
||||
visible: opacity > 0
|
||||
|
||||
@@ -99,7 +98,7 @@ Item {
|
||||
Anim {}
|
||||
}
|
||||
|
||||
onClicked: Quickshell.clipboardText = root.current.content
|
||||
onClicked: Quickshell.clipboardText = root.message.content
|
||||
}
|
||||
|
||||
IconButton {
|
||||
@@ -0,0 +1,408 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import ZShell.Config
|
||||
import ZShell.Components
|
||||
import ZShell.Llm
|
||||
import ZShell.Blobs
|
||||
import qs.Components
|
||||
import qs.Services
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property Item rootParent
|
||||
required property ChatGeneration current
|
||||
|
||||
implicitHeight: approvalLayout.implicitHeight + approvalLayout.anchors.margins * 2
|
||||
|
||||
CustomRect {
|
||||
id: approvalBg
|
||||
|
||||
anchors.fill: parent
|
||||
radius: Tokens.rounding.medium
|
||||
color: Colors.palette.m3surface
|
||||
border.width: 1
|
||||
border.color: Colors.palette.m3outlineVariant
|
||||
|
||||
ColumnLayout {
|
||||
id: approvalLayout
|
||||
anchors.fill: parent
|
||||
anchors.margins: Tokens.padding.medium
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
CustomText {
|
||||
id: requestHeader
|
||||
text: qsTr("Tool call request")
|
||||
font: Tokens.font.headline.medium
|
||||
color: Colors.palette.m3onSurface
|
||||
}
|
||||
|
||||
Item {
|
||||
id: toolsBg
|
||||
|
||||
property bool open: false
|
||||
property int maxHeight: root.rootParent.height - Tokens.padding.medium * 2
|
||||
property int openHeight: Math.min(toolsContent.item?.implicitHeight, maxHeight)
|
||||
property int openWidth: root.rootParent.width - Tokens.padding.medium * 2
|
||||
property real openHeightProg: openHeight / maxHeight
|
||||
property int collapsedY: 0
|
||||
|
||||
implicitHeight: toolList.implicitHeight + toolList.anchors.margins * 2
|
||||
z: open || dialogTransition.running ? 2 : 0
|
||||
Layout.fillWidth: true
|
||||
|
||||
function reparentWrapper(): void {
|
||||
const newParent = open ? root.rootParent : toolsBg;
|
||||
const c = toolsWrapper.parent.mapToItem(newParent, toolsWrapper.x + toolsWrapper.width / 2, toolsWrapper.y + toolsWrapper.height / 2);
|
||||
toolsBg.collapsedY = toolsWrapper.y;
|
||||
toolsWrapper.parent = newParent;
|
||||
toolsWrapper.x = c.x - toolsWrapper.width / 2;
|
||||
toolsWrapper.y = c.y - toolsWrapper.height / 2;
|
||||
}
|
||||
|
||||
onOpenChanged: {
|
||||
if (open)
|
||||
root.rootParent.interactive = false;
|
||||
else
|
||||
root.rootParent.interactive = true;
|
||||
}
|
||||
|
||||
BlobGroup {
|
||||
id: blobGroup
|
||||
|
||||
color: toolsBg.open ? Colors.palette.m3surfaceContainerHighest : Colors.tPalette.m3primaryContainer
|
||||
|
||||
Behavior on color {
|
||||
CAnim {}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: backdrop
|
||||
|
||||
anchors.fill: parent
|
||||
enabled: false
|
||||
hoverEnabled: enabled
|
||||
preventStealing: true
|
||||
parent: toolsBg.open ? root.rootParent : toolsBg
|
||||
|
||||
onClicked: toolsBg.open = false
|
||||
}
|
||||
|
||||
Item {
|
||||
id: toolsWrapper
|
||||
|
||||
width: toolsBg.width
|
||||
height: toolBox.implicitHeight
|
||||
rotation: parent === root.rootParent ? 180 : 0
|
||||
z: 1
|
||||
|
||||
states: State {
|
||||
name: "open"
|
||||
when: toolsBg.open
|
||||
|
||||
PropertyChanges {
|
||||
backdrop.enabled: true
|
||||
toolsBackground.bottomLeftRadius: Tokens.rounding.largeIncreased
|
||||
toolsBackground.bottomRightRadius: Tokens.rounding.largeIncreased
|
||||
toolsBackground.topRightRadius: Tokens.rounding.largeIncreased
|
||||
toolsBackground.topLeftRadius: Tokens.rounding.largeIncreased
|
||||
toolsContent.opacity: 1
|
||||
toolsWrapper.height: toolsBg.openHeight
|
||||
toolsWrapper.width: toolsBg.openWidth
|
||||
toolsWrapper.x: (root.rootParent.width - toolsBg.openWidth) / 2
|
||||
toolsWrapper.y: (confirmationRow.implicitHeight + Tokens.spacing.small + Tokens.padding.medium) * (1 - toolsBg.openHeightProg) + Tokens.padding.medium
|
||||
elevation.opacity: 1
|
||||
toolBox.opacity: 0
|
||||
}
|
||||
}
|
||||
transitions: Transition {
|
||||
id: dialogTransition
|
||||
|
||||
SequentialAnimation {
|
||||
ScriptAction {
|
||||
script: toolsBg.reparentWrapper()
|
||||
}
|
||||
|
||||
Anim {
|
||||
properties: "x,y"
|
||||
}
|
||||
}
|
||||
|
||||
PropertyAction {
|
||||
property: "enabled"
|
||||
}
|
||||
|
||||
Anim {
|
||||
properties: "opacity,topLeftRadius,topRightRadius,bottomLeftRadius,bottomRightRadius"
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
|
||||
Anim {
|
||||
properties: "width,height"
|
||||
}
|
||||
}
|
||||
|
||||
Elevation {
|
||||
id: elevation
|
||||
|
||||
anchors.fill: parent
|
||||
bottomLeftRadius: toolsBackground.bottomLeftRadius
|
||||
bottomRightRadius: toolsBackground.bottomRightRadius
|
||||
level: 4
|
||||
opacity: 0
|
||||
radius: toolsBackground.radius
|
||||
|
||||
transform: Matrix4x4 {
|
||||
matrix: toolsBackground.deformMatrix
|
||||
}
|
||||
}
|
||||
|
||||
BlobRect {
|
||||
id: toolsBackground
|
||||
|
||||
anchors.fill: parent
|
||||
bottomLeftRadius: toolsBg.open ? Tokens.rounding.largeIncreased : Tokens.rounding.small
|
||||
bottomRightRadius: toolsBg.open ? Tokens.rounding.largeIncreased : Tokens.rounding.small
|
||||
topRightRadius: toolsBg.open ? Tokens.rounding.largeIncreased : Tokens.rounding.small
|
||||
topLeftRadius: toolsBg.open ? Tokens.rounding.largeIncreased : Tokens.rounding.small
|
||||
deformScale: 0
|
||||
group: blobGroup
|
||||
opacity: blobGroup.color.a * (root.enabled ? 1 : 0.5)
|
||||
}
|
||||
|
||||
CustomRect {
|
||||
id: toolBox
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: Math.min(implicitHeight, parent.height)
|
||||
implicitHeight: toolList.implicitHeight + Tokens.padding.medium * 2
|
||||
radius: Tokens.rounding.small
|
||||
|
||||
StateLayer {
|
||||
onClicked: {
|
||||
toolsBg.open = true;
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: toolList
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.margins: Tokens.padding.medium
|
||||
|
||||
Repeater {
|
||||
id: repeater
|
||||
|
||||
model: root.current.pendingToolCalls?.slice(0, 4) ?? []
|
||||
|
||||
delegate: RowLayout {
|
||||
id: toolText
|
||||
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
spacing: Tokens.spacing.medium
|
||||
|
||||
function toolTarget(seg: var): string {
|
||||
var a = null;
|
||||
try {
|
||||
a = JSON.parse(seg.arguments);
|
||||
} catch (e) {}
|
||||
if (a && typeof a === "object") {
|
||||
const v = a.url ?? a.path;
|
||||
if (v !== undefined && v !== null)
|
||||
return String(v);
|
||||
}
|
||||
return String(seg.arguments);
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
color: Colors.palette.m3onPrimaryContainer
|
||||
text: toolText.modelData.name === "webfetch" ? "web" : "text_snippet"
|
||||
fill: 1
|
||||
}
|
||||
|
||||
CustomText {
|
||||
color: Colors.palette.m3onPrimaryContainer
|
||||
text: qsTr("%1").arg(toolText.modelData.name)
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
color: Colors.palette.m3onPrimaryContainer
|
||||
text: "arrow_right_alt"
|
||||
}
|
||||
|
||||
CustomText {
|
||||
color: Colors.palette.m3onPrimaryContainer
|
||||
text: qsTr("%1").arg(toolText.toolTarget(toolText.modelData))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CustomText {
|
||||
color: Colors.palette.m3onPrimaryContainer
|
||||
text: qsTr("And %1 more").arg(root.current.pendingToolCalls.slice(4).length)
|
||||
visible: repeater.count < root.current.pendingToolCalls.length
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: toolsContent
|
||||
|
||||
active: opacity > 0
|
||||
anchors.fill: parent
|
||||
asynchronous: false
|
||||
opacity: 0
|
||||
|
||||
sourceComponent: MouseArea {
|
||||
onWheel: event => event.accepted = true
|
||||
preventStealing: true
|
||||
implicitHeight: innerLayout.contentHeight + innerLayout.anchors.margins * 2
|
||||
|
||||
VerticalFadeListView {
|
||||
id: innerLayout
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Tokens.padding.small
|
||||
cacheBuffer: height * 10
|
||||
fadeAmount: 0.1
|
||||
fadeEnabled: interactive
|
||||
spacing: 0
|
||||
clip: true
|
||||
interactive: contentHeight > toolsBg.maxHeight
|
||||
|
||||
model: root.current.pendingToolCalls ?? []
|
||||
|
||||
delegate: Item {
|
||||
id: toolCall
|
||||
|
||||
required property var modelData
|
||||
property bool expanded: false
|
||||
|
||||
implicitWidth: CustomListView.view.width
|
||||
implicitHeight: collapsedRow.implicitHeight + collapsedRow.anchors.margins + (expanded ? expandedTool.implicitHeight + expandedTool.anchors.margins + expandedTool.anchors.topMargin : collapsedRow.anchors.margins)
|
||||
|
||||
function toolTarget(seg: var): string {
|
||||
var a = null;
|
||||
try {
|
||||
a = JSON.parse(seg.arguments);
|
||||
} catch (e) {}
|
||||
if (a && typeof a === "object") {
|
||||
const v = a.url ?? a.path;
|
||||
if (v !== undefined && v !== null)
|
||||
return String(v);
|
||||
}
|
||||
return String(seg.arguments);
|
||||
}
|
||||
|
||||
Behavior on implicitHeight {
|
||||
Anim {}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: collapsedRow
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: Tokens.padding.medium
|
||||
|
||||
IconButton {
|
||||
id: toolExpandBtn
|
||||
|
||||
font: Tokens.font.icon.medium
|
||||
icon: "keyboard_arrow_down"
|
||||
inactiveOnColor: hovered ? Colors.palette.m3onSurface : Colors.palette.m3outline
|
||||
rotation: toolCall.expanded ? 180 : 0
|
||||
type: IconButton.Text
|
||||
|
||||
Behavior on rotation {
|
||||
Anim {}
|
||||
}
|
||||
|
||||
onClicked: toolCall.expanded = !toolCall.expanded
|
||||
}
|
||||
|
||||
CustomText {
|
||||
id: preview
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: toolCall.modelData.name
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: expandedTool
|
||||
|
||||
anchors.top: collapsedRow.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.margins: Tokens.padding.medium
|
||||
anchors.topMargin: Tokens.spacing.medium
|
||||
|
||||
implicitHeight: fullToolCall.implicitHeight + fullToolCall.anchors.margins * 2
|
||||
clip: true
|
||||
|
||||
CustomRect {
|
||||
id: bg
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
radius: Tokens.rounding.medium
|
||||
color: Colors.palette.m3surfaceContainerLow
|
||||
}
|
||||
|
||||
CustomText {
|
||||
id: fullToolCall
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Tokens.padding.medium
|
||||
|
||||
text: toolCall.toolTarget(toolCall.modelData)
|
||||
wrapMode: CustomText.WrapAtWordBoundaryOrAnywhere
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ButtonRow {
|
||||
id: confirmationRow
|
||||
Layout.alignment: Qt.AlignRight
|
||||
spacing: Tokens.spacing.medium
|
||||
|
||||
IconTextButton {
|
||||
icon: "check_circle"
|
||||
text: qsTr("Allow")
|
||||
horizontalPadding: Tokens.padding.large
|
||||
verticalPadding: Tokens.padding.small
|
||||
isRound: true
|
||||
shapeMorph: true
|
||||
onClicked: root.current.approveTools()
|
||||
}
|
||||
|
||||
IconTextButton {
|
||||
icon: "cancel"
|
||||
text: qsTr("Deny")
|
||||
isRound: true
|
||||
shapeMorph: true
|
||||
inactiveColor: Colors.palette.m3error
|
||||
inactiveOnColor: Colors.palette.m3onError
|
||||
horizontalPadding: Tokens.padding.large
|
||||
verticalPadding: Tokens.padding.small
|
||||
onClicked: root.current.denyTools()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -3,7 +3,7 @@ import ZShell.Llm
|
||||
import ZShell.Config
|
||||
import qs.Components
|
||||
import qs.Services
|
||||
import qs.Modules.Notifications.Sidebar.Chat
|
||||
import qs.Modules.Sidebar.Chat
|
||||
|
||||
Item {
|
||||
id: root
|
||||
@@ -59,7 +59,6 @@ Item {
|
||||
id: container
|
||||
|
||||
anchors.fill: parent
|
||||
layer.enabled: opacity < 1
|
||||
objectName: "ConversationContainer"
|
||||
|
||||
Component.onCompleted: {
|
||||
+8
-8
@@ -220,9 +220,8 @@ CustomRect {
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
color: root.codeHeaderColor
|
||||
font: Tokens.font.label.small
|
||||
font: Tokens.font.label.builders.small.capitalization(Font.Capitalize).build()
|
||||
text: CodeIcons.name(root.language)
|
||||
visible: root.language.length > 0
|
||||
}
|
||||
|
||||
Item {
|
||||
@@ -252,7 +251,7 @@ CustomRect {
|
||||
}
|
||||
}
|
||||
|
||||
CustomClippingRect {
|
||||
CustomRect {
|
||||
id: codeRect
|
||||
|
||||
anchors.left: parent.left
|
||||
@@ -265,6 +264,7 @@ 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,6 +272,9 @@ CustomRect {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Tokens.padding.small
|
||||
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
contentWidth: codeText.implicitWidth
|
||||
|
||||
CustomScrollBar.horizontal: CustomScrollBar {
|
||||
flickable: codeFlick
|
||||
parent: codeFlick.parent
|
||||
@@ -280,16 +283,13 @@ CustomRect {
|
||||
anchors.right: parent.right
|
||||
}
|
||||
|
||||
TextAreaBase.flickable: TextAreaBase {
|
||||
CustomText {
|
||||
id: codeText
|
||||
|
||||
color: CodeColors.active.normal
|
||||
layer.enabled: true
|
||||
clip: false
|
||||
font: Tokens.font.mono.small
|
||||
textFormat: root.colored ? Text.RichText : Text.PlainText
|
||||
textFormat: Text.RichText
|
||||
text: root.colored ? root.html : root.plainForDisplay(root.code)
|
||||
readOnly: true
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user