Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9c29e3201d
|
||
|
|
c215b52903
|
||
|
|
39f99bb336
|
||
|
|
068319a4cb
|
||
|
|
bdb95af7a1
|
@@ -18,7 +18,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Docker CLI
|
||||
run: apk add --no-cache docker-cli
|
||||
run: apk add --no-cache docker-cli docker-cli-buildx
|
||||
|
||||
- name: Login to registry
|
||||
run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login git.aramjonghu.dev --username aramjonghu --password-stdin
|
||||
|
||||
@@ -20,28 +20,10 @@ jobs:
|
||||
-exec clang-format -i --style=file {} +
|
||||
git diff --exit-code && echo "clang-format: passed"
|
||||
|
||||
build:
|
||||
build & lint:
|
||||
runs-on: alpine
|
||||
container:
|
||||
image: git.aramjonghu.dev/aramjonghu/zshell-ci:latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Configure
|
||||
run: cmake -B build -G Ninja -DENABLE_MODULES=plugin -DCMAKE_BUILD_TYPE=Release
|
||||
|
||||
- name: Build
|
||||
run: ninja -C build
|
||||
|
||||
clang-tidy:
|
||||
runs-on: alpine
|
||||
container:
|
||||
image: git.aramjonghu.dev/aramjonghu/zshell-ci:latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
@@ -4,7 +4,7 @@ on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
fmt:
|
||||
fmt & lint:
|
||||
runs-on: alpine
|
||||
container: node:26-alpine
|
||||
|
||||
@@ -24,20 +24,6 @@ jobs:
|
||||
else
|
||||
echo "No JS/TS files found"
|
||||
fi
|
||||
|
||||
lint:
|
||||
runs-on: alpine
|
||||
container: node:26-alpine
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install tools
|
||||
run: |
|
||||
apk add --no-cache \
|
||||
git
|
||||
|
||||
- name: ESLint
|
||||
run: |
|
||||
if [ -n "$(find . \( -iname "*.js" -o -iname "*.jsx" -o -iname "*.ts" -o -iname "*.tsx" -o -iname "*.mjs" -o -iname "*.cjs" \) -print -quit)" ]; then
|
||||
|
||||
@@ -7,7 +7,7 @@ on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
static:
|
||||
fmt & lint:
|
||||
runs-on: debian
|
||||
container: node:26-trixie-slim
|
||||
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
. .venv/bin/activate
|
||||
ruff check .
|
||||
|
||||
verify:
|
||||
test & nuitka build:
|
||||
if: always()
|
||||
runs-on: debian
|
||||
container: node:26-trixie-slim
|
||||
@@ -64,4 +64,4 @@ jobs:
|
||||
- name: Nuitka module check
|
||||
run: |
|
||||
. .venv/bin/activate
|
||||
nuitka --module --include-package=zshell cli/src/zshell/
|
||||
nuitka --module --include-package=zshell cli/src/zshell/
|
||||
|
||||
+21
-86
@@ -3,8 +3,9 @@ name: Rust
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
# jobs can be separated if we integrate tests
|
||||
jobs:
|
||||
build:
|
||||
build, clippy & fmt:
|
||||
runs-on: alpine
|
||||
container: node:26-alpine
|
||||
env:
|
||||
@@ -34,7 +35,9 @@ jobs:
|
||||
apk add --no-cache \
|
||||
git \
|
||||
cargo \
|
||||
rust
|
||||
rust \
|
||||
rust-clippy \
|
||||
rustfmt
|
||||
|
||||
- name: Cargo check
|
||||
run: |
|
||||
@@ -50,90 +53,6 @@ jobs:
|
||||
else
|
||||
echo "No Rust project found"
|
||||
fi
|
||||
|
||||
fmt:
|
||||
runs-on: alpine
|
||||
container: node:26-alpine
|
||||
env:
|
||||
CARGO_HOME: ${{ github.workspace }}/.cargo
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Cache cargo packages
|
||||
uses: actions/cache@v4
|
||||
env:
|
||||
cache-name: cache-cargo-packages
|
||||
with:
|
||||
path: |
|
||||
.cargo/registry
|
||||
.cargo/git
|
||||
target
|
||||
key: rust-fmt-${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
rust-${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
rust-${{ runner.os }}-build-
|
||||
rust-
|
||||
|
||||
- name: Install tools
|
||||
run: |
|
||||
apk add --no-cache \
|
||||
git \
|
||||
cargo \
|
||||
rust \
|
||||
rustfmt
|
||||
|
||||
- name: Format check
|
||||
run: |
|
||||
if [ -n "$(find . -name "Cargo.toml" -print -quit)" ]; then
|
||||
status=0
|
||||
for manifest in $(find . -name "Cargo.toml"); do
|
||||
cargo fmt --manifest-path "$manifest" --check && \
|
||||
echo "$manifest: formatting OK" || \
|
||||
{ echo "$manifest: needs formatting"; status=1; }
|
||||
done
|
||||
exit $status
|
||||
elif [ -n "$(find . -name "*.rs" -print -quit)" ]; then
|
||||
echo "Rust files found but no Cargo.toml"
|
||||
exit 1
|
||||
else
|
||||
echo "No Rust project found"
|
||||
fi
|
||||
|
||||
clippy:
|
||||
runs-on: alpine
|
||||
container: node:26-alpine
|
||||
env:
|
||||
CARGO_HOME: ${{ github.workspace }}/.cargo
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Cache cargo packages
|
||||
uses: actions/cache@v4
|
||||
env:
|
||||
cache-name: cache-cargo-packages
|
||||
with:
|
||||
path: |
|
||||
.cargo/registry
|
||||
.cargo/git
|
||||
target
|
||||
key: rust-clippy-${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
rust-${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
rust-${{ runner.os }}-build-
|
||||
rust-
|
||||
|
||||
- name: Install tools
|
||||
run: |
|
||||
apk add --no-cache \
|
||||
git \
|
||||
cargo \
|
||||
rust \
|
||||
rust-clippy
|
||||
|
||||
- name: Clippy
|
||||
run: |
|
||||
if [ -n "$(find . -name "Cargo.toml" -print -quit)" ]; then
|
||||
@@ -150,3 +69,19 @@ jobs:
|
||||
else
|
||||
echo "No Rust project found"
|
||||
fi
|
||||
- name: Format check
|
||||
run: |
|
||||
if [ -n "$(find . -name "Cargo.toml" -print -quit)" ]; then
|
||||
status=0
|
||||
for manifest in $(find . -name "Cargo.toml"); do
|
||||
cargo fmt --manifest-path "$manifest" --check && \
|
||||
echo "$manifest: formatting OK" || \
|
||||
{ echo "$manifest: needs formatting"; status=1; }
|
||||
done
|
||||
exit $status
|
||||
elif [ -n "$(find . -name "*.rs" -print -quit)" ]; then
|
||||
echo "Rust files found but no Cargo.toml"
|
||||
exit 1
|
||||
else
|
||||
echo "No Rust project found"
|
||||
fi
|
||||
|
||||
@@ -16,7 +16,7 @@ CustomRect {
|
||||
property color activeOnColor
|
||||
property bool checked
|
||||
property real checkedRadius: Tokens.rounding.medium
|
||||
property real defaultRadius: Math.min(width, height) / 2
|
||||
property real defaultRadius: Tokens.rounding.large
|
||||
property color disabledColor: Qt.alpha(Colors.palette.m3onSurface, 0.1)
|
||||
property color disabledOnColor: Qt.alpha(Colors.palette.m3onSurface, 0.38)
|
||||
property bool fillWidth
|
||||
|
||||
@@ -34,7 +34,6 @@ BusyIndicator {
|
||||
anchors.fill: parent
|
||||
bgColor: root.bgColor
|
||||
fgColor: root.fgColor
|
||||
hasEndIndicator: false
|
||||
padding: root.padding
|
||||
rotation: manager.rotation
|
||||
startAngle: manager.startFraction * 360
|
||||
@@ -54,7 +53,6 @@ BusyIndicator {
|
||||
Anim {
|
||||
duration: manager.completeEndDuration * Tokens.anim.durations.scale
|
||||
properties: "opacity,internalStrokeWidth"
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -150,7 +150,6 @@ TextFieldBase {
|
||||
|
||||
sourceComponent: MaterialIcon {
|
||||
color: Colors.palette.m3onSurfaceVariant
|
||||
font.pointSize: Tokens.font.size.larger
|
||||
text: root.leadingIcon
|
||||
fontStyle: Tokens.font.icon.builders.medium.scale(0.9).build()
|
||||
}
|
||||
|
||||
@@ -2,57 +2,55 @@ import QtQuick
|
||||
import ZShell.Config
|
||||
import qs.Services
|
||||
|
||||
Item {
|
||||
CustomClippingRect {
|
||||
id: track
|
||||
|
||||
required property var scrollBar
|
||||
required property var mouseArea
|
||||
|
||||
ClipRect {
|
||||
radius: Tokens.rounding.full
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
implicitHeight: handle.implicitHeight
|
||||
radius: Tokens.rounding.full
|
||||
|
||||
CustomRect {
|
||||
id: handle
|
||||
|
||||
anchors.bottom: parent.bottom
|
||||
implicitHeight: handle.implicitHeight
|
||||
|
||||
CustomRect {
|
||||
id: handle
|
||||
|
||||
anchors.bottom: parent.bottom
|
||||
color: Colors.palette.m3secondary
|
||||
implicitWidth: track.scrollBar.width * track.scrollBar.effectiveSize
|
||||
implicitHeight: track.mouseArea.pressed || track.mouseArea.containsMouse ? Tokens.padding.extraSmall * 2 : Tokens.padding.extraSmall
|
||||
opacity: {
|
||||
if (!track.scrollBar.enabled)
|
||||
return 0;
|
||||
if (track.scrollBar.size === 1)
|
||||
return 0;
|
||||
if (track.mouseArea.pressed)
|
||||
return 1;
|
||||
if (track.mouseArea.containsMouse)
|
||||
return 0.8;
|
||||
if (track.scrollBar.policy === CustomScrollBar.AlwaysOn || track.scrollBar.shouldBeActive)
|
||||
return 0.6;
|
||||
color: Colors.palette.m3secondary
|
||||
implicitWidth: track.scrollBar.width * track.scrollBar.effectiveSize
|
||||
implicitHeight: track.mouseArea.pressed || track.mouseArea.containsMouse ? Tokens.padding.extraSmall * 2 : Tokens.padding.extraSmall
|
||||
opacity: {
|
||||
if (!track.scrollBar.enabled)
|
||||
return 0;
|
||||
}
|
||||
radius: Tokens.rounding.full
|
||||
x: track.scrollBar.reversed ? track.scrollBar.width * (1 + track.scrollBar.nonAnimY) * track.scrollBar.travelScale : track.scrollBar.width * track.scrollBar.nonAnimY * track.scrollBar.travelScale
|
||||
if (track.scrollBar.size === 1)
|
||||
return 0;
|
||||
if (track.mouseArea.pressed)
|
||||
return 1;
|
||||
if (track.mouseArea.containsMouse)
|
||||
return 0.8;
|
||||
if (track.scrollBar.policy === CustomScrollBar.AlwaysOn || track.scrollBar.shouldBeActive)
|
||||
return 0.6;
|
||||
return 0;
|
||||
}
|
||||
radius: Tokens.rounding.full
|
||||
x: track.scrollBar.reversed ? track.scrollBar.width * (1 + track.scrollBar.nonAnimY) * track.scrollBar.travelScale : track.scrollBar.width * track.scrollBar.nonAnimY * track.scrollBar.travelScale
|
||||
|
||||
Behavior on implicitHeight {
|
||||
Anim {}
|
||||
}
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
Behavior on implicitHeight {
|
||||
Anim {}
|
||||
}
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
acceptedButtons: Qt.NoButton
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
hoverEnabled: true
|
||||
}
|
||||
MouseArea {
|
||||
acceptedButtons: Qt.NoButton
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
hoverEnabled: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ TextField {
|
||||
implicitHeight: contentHeight + topPadding + bottomPadding
|
||||
implicitWidth: contentWidth + leftPadding + rightPadding
|
||||
placeholderTextColor: Colors.palette.m3onSurfaceVariant // No anim cause placeholder is custom
|
||||
renderType: TextField.QtRendering
|
||||
renderType: echoMode === TextField.Password ? TextField.QtRendering : TextField.QtRendering
|
||||
selectedTextColor: color
|
||||
selectionColor: Qt.alpha(Colors.palette.m3primary, 0.4)
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
|
||||
@@ -30,7 +30,7 @@ CustomFlickable {
|
||||
type: Anim.SlowEffects
|
||||
}
|
||||
}
|
||||
layer.textureSize: Qt.size(width * dpr, height * dpr)
|
||||
// layer.textureSize: Qt.size(width * dpr, height * dpr)
|
||||
layer.effect: Mask {
|
||||
maskSource: mask
|
||||
|
||||
|
||||
@@ -2,57 +2,55 @@ import QtQuick
|
||||
import ZShell.Config
|
||||
import qs.Services
|
||||
|
||||
Item {
|
||||
CustomClippingRect {
|
||||
id: track
|
||||
|
||||
required property var scrollBar
|
||||
required property var mouseArea
|
||||
|
||||
ClipRect {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
implicitWidth: handle.implicitWidth
|
||||
radius: Tokens.rounding.full
|
||||
|
||||
CustomRect {
|
||||
id: handle
|
||||
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
radius: Tokens.rounding.full
|
||||
implicitWidth: handle.implicitWidth
|
||||
|
||||
CustomRect {
|
||||
id: handle
|
||||
|
||||
anchors.right: parent.right
|
||||
color: Colors.palette.m3secondary
|
||||
implicitHeight: track.scrollBar.height * track.scrollBar.effectiveSize
|
||||
implicitWidth: track.mouseArea.pressed || track.mouseArea.containsMouse ? Tokens.padding.extraSmall * 2 : Tokens.padding.extraSmall
|
||||
opacity: {
|
||||
if (!track.scrollBar.enabled)
|
||||
return 0;
|
||||
if (track.scrollBar.size === 1)
|
||||
return 0;
|
||||
if (track.mouseArea.pressed)
|
||||
return 1;
|
||||
if (track.mouseArea.containsMouse)
|
||||
return 0.8;
|
||||
if (track.scrollBar.policy === CustomScrollBar.AlwaysOn || track.scrollBar.shouldBeActive)
|
||||
return 0.6;
|
||||
color: Colors.palette.m3secondary
|
||||
implicitHeight: track.scrollBar.height * track.scrollBar.effectiveSize
|
||||
implicitWidth: track.mouseArea.pressed || track.mouseArea.containsMouse ? Tokens.padding.extraSmall * 2 : Tokens.padding.extraSmall
|
||||
opacity: {
|
||||
if (!track.scrollBar.enabled)
|
||||
return 0;
|
||||
}
|
||||
radius: Tokens.rounding.full
|
||||
y: track.scrollBar.reversed ? track.scrollBar.height * (1 + track.scrollBar.nonAnimY) * track.scrollBar.travelScale : track.scrollBar.height * track.scrollBar.nonAnimY * track.scrollBar.travelScale
|
||||
if (track.scrollBar.size === 1)
|
||||
return 0;
|
||||
if (track.mouseArea.pressed)
|
||||
return 1;
|
||||
if (track.mouseArea.containsMouse)
|
||||
return 0.8;
|
||||
if (track.scrollBar.policy === CustomScrollBar.AlwaysOn || track.scrollBar.shouldBeActive)
|
||||
return 0.6;
|
||||
return 0;
|
||||
}
|
||||
radius: Tokens.rounding.full
|
||||
y: track.scrollBar.reversed ? track.scrollBar.height * (1 + track.scrollBar.nonAnimY) * track.scrollBar.travelScale : track.scrollBar.height * track.scrollBar.nonAnimY * track.scrollBar.travelScale
|
||||
|
||||
Behavior on implicitWidth {
|
||||
Anim {}
|
||||
}
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
Behavior on implicitWidth {
|
||||
Anim {}
|
||||
}
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
acceptedButtons: Qt.NoButton
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
hoverEnabled: true
|
||||
}
|
||||
MouseArea {
|
||||
acceptedButtons: Qt.NoButton
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
hoverEnabled: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@ Scope {
|
||||
name: "Bar-Exclusion"
|
||||
screen: root.screen
|
||||
|
||||
mask: Region {}
|
||||
mask: Region {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+6
-7
@@ -19,11 +19,15 @@ import ZShell.Config
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property Item bar
|
||||
required property real borderThickness
|
||||
readonly property alias clipboard: clipboard
|
||||
readonly property alias dashboard: dashboard
|
||||
readonly property alias dashboardWrapper: dashboardWrapper
|
||||
readonly property alias dock: dock
|
||||
readonly property alias drawing: drawing
|
||||
required property var drawingItem
|
||||
required property EdgeGeometry geometry
|
||||
readonly property alias launcher: launcher
|
||||
readonly property alias notifications: notifications
|
||||
readonly property alias osd: osd
|
||||
@@ -32,19 +36,14 @@ Item {
|
||||
readonly property alias popoutsWrapper: popouts
|
||||
readonly property alias resources: resources
|
||||
readonly property alias resourcesWrapper: resourcesWrapper
|
||||
required property ShellScreen screen
|
||||
required property ScreenState screenState
|
||||
readonly property alias settings: settings
|
||||
readonly property alias settingsWrapper: settingsWrapper
|
||||
readonly property alias sidebar: sidebar
|
||||
readonly property alias toasts: toasts
|
||||
readonly property alias utilities: utilities
|
||||
|
||||
required property Item bar
|
||||
required property real borderThickness
|
||||
required property var drawingItem
|
||||
required property EdgeGeometry geometry
|
||||
required property ShellScreen screen
|
||||
required property ScreenState screenState
|
||||
|
||||
anchors.bottomMargin: geometry.insetBottom(borderThickness)
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: geometry.insetLeft(borderThickness)
|
||||
|
||||
@@ -11,7 +11,6 @@ import ZShell.Blobs
|
||||
import qs.Daemons
|
||||
import qs.Components
|
||||
import qs.Modules.Bar
|
||||
import qs.Modules.Bar.Popouts.Network as N
|
||||
import ZShell.Config
|
||||
import qs.Helpers
|
||||
import qs.Drawers
|
||||
|
||||
+28
-12
@@ -27,21 +27,27 @@ ColumnLayout {
|
||||
CustomText {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
color: Colors.palette.m3secondary
|
||||
font: Tokens.font.headline.builders.large.scale(3 * root.centerScale).weight(Font.Bold).width(30).build()
|
||||
font.bold: true
|
||||
font.family: Config.appearance.font.family.clock
|
||||
font.pointSize: Math.floor(Tokens.font.size.extraLarge * 3 * root.centerScale)
|
||||
text: Time.hourStr
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
color: Colors.palette.m3primary
|
||||
font: Tokens.font.headline.builders.large.scale(3 * root.centerScale).weight(Font.Bold).width(30).build()
|
||||
font.bold: true
|
||||
font.family: Config.appearance.font.family.clock
|
||||
font.pointSize: Math.floor(Tokens.font.size.extraLarge * 3 * root.centerScale)
|
||||
text: ":"
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
color: Colors.palette.m3secondary
|
||||
font: Tokens.font.headline.builders.large.scale(3 * root.centerScale).weight(Font.Bold).width(30).build()
|
||||
font.bold: true
|
||||
font.family: Config.appearance.font.family.clock
|
||||
font.pointSize: Math.floor(Tokens.font.size.extraLarge * 3 * root.centerScale)
|
||||
text: Time.minuteStr
|
||||
}
|
||||
}
|
||||
@@ -50,11 +56,13 @@ ColumnLayout {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.topMargin: -Tokens.padding.large * 2
|
||||
color: Colors.palette.m3tertiary
|
||||
font: Tokens.font.title.builders.medium.weight(Font.DemiBold).build()
|
||||
font.bold: true
|
||||
font.family: Config.appearance.font.family.mono
|
||||
font.pointSize: Math.floor(Tokens.font.size.extraLarge * root.centerScale)
|
||||
text: Time.format("dddd, d MMMM yyyy")
|
||||
}
|
||||
|
||||
ClipRect {
|
||||
CustomClippingRect {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.topMargin: Tokens.spacing.largeIncreased * 2
|
||||
color: Colors.tPalette.m3surfaceContainer
|
||||
@@ -65,7 +73,7 @@ ColumnLayout {
|
||||
MaterialIcon {
|
||||
anchors.centerIn: parent
|
||||
color: Colors.palette.m3onSurfaceVariant
|
||||
fontStyle: Tokens.font.icon.size(root.centerWidth / 4).build()
|
||||
font.pointSize: Math.floor(root.centerWidth / 4)
|
||||
text: "person"
|
||||
}
|
||||
|
||||
@@ -80,7 +88,9 @@ ColumnLayout {
|
||||
CustomText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
color: Colors.palette.m3onSurfaceVariant
|
||||
font: Tokens.font.body.large
|
||||
font.family: Config.appearance.font.family.mono
|
||||
font.pointSize: Tokens.font.size.normal
|
||||
font.weight: 600
|
||||
text: root.greeter.username
|
||||
visible: text.length > 0
|
||||
}
|
||||
@@ -145,7 +155,8 @@ ColumnLayout {
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {}
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,6 +191,7 @@ ColumnLayout {
|
||||
|
||||
anchors.centerIn: parent
|
||||
color: root.greeter.buffer && !root.greeter.launching ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
|
||||
font.weight: 500
|
||||
text: root.greeter.launching ? "hourglass_top" : "arrow_forward"
|
||||
}
|
||||
}
|
||||
@@ -192,7 +204,8 @@ ColumnLayout {
|
||||
implicitHeight: Math.max(message.implicitHeight, stateMessage.implicitHeight)
|
||||
|
||||
Behavior on implicitHeight {
|
||||
Anim {}
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
|
||||
CustomText {
|
||||
@@ -232,10 +245,12 @@ ColumnLayout {
|
||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {}
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
Behavior on scale {
|
||||
Anim {}
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
|
||||
onMsgChanged: {
|
||||
@@ -277,7 +292,8 @@ ColumnLayout {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
color: isError ? Colors.palette.m3error : Colors.palette.m3primary
|
||||
font: Tokens.font.mono.medium
|
||||
font.family: Config.appearance.font.family.mono
|
||||
font.pointSize: Tokens.font.size.small
|
||||
horizontalAlignment: Qt.AlignHCenter
|
||||
opacity: 0
|
||||
scale: 0.7
|
||||
|
||||
@@ -20,7 +20,7 @@ BusyIndicator {
|
||||
property int animState
|
||||
property color bgColor: Colors.palette.m3secondaryContainer
|
||||
property color fgColor: Colors.palette.m3primary
|
||||
property real implicitSize: Tokens.font.body.medium.pointSize * 3
|
||||
property real implicitSize: Tokens.font.size.normal * 3
|
||||
property real internalStrokeWidth: strokeWidth
|
||||
readonly property alias progress: manager.progress
|
||||
property real strokeWidth: Tokens.padding.extraSmall * 0.8
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
import QtQuick
|
||||
import ZShell.Components
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property real radius
|
||||
property real topLeftRadius: radius
|
||||
property real topRightRadius: radius
|
||||
property real bottomRightRadius: radius
|
||||
property real bottomLeftRadius: radius
|
||||
property color color: "transparent"
|
||||
default property alias contentData: contentItem.data
|
||||
|
||||
Item {
|
||||
id: contentItem
|
||||
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
ShaderEffectSource {
|
||||
id: source
|
||||
|
||||
hideSource: true
|
||||
sourceItem: contentItem
|
||||
}
|
||||
|
||||
RoundedMask {
|
||||
anchors.fill: parent
|
||||
source: source
|
||||
topLeftRadius: root.topLeftRadius
|
||||
topRightRadius: root.topRightRadius
|
||||
bottomRightRadius: root.bottomRightRadius
|
||||
bottomLeftRadius: root.bottomLeftRadius
|
||||
color: root.color
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
CAnim {}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +1,13 @@
|
||||
import QtQuick
|
||||
import qs.Services
|
||||
|
||||
Flickable {
|
||||
id: root
|
||||
|
||||
property bool doneFakeFlick
|
||||
|
||||
interactive: !ShellState.forActive().isDrawing
|
||||
maximumFlickVelocity: 3000
|
||||
|
||||
rebound: Transition {
|
||||
onRunningChanged: {
|
||||
if (!running && !root.doneFakeFlick) {
|
||||
root.doneFakeFlick = true;
|
||||
root.flick(1, 1);
|
||||
root.flick(-1, -1);
|
||||
Qt.callLater(() => root.cancelFlick());
|
||||
}
|
||||
}
|
||||
|
||||
Anim {
|
||||
properties: "x,y"
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 10
|
||||
running: root.doneFakeFlick
|
||||
|
||||
onTriggered: {
|
||||
root.doneFakeFlick = false;
|
||||
root.returnToBounds();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +1,13 @@
|
||||
import QtQuick
|
||||
import qs.Services
|
||||
|
||||
ListView {
|
||||
id: root
|
||||
|
||||
property bool doneFakeFlick
|
||||
|
||||
interactive: !ShellState.forActive()?.isDrawing
|
||||
maximumFlickVelocity: 3000
|
||||
|
||||
rebound: Transition {
|
||||
// onRunningChanged: {
|
||||
// if (!running && !root.doneFakeFlick) {
|
||||
// root.doneFakeFlick = true;
|
||||
// root.flick(1, 1);
|
||||
// root.flick(-1, -1);
|
||||
// Qt.callLater(() => root.cancelFlick());
|
||||
// }
|
||||
// }
|
||||
|
||||
Anim {
|
||||
properties: "x,y"
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
|
||||
// Timer {
|
||||
// interval: 10
|
||||
// running: root.doneFakeFlick
|
||||
//
|
||||
// onTriggered: {
|
||||
// root.doneFakeFlick = false;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ MouseArea {
|
||||
property int scrollAccumulatedY: 0
|
||||
|
||||
function onWheel(event: WheelEvent): void {
|
||||
event.accepted = false;
|
||||
}
|
||||
|
||||
onWheel: event => {
|
||||
|
||||
@@ -6,6 +6,7 @@ Rectangle {
|
||||
color: "transparent"
|
||||
|
||||
Behavior on color {
|
||||
CAnim {}
|
||||
CAnim {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,9 +14,10 @@ Text {
|
||||
property real animateTo: 1
|
||||
|
||||
color: Colors.palette.m3onSurface
|
||||
font: Tokens.font.body.small
|
||||
font.family: Config.appearance.font.family.sans
|
||||
font.pointSize: Tokens.font.size.normal
|
||||
linkColor: Colors.palette.m3onPrimaryFixedVariant
|
||||
renderType: Text.QtRendering
|
||||
renderType: Text.NativeRendering
|
||||
textFormat: Text.PlainText
|
||||
|
||||
Behavior on color {
|
||||
@@ -27,25 +28,22 @@ Text {
|
||||
enabled: root.animate
|
||||
|
||||
SequentialAnimation {
|
||||
TAnim {
|
||||
target: root
|
||||
Anim {
|
||||
to: root.animateFrom
|
||||
type: Anim.FastEffects
|
||||
}
|
||||
|
||||
PropertyAction {
|
||||
}
|
||||
|
||||
TAnim {
|
||||
target: root
|
||||
Anim {
|
||||
to: root.animateTo
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component TAnim: Anim {
|
||||
component Anim: NumberAnimation {
|
||||
duration: root.animateDuration / 2
|
||||
easing.type: Easing.BezierSpline
|
||||
properties: root.animateProp.split(",").length > 1 ? root.animateProp : ""
|
||||
property: root.animateProp.split(",").length === 1 ? root.animateProp : ""
|
||||
target: root
|
||||
|
||||
@@ -1,305 +1,76 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Shapes
|
||||
import QtQuick.Controls
|
||||
import ZShell.Config
|
||||
import qs.Services
|
||||
|
||||
TextFieldBase {
|
||||
TextField {
|
||||
id: root
|
||||
|
||||
enum TextFieldType {
|
||||
Outlined,
|
||||
Filled
|
||||
}
|
||||
background: null
|
||||
color: Colors.palette.m3onSurface
|
||||
cursorVisible: !readOnly
|
||||
font.family: Config.appearance.font.family.sans
|
||||
font.pointSize: Tokens.font.size.smaller
|
||||
placeholderTextColor: Colors.palette.m3outline
|
||||
renderType: echoMode === TextField.Password ? TextField.QtRendering : TextField.NativeRendering
|
||||
|
||||
readonly property int clampedRadius: Math.min(horizontalPadding, Math.min(width, height) / 2, radius)
|
||||
readonly property string effectiveSupportingText: isError && errorText ? errorText : supportingText
|
||||
readonly property int filledOffset: type === CustomTextField.Filled ? Tokens.spacing.small : 0
|
||||
readonly property int leadingOffset: leadingIcon ? leadingIconLoader.width + leadingIconLoader.anchors.leftMargin : 0
|
||||
readonly property real smallFontScale: smallFontSize / font.pointSize
|
||||
readonly property int supportingTextOffset: effectiveSupportingText ? supportingTextLoader.height + Tokens.spacing.extraSmall : 0
|
||||
readonly property int trailingOffset: trailingIcon ? trailingIconLoader.width + trailingIconLoader.anchors.rightMargin : 0
|
||||
readonly property bool valid: !validate || (!text && emptyIsValid) || (validate instanceof RegExp ? validate.test(text) : !!validate(text))
|
||||
|
||||
property int horizontalPadding: Tokens.padding.large
|
||||
property int verticalPadding: Tokens.padding.large
|
||||
|
||||
property int radius: Tokens.rounding.small
|
||||
|
||||
property string supportingText
|
||||
property string trailingIcon
|
||||
property int smallFontSize: Tokens.font.label.small.pointSize
|
||||
|
||||
property int type: CustomTextField.Outlined
|
||||
property var validate // Regex or function
|
||||
property bool emptyIsValid: true
|
||||
property string errorText
|
||||
property bool isError
|
||||
property string leadingIcon
|
||||
|
||||
bottomPadding: verticalPadding + supportingTextOffset - filledOffset
|
||||
leftPadding: horizontalPadding + leadingOffset
|
||||
rightPadding: horizontalPadding + trailingOffset
|
||||
topPadding: verticalPadding + filledOffset
|
||||
|
||||
background: Loader {
|
||||
anchors.bottomMargin: root.supportingTextOffset
|
||||
anchors.fill: parent
|
||||
sourceComponent: root.type === CustomTextField.Filled ? filledComp : outlineComp
|
||||
|
||||
StateLayer {
|
||||
id: stateLayer
|
||||
|
||||
cursorShape: Qt.IBeamCursor
|
||||
enabled: !root.activeFocus
|
||||
manualPressOverride: tapHandler.pressed
|
||||
radius: root.type === CustomTextField.Outlined ? root.clampedRadius : 0
|
||||
topLeftRadius: root.clampedRadius
|
||||
topRightRadius: root.clampedRadius
|
||||
|
||||
onClicked: root.focus = true
|
||||
Behavior on color {
|
||||
CAnim {
|
||||
}
|
||||
}
|
||||
cursorDelegate: CustomRect {
|
||||
id: cursor
|
||||
|
||||
onEditingFinished: {
|
||||
if (!valid)
|
||||
isError = true;
|
||||
}
|
||||
onPressed: {
|
||||
if (stateLayer.enabled)
|
||||
stateLayer.press(stateLayer.mouseX, stateLayer.mouseY);
|
||||
}
|
||||
onTextEdited: {
|
||||
if (isError)
|
||||
isError = false;
|
||||
}
|
||||
property bool disableBlink
|
||||
|
||||
Item {
|
||||
id: contentWrapper
|
||||
color: Colors.palette.m3primary
|
||||
implicitWidth: 2
|
||||
radius: Tokens.rounding.medium
|
||||
|
||||
anchors.bottomMargin: root.supportingTextOffset
|
||||
anchors.fill: parent
|
||||
|
||||
CustomText {
|
||||
id: placeholder
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: root.leftPadding
|
||||
anchors.topMargin: Tokens.padding.extraSmall
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: root.isError ? Colors.palette.m3error : (root.activeFocus ? Colors.palette.m3primary : root.text ? Colors.palette.m3outline : root.placeholderTextColor)
|
||||
font.family: root.font.family
|
||||
font.pointSize: root.font.pointSize
|
||||
font.variableAxes: root.font.variableAxes
|
||||
font.weight: root.font.weight
|
||||
renderType: Text.QtRendering
|
||||
text: root.placeholderText
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "smallOutlined"
|
||||
when: root.type === CustomTextField.Outlined && (root.activeFocus || root.text)
|
||||
|
||||
PropertyChanges {
|
||||
placeholder.anchors.leftMargin: -(1 - root.smallFontScale) * placeholder.width / 2 + root.horizontalPadding
|
||||
placeholder.scale: root.smallFontScale
|
||||
}
|
||||
|
||||
AnchorChanges {
|
||||
anchors.verticalCenter: contentWrapper.top
|
||||
target: placeholder
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "smallFilled"
|
||||
when: root.type === CustomTextField.Filled && (root.activeFocus || root.text)
|
||||
|
||||
PropertyChanges {
|
||||
placeholder.anchors.leftMargin: -(1 - root.smallFontScale) * placeholder.width / 2 + root.horizontalPadding + root.leadingOffset
|
||||
placeholder.scale: root.smallFontScale
|
||||
}
|
||||
|
||||
AnchorChanges {
|
||||
anchors.top: contentWrapper.top
|
||||
anchors.verticalCenter: undefined
|
||||
target: placeholder
|
||||
}
|
||||
}
|
||||
]
|
||||
transitions: Transition {
|
||||
Anim {
|
||||
properties: "scale,leftMargin"
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
|
||||
AnchorAnim {
|
||||
duration: Tokens.anim.durations.expressiveDefaultEffects
|
||||
easing: Tokens.anim.expressiveDefaultEffects
|
||||
}
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
duration: Tokens.anim.durations.small
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: leadingIconLoader
|
||||
|
||||
active: root.leadingIcon
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Tokens.padding.small
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
sourceComponent: MaterialIcon {
|
||||
color: Colors.palette.m3onSurfaceVariant
|
||||
text: root.leadingIcon
|
||||
fontStyle: Tokens.font.icon.builders.medium.scale(0.9).build()
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: trailingIconLoader
|
||||
|
||||
active: root.trailingIcon
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Tokens.padding.small
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
sourceComponent: MaterialIcon {
|
||||
color: Colors.palette.m3onSurfaceVariant
|
||||
text: root.trailingIcon
|
||||
fontStyle: Tokens.font.icon.builders.medium.scale(0.9).build()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: supportingTextLoader
|
||||
|
||||
active: root.effectiveSupportingText
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: root.horizontalPadding
|
||||
|
||||
sourceComponent: CustomText {
|
||||
color: root.isError ? Colors.palette.m3error : Colors.palette.m3onSurfaceVariant
|
||||
font: Tokens.font.label.small
|
||||
text: root.effectiveSupportingText
|
||||
}
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
id: tapHandler
|
||||
}
|
||||
|
||||
Component {
|
||||
id: outlineComp
|
||||
|
||||
Shape {
|
||||
id: bg
|
||||
|
||||
asynchronous: true
|
||||
preferredRendererType: Shape.CurveRenderer
|
||||
|
||||
ShapePath {
|
||||
id: path
|
||||
|
||||
readonly property real inset: strokeWidth / 2
|
||||
readonly property real outlineGap: placeholder.width * root.smallFontScale + Tokens.spacing.small * 2
|
||||
property real outlineGapScale: root.activeFocus || root.text ? 1 : 0
|
||||
|
||||
capStyle: ShapePath.RoundCap
|
||||
fillColor: "transparent"
|
||||
startX: path.inset + root.horizontalPadding - root.clampedRadius + path.outlineGap * (1 - path.outlineGapScale) / 2 + path.outlineGap * path.outlineGapScale
|
||||
startY: path.inset
|
||||
strokeColor: root.isError ? Colors.palette.m3error : (root.activeFocus ? Colors.palette.m3primary : Colors.palette.m3outline)
|
||||
strokeWidth: root.activeFocus ? 2 : 1
|
||||
|
||||
Behavior on outlineGapScale {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
Behavior on strokeColor {
|
||||
CAnim {}
|
||||
}
|
||||
Behavior on strokeWidth {
|
||||
Anim {}
|
||||
}
|
||||
|
||||
PathLine {
|
||||
x: bg.width - path.inset - root.clampedRadius
|
||||
y: path.inset
|
||||
}
|
||||
|
||||
PathArc {
|
||||
radiusX: root.clampedRadius
|
||||
radiusY: root.clampedRadius
|
||||
x: bg.width - path.inset
|
||||
y: path.inset + root.clampedRadius
|
||||
}
|
||||
|
||||
PathLine {
|
||||
x: bg.width - path.inset
|
||||
y: bg.height - path.inset - root.clampedRadius
|
||||
}
|
||||
|
||||
PathArc {
|
||||
radiusX: root.clampedRadius
|
||||
radiusY: root.clampedRadius
|
||||
x: bg.width - path.inset - root.clampedRadius
|
||||
y: bg.height - path.inset
|
||||
}
|
||||
|
||||
PathLine {
|
||||
x: path.inset + root.clampedRadius
|
||||
y: bg.height - path.inset
|
||||
}
|
||||
|
||||
PathArc {
|
||||
radiusX: root.clampedRadius
|
||||
radiusY: root.clampedRadius
|
||||
x: path.inset
|
||||
y: bg.height - path.inset - root.clampedRadius
|
||||
}
|
||||
|
||||
PathLine {
|
||||
x: path.inset
|
||||
y: path.inset + root.clampedRadius
|
||||
}
|
||||
|
||||
PathArc {
|
||||
radiusX: root.clampedRadius
|
||||
radiusY: root.clampedRadius
|
||||
x: path.inset + root.clampedRadius
|
||||
y: path.inset
|
||||
}
|
||||
|
||||
PathLine {
|
||||
x: path.inset + root.horizontalPadding - root.clampedRadius / 2 + path.outlineGap * (1 - path.outlineGapScale) / 2
|
||||
y: path.inset
|
||||
Connections {
|
||||
function onCursorPositionChanged(): void {
|
||||
if (root.activeFocus && root.cursorVisible) {
|
||||
cursor.opacity = 1;
|
||||
cursor.disableBlink = true;
|
||||
enableBlink.restart();
|
||||
}
|
||||
}
|
||||
|
||||
target: root
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: enableBlink
|
||||
|
||||
interval: 100
|
||||
|
||||
onTriggered: cursor.disableBlink = false
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 500
|
||||
repeat: true
|
||||
running: root.activeFocus && root.cursorVisible && !cursor.disableBlink
|
||||
triggeredOnStart: true
|
||||
|
||||
onTriggered: parent.opacity = parent.opacity === 1 ? 0 : 1
|
||||
}
|
||||
|
||||
Binding {
|
||||
cursor.opacity: 0
|
||||
when: !root.activeFocus || !root.cursorVisible
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: filledComp
|
||||
|
||||
CustomRect {
|
||||
color: root.activeFocus ? Colors.tPalette.m3surfaceContainerHighest : Colors.tPalette.m3surfaceContainerHigh
|
||||
topLeftRadius: root.clampedRadius
|
||||
topRightRadius: root.clampedRadius
|
||||
|
||||
CustomRect {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
color: root.isError ? Colors.palette.m3error : (root.activeFocus ? Colors.palette.m3primary : Colors.palette.m3outline)
|
||||
implicitHeight: root.activeFocus ? 2 : 1
|
||||
|
||||
Behavior on implicitHeight {
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
Behavior on placeholderTextColor {
|
||||
CAnim {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import ZShell.Config
|
||||
import qs.Services
|
||||
|
||||
TextInput {
|
||||
renderType: Text.NativeRendering
|
||||
selectedTextColor: Colors.palette.m3onSecondaryContainer
|
||||
selectionColor: Colors.tPalette.colSecondaryContainer
|
||||
selectionColor: Colors.tPalette.m3onSecondaryContainer
|
||||
|
||||
font {
|
||||
family: Config.appearance?.font.family.sans ?? "sans-serif"
|
||||
hintingPreference: Font.PreferFullHinting
|
||||
pixelSize: Config.appearance?.font.size.normal ?? 15
|
||||
pixelSize: Tokens?.font.size.normal ?? 15
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import ZShell.Config
|
||||
|
||||
Image {
|
||||
id: root
|
||||
|
||||
property int fadeInAnim: Anim.DefaultEffects
|
||||
property int fadeInLargeAnim: Anim.StandardLarge
|
||||
property int fadeOutAnim: Anim.FastEffects
|
||||
property bool fadingOut
|
||||
property bool hadPrevious
|
||||
property bool preventInit
|
||||
|
||||
function maybeStartInAnim(): void {
|
||||
if (!preventInit && !opacityInAnim.running && status === Image.Ready) {
|
||||
opacityInAnim.type = hadPrevious ? fadeInAnim : fadeInLargeAnim;
|
||||
opacityInAnim.start();
|
||||
}
|
||||
}
|
||||
|
||||
asynchronous: true
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
opacity: 0
|
||||
retainWhileLoading: true
|
||||
sourceSize: {
|
||||
const dpr = (QsWindow.window as QsWindow)?.devicePixelRatio ?? 1;
|
||||
return Qt.size(width * dpr, height * dpr);
|
||||
}
|
||||
|
||||
Anim on opacity {
|
||||
id: opacityInAnim
|
||||
|
||||
running: false
|
||||
to: 1
|
||||
}
|
||||
Behavior on source {
|
||||
SequentialAnimation {
|
||||
ScriptAction {
|
||||
script: opacityInAnim.stop()
|
||||
}
|
||||
|
||||
PropertyAction {
|
||||
property: "fadingOut"
|
||||
target: root
|
||||
value: true
|
||||
}
|
||||
|
||||
Anim {
|
||||
property: "opacity"
|
||||
target: root
|
||||
to: 0
|
||||
type: root.fadeOutAnim
|
||||
}
|
||||
|
||||
PropertyAction {
|
||||
property: "fadingOut"
|
||||
target: root
|
||||
value: false
|
||||
}
|
||||
|
||||
PropertyAction {
|
||||
property: "hadPrevious"
|
||||
target: root
|
||||
value: root.source
|
||||
}
|
||||
|
||||
PropertyAction {
|
||||
}
|
||||
|
||||
ScriptAction {
|
||||
script: root.maybeStartInAnim()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onPreventInitChanged: maybeStartInAnim()
|
||||
onStatusChanged: maybeStartInAnim()
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import M3Shapes
|
||||
import ZShell.Config
|
||||
import qs.Services
|
||||
|
||||
MaterialShape {
|
||||
id: root
|
||||
|
||||
property bool animated: true
|
||||
property real cRotation
|
||||
property bool containsIcon
|
||||
property real dampingRatio: 0.6
|
||||
property real lRotation
|
||||
property int morphAnimRotation: 60
|
||||
property real morphScale: 0.14
|
||||
property alias rotateAnimDuration: rotateAnim.duration
|
||||
property int shapeIndex
|
||||
property list<int> shapes: {
|
||||
if (containsIcon)
|
||||
return [MaterialShape.SoftBurst, MaterialShape.Cookie9Sided, MaterialShape.Pill, MaterialShape.Sunny, MaterialShape.Cookie4Sided, MaterialShape.Oval];
|
||||
return [MaterialShape.SoftBurst, MaterialShape.Cookie9Sided, MaterialShape.Pentagon, MaterialShape.Pill, MaterialShape.Sunny, MaterialShape.Cookie4Sided, MaterialShape.Oval];
|
||||
}
|
||||
readonly property real springDuration: {
|
||||
const wn = Math.sqrt(stiffness);
|
||||
const r = -dampingRatio * wn;
|
||||
const c = 1 / Math.sqrt(1 - dampingRatio * dampingRatio);
|
||||
return Math.log(visibilityThreshold / c) / r;
|
||||
}
|
||||
readonly property real springMaxVelocity: {
|
||||
const wn = Math.sqrt(stiffness);
|
||||
const factor = Math.exp(-z * Math.acos(z) / Math.sqrt(1 - z * z));
|
||||
return wn * factor;
|
||||
}
|
||||
property bool springSettled: true
|
||||
property real stiffness: 180
|
||||
property real thisLRotation
|
||||
property real visibilityThreshold: 0.075
|
||||
|
||||
function spring(t: real): var {
|
||||
const wn = Math.sqrt(stiffness);
|
||||
const za = dampingRatio * wn;
|
||||
|
||||
const wd = wn * Math.sqrt(1 - dampingRatio * dampingRatio);
|
||||
const r = za / wd;
|
||||
const pos = 1 - Math.exp(-za * t) * (Math.cos(wd * t) + r * Math.sin(wd * t));
|
||||
const vel = Math.exp(-za * t) * (wn * wn / wd) * Math.sin(wd * t);
|
||||
|
||||
return [pos, vel];
|
||||
}
|
||||
|
||||
color: Colors.palette.m3primary
|
||||
implicitSize: 38
|
||||
toShape: shapes[0]
|
||||
|
||||
RotationAnimation on cRotation {
|
||||
id: rotateAnim
|
||||
|
||||
duration: 4666
|
||||
easing.type: Easing.Linear
|
||||
from: 0
|
||||
loops: Animation.Infinite
|
||||
running: root.animated
|
||||
to: 360
|
||||
}
|
||||
Behavior on color {
|
||||
CAnim {
|
||||
}
|
||||
}
|
||||
|
||||
ElapsedTimer {
|
||||
id: timer
|
||||
}
|
||||
|
||||
FrameAnimation {
|
||||
running: root.animated && !root.springSettled
|
||||
|
||||
onTriggered: {
|
||||
const t = timer.elapsed();
|
||||
|
||||
if (t >= root.springDuration) {
|
||||
root.springSettled = true;
|
||||
} else {
|
||||
const [pos, vel] = root.spring(t);
|
||||
root.morphProgress = Math.min(1, pos); // Overshooting the morph looks weird
|
||||
root.thisLRotation = pos * root.morphAnimRotation;
|
||||
root.scale = 1 + vel * root.morphScale / root.springMaxVelocity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 650
|
||||
repeat: true
|
||||
running: root.animated
|
||||
triggeredOnStart: true
|
||||
|
||||
onTriggered: {
|
||||
root.beginBatchUpdate();
|
||||
root.fromShape = root.toShape;
|
||||
root.shapeIndex = (root.shapeIndex + 1) % root.shapes.length;
|
||||
root.toShape = root.shapes[root.shapeIndex];
|
||||
root.morphProgress = 0;
|
||||
|
||||
root.rotation = root.rotation;
|
||||
root.lRotation = (root.lRotation + root.thisLRotation) % 360;
|
||||
root.thisLRotation = 0;
|
||||
root.rotation = Qt.binding(() => root.cRotation + root.lRotation + root.thisLRotation);
|
||||
|
||||
root.springSettled = false;
|
||||
timer.restart();
|
||||
root.endBatchUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,16 @@
|
||||
import QtQuick
|
||||
import ZShell.Config
|
||||
import qs.Services
|
||||
|
||||
CustomText {
|
||||
id: root
|
||||
|
||||
property real fill: 0
|
||||
property real fill
|
||||
property int grade: Colors.light ? 0 : -25
|
||||
property font fontStyle: Tokens.font.icon.small
|
||||
|
||||
renderType: Text.NativeRendering
|
||||
font: Tokens.font.icon.size(fontStyle.pointSize).weight(fontStyle.weight).vaxes(fontStyle.variableAxes).fill(fill.toFixed(1)).grade(grade).build()
|
||||
font.family: "Material Symbols Rounded"
|
||||
font.pointSize: Tokens.font.size.larger
|
||||
font.variableAxes: ({
|
||||
FILL: fill.toFixed(1),
|
||||
GRAD: grade,
|
||||
opsz: fontInfo.pixelSize,
|
||||
wght: fontInfo.weight
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import QtQuick
|
||||
import QtQuick.Shapes
|
||||
import ZShell
|
||||
import ZShell.Components
|
||||
import qs.Helpers
|
||||
import ZShell.Config
|
||||
import qs.Services
|
||||
|
||||
@@ -11,6 +13,7 @@ MouseArea {
|
||||
property alias bottomRightRadius: base.bottomRightRadius
|
||||
property real circleRadius
|
||||
property alias color: base.color
|
||||
property bool disabled
|
||||
readonly property real endRadius: {
|
||||
const d1 = distSq(0, 0);
|
||||
const d2 = distSq(width, 0);
|
||||
@@ -19,7 +22,6 @@ MouseArea {
|
||||
return (Math.sqrt(Math.max(d1, d2, d3, d4)) + (shapeMorph ? 24 : 0)) * 1.3;
|
||||
}
|
||||
property real endRadiusAtPress
|
||||
property bool manualHoverOverride
|
||||
property bool manualPressOverride
|
||||
property real pressX: width / 2
|
||||
property real pressY: height / 2
|
||||
@@ -27,7 +29,7 @@ MouseArea {
|
||||
readonly property alias rect: base
|
||||
property bool shapeMorph
|
||||
property bool showHoverBackground: true
|
||||
property real stateOpacity: containsMouse || manualHoverOverride ? 0.08 : 0
|
||||
property real stateOpacity: containsMouse ? 0.08 : 0
|
||||
property alias topLeftRadius: base.topLeftRadius
|
||||
property alias topRightRadius: base.topRightRadius
|
||||
|
||||
@@ -50,8 +52,8 @@ MouseArea {
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
cursorShape: !enabled ? undefined : Qt.PointingHandCursor
|
||||
enabled: parent.enabled
|
||||
cursorShape: disabled ? undefined : Qt.PointingHandCursor
|
||||
enabled: !disabled
|
||||
hoverEnabled: true
|
||||
|
||||
Behavior on stateOpacity {
|
||||
@@ -98,13 +100,13 @@ MouseArea {
|
||||
id: base
|
||||
|
||||
anchors.fill: parent
|
||||
bottomLeftRadius: root.parent?.bottomLeftRadius ?? radius ?? 0
|
||||
bottomRightRadius: root.parent?.bottomRightRadius ?? radius ?? 0
|
||||
color: Colors.palette.m3onSurface
|
||||
opacity: root.stateOpacity
|
||||
// Pick up radius from parent if it has one (parent can be anything with radius props)
|
||||
// qmllint disable missing-property
|
||||
radius: root.parent?.radius ?? 0
|
||||
bottomLeftRadius: root.parent?.bottomLeftRadius ?? radius ?? 0
|
||||
bottomRightRadius: root.parent?.bottomRightRadius ?? radius ?? 0
|
||||
topLeftRadius: root.parent?.topLeftRadius ?? radius ?? 0
|
||||
topRightRadius: root.parent?.topRightRadius ?? radius ?? 0
|
||||
// qmllint enable missing-property
|
||||
|
||||
+39
-16
@@ -13,16 +13,34 @@ RowLayout {
|
||||
|
||||
spacing: Tokens.spacing.largeIncreased * 2
|
||||
|
||||
CustomRect {
|
||||
Layout.fillHeight: true
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
topLeftRadius: Tokens.rounding.largeIncreased
|
||||
bottomLeftRadius: Tokens.rounding.largeIncreased
|
||||
color: Colors.tPalette.m3surfaceContainer
|
||||
radius: Tokens.rounding.small
|
||||
spacing: Tokens.spacing.medium
|
||||
|
||||
UserDock {
|
||||
greeter: root.greeter
|
||||
CustomRect {
|
||||
Layout.fillWidth: true
|
||||
color: Colors.tPalette.m3surfaceContainer
|
||||
implicitHeight: weather.implicitHeight
|
||||
radius: Tokens.rounding.small
|
||||
topLeftRadius: Tokens.rounding.largeIncreased
|
||||
|
||||
WeatherInfo {
|
||||
id: weather
|
||||
|
||||
rootHeight: root.height
|
||||
}
|
||||
}
|
||||
|
||||
CustomClippingRect {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
bottomLeftRadius: Tokens.rounding.largeIncreased
|
||||
color: Colors.tPalette.m3surfaceContainer
|
||||
radius: Tokens.rounding.small
|
||||
|
||||
UserDock {
|
||||
greeter: root.greeter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,16 +49,21 @@ RowLayout {
|
||||
screenHeight: root.screenHeight
|
||||
}
|
||||
|
||||
CustomRect {
|
||||
Layout.fillHeight: true
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
bottomRightRadius: Tokens.rounding.largeIncreased
|
||||
color: Colors.tPalette.m3surfaceContainer
|
||||
radius: Tokens.rounding.small
|
||||
topRightRadius: Tokens.rounding.largeIncreased
|
||||
spacing: Tokens.spacing.medium
|
||||
|
||||
SessionDock {
|
||||
greeter: root.greeter
|
||||
CustomRect {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
bottomRightRadius: Tokens.rounding.largeIncreased
|
||||
color: Colors.tPalette.m3surfaceContainer
|
||||
radius: Tokens.rounding.small
|
||||
topRightRadius: Tokens.rounding.largeIncreased
|
||||
|
||||
SessionDock {
|
||||
greeter: root.greeter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,7 +134,8 @@ Scope {
|
||||
id: lockIcon
|
||||
|
||||
anchors.centerIn: parent
|
||||
fontStyle: Tokens.font.icon.builders.extraLarge.size(Tokens.font.icon.extraLarge.pointSize * 3).build()
|
||||
font.bold: true
|
||||
font.pointSize: Tokens.font.size.extraLarge * 4
|
||||
text: "lock"
|
||||
}
|
||||
|
||||
|
||||
+15
-8
@@ -39,7 +39,8 @@ Item {
|
||||
anchors.centerIn: parent
|
||||
animate: true
|
||||
color: root.greeter.launching ? Colors.palette.m3secondary : Colors.palette.m3outline
|
||||
font: Tokens.font.mono.medium
|
||||
font.family: Config.appearance.font.family.mono
|
||||
font.pointSize: Tokens.font.size.normal
|
||||
opacity: root.buffer ? 0 : 1
|
||||
text: {
|
||||
if (root.greeter.launching)
|
||||
@@ -50,7 +51,8 @@ Item {
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {}
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,13 +64,15 @@ Item {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: Colors.palette.m3onSurface
|
||||
elide: Text.ElideLeft
|
||||
font: Tokens.font.mono.medium
|
||||
font.family: Config.appearance.font.family.mono
|
||||
font.pointSize: Tokens.font.size.normal
|
||||
horizontalAlignment: Qt.AlignHCenter
|
||||
opacity: root.greeter.echoResponse && root.buffer ? 1 : 0
|
||||
text: root.buffer
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {}
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +89,7 @@ Item {
|
||||
|
||||
anchors.centerIn: parent
|
||||
anchors.horizontalCenterOffset: implicitWidth > root.width ? -(implicitWidth - root.width) / 2 : 0
|
||||
implicitHeight: Tokens.font.body.medium
|
||||
implicitHeight: Tokens.font.size.normal
|
||||
implicitWidth: fullWidth
|
||||
interactive: false
|
||||
opacity: root.greeter.echoResponse ? 0 : 1
|
||||
@@ -103,7 +107,8 @@ Item {
|
||||
scale: 0
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {}
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
Behavior on scale {
|
||||
Anim {
|
||||
@@ -151,13 +156,15 @@ Item {
|
||||
Behavior on implicitWidth {
|
||||
id: imWidthBehavior
|
||||
|
||||
Anim {}
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
model: ScriptModel {
|
||||
values: root.buffer.split("")
|
||||
}
|
||||
Behavior on opacity {
|
||||
Anim {}
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+17
-11
@@ -13,19 +13,22 @@ ColumnLayout {
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Tokens.padding.large
|
||||
spacing: Tokens.spacing.large
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
color: Colors.palette.m3outline
|
||||
elide: Text.ElideRight
|
||||
font: Tokens.font.mono.builders.small.weight(Font.Medium).build()
|
||||
font.family: Config.appearance.font.family.mono
|
||||
font.weight: 500
|
||||
text: root.greeter.sessions.length > 0 ? qsTr("%1 session%2").arg(root.greeter.sessions.length).arg(root.greeter.sessions.length === 1 ? "" : "s") : qsTr("Sessions")
|
||||
}
|
||||
|
||||
Item {
|
||||
CustomClippingRect {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
color: "transparent"
|
||||
radius: Tokens.rounding.small
|
||||
|
||||
Loader {
|
||||
active: opacity > 0
|
||||
@@ -43,14 +46,16 @@ ColumnLayout {
|
||||
MaterialIcon {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
color: Colors.palette.m3outlineVariant
|
||||
fontStyle: Tokens.font.icon.large
|
||||
font.pointSize: Tokens.font.size.extraLarge * 2
|
||||
text: "desktop_windows"
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
color: Colors.palette.m3outlineVariant
|
||||
font: Tokens.font.headline.builders.small.width(90).build()
|
||||
font.family: Config.appearance.font.family.mono
|
||||
font.pointSize: Tokens.font.size.large
|
||||
font.weight: 500
|
||||
text: qsTr("No Sessions Found")
|
||||
}
|
||||
}
|
||||
@@ -63,7 +68,6 @@ ColumnLayout {
|
||||
clip: true
|
||||
currentIndex: root.greeter.sessionIndex
|
||||
highlightFollowsCurrentItem: false
|
||||
boundsBehavior: ListView.StopAtBounds
|
||||
model: root.greeter.sessions
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
@@ -76,7 +80,7 @@ ColumnLayout {
|
||||
anchors.left: parent?.left
|
||||
anchors.right: parent?.right
|
||||
implicitHeight: row.implicitHeight + Tokens.padding.small * 2
|
||||
radius: Tokens.rounding.large
|
||||
radius: Tokens.rounding.medium - Tokens.padding.small
|
||||
|
||||
StateLayer {
|
||||
onClicked: {
|
||||
@@ -93,7 +97,7 @@ ColumnLayout {
|
||||
|
||||
MaterialIcon {
|
||||
color: session.index === sessions.currentIndex ? Colors.palette.m3onPrimary : Colors.palette.m3onSurfaceVariant
|
||||
fontStyle: Tokens.font.icon.extraLarge
|
||||
font.pointSize: Tokens.font.size.extraLarge
|
||||
text: modelData.kind === "x11" ? "tv" : "desktop_windows"
|
||||
}
|
||||
|
||||
@@ -105,7 +109,8 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
color: session.index === sessions.currentIndex ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
|
||||
elide: Text.ElideRight
|
||||
font: Tokens.font.body.builders.medium.weight(Font.Bold).build()
|
||||
font.pointSize: Tokens.font.size.normal
|
||||
font.weight: 600
|
||||
text: modelData.name
|
||||
}
|
||||
|
||||
@@ -113,7 +118,8 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
color: session.index === sessions.currentIndex ? Colors.palette.m3onPrimaryFixedVariant : Colors.palette.m3onSurfaceVariant
|
||||
elide: Text.ElideRight
|
||||
font: Tokens.font.mono.small
|
||||
font.family: Config.appearance.font.family.mono
|
||||
font.pointSize: Tokens.font.size.small
|
||||
text: modelData.kind
|
||||
}
|
||||
}
|
||||
@@ -123,7 +129,7 @@ ColumnLayout {
|
||||
color: Colors.palette.m3primary
|
||||
implicitHeight: sessions.currentItem?.implicitHeight ?? 0
|
||||
implicitWidth: sessions.width
|
||||
radius: Tokens.rounding.large
|
||||
radius: Tokens.rounding.medium - Tokens.padding.small
|
||||
y: sessions.currentItem?.y ?? 0
|
||||
|
||||
Behavior on y {
|
||||
|
||||
+16
-11
@@ -14,19 +14,22 @@ ColumnLayout {
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Tokens.padding.large
|
||||
spacing: Tokens.spacing.large
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
color: Colors.palette.m3outline
|
||||
elide: Text.ElideRight
|
||||
font: Tokens.font.mono.builders.small.weight(Font.Medium).build()
|
||||
font.family: Config.appearance.font.family.mono
|
||||
font.weight: 500
|
||||
text: root.greeter.users.length > 0 ? qsTr("%1 user%2").arg(root.greeter.users.length).arg(root.greeter.users.length === 1 ? "" : "s") : qsTr("Users")
|
||||
}
|
||||
|
||||
Item {
|
||||
CustomClippingRect {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
color: "transparent"
|
||||
radius: Tokens.rounding.small
|
||||
|
||||
Loader {
|
||||
active: opacity > 0
|
||||
@@ -45,14 +48,16 @@ ColumnLayout {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
color: Colors.palette.m3outlineVariant
|
||||
fill: 1
|
||||
fontStyle: Tokens.font.icon.large
|
||||
font.pointSize: Tokens.font.size.extraLarge * 2
|
||||
text: "account_circle"
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
color: Colors.palette.m3outlineVariant
|
||||
font: Tokens.font.headline.builders.small.width(90).build()
|
||||
font.family: Config.appearance.font.family.mono
|
||||
font.pointSize: Tokens.font.size.large
|
||||
font.weight: 500
|
||||
text: qsTr("No Users Found")
|
||||
}
|
||||
}
|
||||
@@ -65,7 +70,6 @@ ColumnLayout {
|
||||
clip: true
|
||||
currentIndex: root.greeter.selectedUser ? root.greeter.users.indexOf(root.greeter.selectedUser) : -1
|
||||
highlightFollowsCurrentItem: false
|
||||
boundsBehavior: ListView.StopAtBounds
|
||||
model: root.greeter.users
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
@@ -78,7 +82,7 @@ ColumnLayout {
|
||||
anchors.left: parent?.left
|
||||
anchors.right: parent?.right
|
||||
implicitHeight: row.implicitHeight + Tokens.padding.small * 2
|
||||
radius: Tokens.rounding.large
|
||||
radius: Tokens.rounding.medium - Tokens.padding.small
|
||||
|
||||
StateLayer {
|
||||
onClicked: {
|
||||
@@ -93,7 +97,7 @@ ColumnLayout {
|
||||
anchors.margins: Tokens.padding.small
|
||||
spacing: Tokens.spacing.medium
|
||||
|
||||
ClipRect {
|
||||
CustomClippingRect {
|
||||
Layout.preferredHeight: 50
|
||||
Layout.preferredWidth: 50
|
||||
radius: Tokens.rounding.full
|
||||
@@ -102,7 +106,7 @@ ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
color: user.index === users.currentIndex ? Colors.palette.m3onPrimary : Colors.palette.m3onSurfaceVariant
|
||||
fill: 1
|
||||
fontStyle: Tokens.font.icon.extraLarge
|
||||
font.pointSize: Tokens.font.size.extraLarge
|
||||
text: modelData.kind === "x11" ? "tv" : "account_circle"
|
||||
}
|
||||
|
||||
@@ -120,7 +124,8 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
color: user.index === users.currentIndex ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
|
||||
elide: Text.ElideRight
|
||||
font: Tokens.font.body.builders.medium.weight(Font.Bold).build()
|
||||
font.pointSize: Tokens.font.size.normal
|
||||
font.weight: 600
|
||||
text: modelData.username
|
||||
}
|
||||
}
|
||||
@@ -130,7 +135,7 @@ ColumnLayout {
|
||||
color: Colors.palette.m3primary
|
||||
implicitHeight: users.currentItem?.implicitHeight ?? 0
|
||||
implicitWidth: users.width
|
||||
radius: Tokens.rounding.large
|
||||
radius: Tokens.rounding.medium - Tokens.padding.small
|
||||
y: users.currentItem?.y ?? 0
|
||||
|
||||
Behavior on y {
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import ZShell.Config
|
||||
import qs.Components
|
||||
import qs.Helpers
|
||||
import qs.Services
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
required property int rootHeight
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.margins: Tokens.padding.large * 2
|
||||
anchors.right: parent.right
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
Loader {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.bottomMargin: -Tokens.padding.large
|
||||
Layout.topMargin: Tokens.padding.large * 2
|
||||
active: root.rootHeight > 610
|
||||
visible: active
|
||||
|
||||
sourceComponent: CustomText {
|
||||
color: Colors.palette.m3primary
|
||||
font.pointSize: Tokens.font.size.extraLarge
|
||||
font.weight: 500
|
||||
text: qsTr("Weather")
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Tokens.spacing.largeIncreased
|
||||
|
||||
MaterialIcon {
|
||||
animate: true
|
||||
color: Colors.palette.m3secondary
|
||||
font.pointSize: Tokens.font.size.extraLarge * 2.5
|
||||
text: Weather.icon
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
animate: true
|
||||
color: Colors.palette.m3secondary
|
||||
elide: Text.ElideRight
|
||||
font.pointSize: Tokens.font.size.large
|
||||
font.weight: 500
|
||||
text: Weather.description
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
animate: true
|
||||
color: Colors.palette.m3onSurfaceVariant
|
||||
elide: Text.ElideRight
|
||||
font.pointSize: Tokens.font.size.normal
|
||||
text: qsTr("Humidity: %1%").arg(Weather.humidity)
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
Layout.rightMargin: Tokens.padding.small
|
||||
active: root.width > 400
|
||||
visible: active
|
||||
|
||||
sourceComponent: ColumnLayout {
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
animate: true
|
||||
color: Colors.palette.m3primary
|
||||
elide: Text.ElideLeft
|
||||
font.pointSize: Tokens.font.size.extraLarge
|
||||
font.weight: 500
|
||||
horizontalAlignment: Text.AlignRight
|
||||
text: Weather.temp
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
animate: true
|
||||
color: Colors.palette.m3outline
|
||||
elide: Text.ElideLeft
|
||||
font.pointSize: Tokens.font.size.smaller
|
||||
horizontalAlignment: Text.AlignRight
|
||||
text: qsTr("Feels like: %1").arg(Weather.feelsLike)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: forecastLoader
|
||||
|
||||
Layout.bottomMargin: Tokens.padding.large * 2
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Tokens.spacing.small
|
||||
active: root.rootHeight > 820
|
||||
visible: active
|
||||
|
||||
sourceComponent: RowLayout {
|
||||
spacing: Tokens.spacing.largeIncreased
|
||||
|
||||
Repeater {
|
||||
model: {
|
||||
const forecast = Weather.hourlyForecast;
|
||||
const count = root.width < 320 ? 3 : root.width < 400 ? 4 : 5;
|
||||
if (!forecast)
|
||||
return Array.from({
|
||||
length: count
|
||||
}, () => null);
|
||||
|
||||
const start = Math.max(0, Weather.currentHourIndex);
|
||||
return forecast.slice(start, start + count);
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: forecastHour
|
||||
|
||||
required property var modelData
|
||||
|
||||
Layout.fillWidth: true
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
color: Colors.palette.m3outline
|
||||
font.pointSize: Tokens.font.size.larger
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: {
|
||||
const hour = forecastHour.modelData?.hour ?? 0;
|
||||
return hour > 12 ? `${(hour - 12).toString().padStart(2, "0")} PM` : `${hour.toString().padStart(2, "0")} AM`;
|
||||
}
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
font.pointSize: Tokens.font.size.extraLarge * 1.5
|
||||
font.weight: 500
|
||||
text: forecastHour.modelData?.icon ?? "cloud_alert"
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
color: Colors.palette.m3secondary
|
||||
font.pointSize: Tokens.font.size.larger
|
||||
text: Config.services.useFahrenheit ? `${forecastHour.modelData?.tempF ?? 0}°F` : `${forecastHour.modelData?.tempC ?? 0}°C`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 900000 // 15 minutes
|
||||
repeat: true
|
||||
running: true
|
||||
triggeredOnStart: true
|
||||
|
||||
onTriggered: Weather.reload()
|
||||
}
|
||||
}
|
||||
@@ -77,6 +77,16 @@ Singleton {
|
||||
"99": "thunderstorm"
|
||||
})
|
||||
|
||||
function getAppCategoryIcon(name: string, fallback: string): string {
|
||||
const categories = DesktopEntries.heuristicLookup(name)?.categories;
|
||||
|
||||
if (categories)
|
||||
for (const [key, value] of Object.entries(categoryIcons))
|
||||
if (categories.includes(key))
|
||||
return value;
|
||||
return fallback;
|
||||
}
|
||||
|
||||
function getAppIcon(name: string, fallback: string): string {
|
||||
const icon = DesktopEntries.heuristicLookup(name)?.icon;
|
||||
if (fallback !== "undefined")
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
pragma Singleton
|
||||
|
||||
import Quickshell
|
||||
import Quickshell.Networking
|
||||
import QtQuick
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property bool active: false
|
||||
readonly property list<Network> connectedNetworks: networks.filter(n => n.connected)
|
||||
property Network connectingNetwork
|
||||
readonly property list<NetworkDevice> devices: Networking.devices.values
|
||||
property Network failedNetwork
|
||||
readonly property list<Network> knownNetworks: networks.filter(n => n.known && !n.connected)
|
||||
readonly property list<Network> networks: {
|
||||
const list = [];
|
||||
for (const d of wifiDevices) {
|
||||
for (const n of d.networks.values) {
|
||||
if (!list.includes(n))
|
||||
list.push(n);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
property bool scanning: false
|
||||
readonly property list<Network> unknownNetworks: networks.filter(n => !n.known)
|
||||
readonly property list<WifiDevice> wifiDevices: devices.filter(d => wifiDevice(d))
|
||||
readonly property bool wifiEnabled: Networking.wifiEnabled
|
||||
|
||||
signal networkConnected
|
||||
|
||||
function forgetNetwork(target): void {
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
|
||||
target.forget();
|
||||
}
|
||||
|
||||
function isSecure(security): bool {
|
||||
return (security === WifiSecurityType.WpaPsk || security === WifiSecurityType.Wpa2Psk || security === WifiSecurityType.Sae);
|
||||
}
|
||||
|
||||
function requestConnect(network: Network, secret = undefined): void {
|
||||
connectingNetwork = network;
|
||||
if (secret === undefined || secret === "") {
|
||||
network.connect();
|
||||
} else {
|
||||
(network as WifiNetwork).connectWithPsk(secret);
|
||||
}
|
||||
}
|
||||
|
||||
function setScan(value: bool): void {
|
||||
for (const d of wifiDevices) {
|
||||
d.scannerEnabled = value;
|
||||
}
|
||||
}
|
||||
|
||||
function setWifi(value: bool): void {
|
||||
Networking.wifiEnabled = value;
|
||||
}
|
||||
|
||||
function wifiDevice(dev): bool {
|
||||
return dev.type === DeviceType.Wifi;
|
||||
}
|
||||
|
||||
onActiveChanged: {
|
||||
for (const d of wifiDevices)
|
||||
d.scannerEnabled = active;
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: flushFailed
|
||||
|
||||
interval: 5000
|
||||
running: root.failedNetwork
|
||||
|
||||
onTriggered: {
|
||||
root.failedNetwork = null;
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: checkConnection
|
||||
|
||||
interval: 500
|
||||
repeat: true
|
||||
running: root.connectingNetwork
|
||||
|
||||
onTriggered: {
|
||||
var completedNetwork;
|
||||
|
||||
switch (root.connectingNetwork.state) {
|
||||
case ConnectionState.Connecting:
|
||||
break;
|
||||
case ConnectionState.Connected:
|
||||
completedNetwork = root.connectingNetwork;
|
||||
root.networkConnected();
|
||||
break;
|
||||
case ConnectionState.Disconnected:
|
||||
root.failedNetwork = root.connectingNetwork;
|
||||
completedNetwork = root.connectingNetwork;
|
||||
break;
|
||||
}
|
||||
|
||||
if (completedNetwork) {
|
||||
root.connectingNetwork = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 10000
|
||||
running: checkConnection.running
|
||||
|
||||
onTriggered: {
|
||||
root.failedNetwork = root.connectingNetwork;
|
||||
root.connectingNetwork = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import QtQuick
|
||||
import ZShell.Config
|
||||
import qs.Services
|
||||
import qs.Modules.Settings.Common
|
||||
import qs.Modules.Bar.Popouts.Network
|
||||
import qs.Components
|
||||
import qs.Helpers
|
||||
|
||||
CustomText {
|
||||
required property var modelData
|
||||
|
||||
bottomPadding: Tokens.spacing.extraSmall
|
||||
color: Colors.palette.m3onSurfaceVariant
|
||||
font.pointSize: Tokens.font.size.smaller
|
||||
text: {
|
||||
if (modelData.type === "known") {
|
||||
const count = Network.knownNetworks.filter(n => !n.connected).length;
|
||||
|
||||
return qsTr("%1 known network%2").arg(count).arg(count === 1 ? "" : "s");
|
||||
}
|
||||
|
||||
if (modelData.type === "unknown") {
|
||||
const count = Network.unknownNetworks.filter(n => !n.connected).length;
|
||||
|
||||
return qsTr("%1 network%2 available").arg(count).arg(count === 1 ? "" : "s");
|
||||
}
|
||||
|
||||
return qsTr("object unknown");
|
||||
}
|
||||
topPadding: Tokens.spacing.small
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import ZShell.Config
|
||||
import qs.Modules.Settings.Common
|
||||
import qs.Modules.Bar.Popouts.Network
|
||||
import qs.Components
|
||||
import qs.Helpers
|
||||
import qs.Helpers.Network
|
||||
import qs.Services
|
||||
|
||||
ConnectedRect {
|
||||
id: networkItem
|
||||
|
||||
readonly property int connectedCount: Network.connectedNetworks.length
|
||||
readonly property bool connecting: Network.connectingNetwork === modelData
|
||||
property int horizontalPadding: Tokens.padding.largeIncreased
|
||||
readonly property bool inConnectedGroup: index < connectedCount
|
||||
// sectioning first / last item in list reminder
|
||||
// if index === network.connectedNetworks.length - 1
|
||||
// if index === network.connectedNetworks.length - 1 + network.knownNetworks.length - 2
|
||||
// if index === network.connectedNetworks.length - 1 + network.knownNetworks.length
|
||||
// list.model.values.length - 1
|
||||
required property int index
|
||||
readonly property bool isSecure: Network.isSecure(modelData.security)
|
||||
required property ListView list
|
||||
required property var modelData
|
||||
property int verticalPadding: Tokens.padding.smaller
|
||||
|
||||
Layout.fillWidth: true
|
||||
first: !inConnectedGroup && index === connectedCount + 1
|
||||
implicitHeight: rowLayout.implicitHeight + verticalPadding * 2
|
||||
last: inConnectedGroup ? index === connectedCount - 1 : index === (list?.count ?? -1)
|
||||
radius: Tokens.rounding.small
|
||||
|
||||
RowLayout {
|
||||
id: rowLayout
|
||||
|
||||
Layout.preferredHeight: visible ? rowLayout.implicitHeight + Tokens.padding.smaller * 2 : 0
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: networkItem.horizontalPadding
|
||||
anchors.rightMargin: networkItem.horizontalPadding
|
||||
|
||||
Column {
|
||||
id: column
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Tokens.spacing.extraSmall
|
||||
Layout.rightMargin: Tokens.spacing.extraSmall
|
||||
color: networkItem.modelData.active ? Colors.palette.m3primary : Colors.palette.m3onSurface
|
||||
elide: Text.ElideRight
|
||||
text: networkItem.modelData.name ? networkItem.modelData.name : qsTr("Unknown Network")
|
||||
}
|
||||
|
||||
CustomText {
|
||||
id: subtext
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
color: Colors.palette.m3outline
|
||||
elide: Text.ElideRight
|
||||
font.pointSize: Tokens.font.size.small
|
||||
text: networkItem.modelData.connected ? qsTr("Connected: %1 strength").arg(Math.round(networkItem.modelData.signalStrength * 100) + "%") : qsTr("%1 strength").arg(Math.round(networkItem.modelData.signalStrength * 100) + "%")
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
implicitHeight: icon.height
|
||||
implicitWidth: icon.width
|
||||
|
||||
MaterialIcon {
|
||||
id: icon
|
||||
|
||||
color: networkItem.modelData.active ? Colors.palette.m3primary : Colors.palette.m3onSurfaceVariant
|
||||
font.pointSize: Tokens.font.size.large
|
||||
opacity: networkItem.connecting ? 0 : 1
|
||||
text: Icons.getNetworkIcon(networkItem.modelData.signalStrength * 100, networkItem.isSecure)
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
active: opacity > 0
|
||||
anchors.fill: parent
|
||||
opacity: networkItem.connecting ? 1 : 0
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
sourceComponent: CircularIndicator {
|
||||
bgColor: "transparent"
|
||||
running: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StateLayer {
|
||||
color: networkItem.modelData.active ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
|
||||
|
||||
onClicked: {
|
||||
if (networkItem.isSecure && !networkItem.known) {
|
||||
PopupManager.requestOpen(Qt.resolvedUrl("../../Modules/Bar/Popouts/Network/PSKOverlay.qml"), {
|
||||
network: networkItem.modelData
|
||||
});
|
||||
} else {
|
||||
Network.requestConnect(networkItem.modelData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import ZShell.Config
|
||||
import qs.Services
|
||||
import qs.Components
|
||||
import qs.Modules.Settings.Common
|
||||
|
||||
CustomSwitch {
|
||||
id: toggleItem
|
||||
|
||||
readonly property alias bg: bg
|
||||
property alias first: bg.first
|
||||
property alias last: bg.last
|
||||
property string subtext
|
||||
|
||||
Layout.fillWidth: true
|
||||
cLayer: 2
|
||||
horizontalPadding: Tokens.padding.largeIncreased
|
||||
implicitHeight: Math.max(implicitContentHeight, implicitIndicatorHeight) + verticalPadding * 2
|
||||
implicitWidth: implicitContentWidth + implicitIndicatorWidth + horizontalPadding * 2
|
||||
indicator.anchors.right: right
|
||||
indicator.anchors.rightMargin: toggleItem.horizontalPadding
|
||||
indicator.anchors.verticalCenter: verticalCenter
|
||||
verticalPadding: Tokens.padding.normal
|
||||
|
||||
background: ConnectedRect {
|
||||
id: bg
|
||||
|
||||
StateLayer {
|
||||
id: stateLayer
|
||||
|
||||
manualPressOverride: toggleItem.pressed
|
||||
}
|
||||
}
|
||||
contentItem: Item {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: toggleItem.horizontalPadding
|
||||
anchors.right: toggleItem.indicator.left
|
||||
anchors.rightMargin: Tokens.spacing.normal
|
||||
implicitHeight: column.implicitHeight
|
||||
implicitWidth: column.implicitWidth
|
||||
|
||||
ColumnLayout {
|
||||
id: column
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 0
|
||||
|
||||
CustomText {
|
||||
id: label
|
||||
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
font.pointSize: Tokens.font.size.smaller
|
||||
text: toggleItem.text
|
||||
}
|
||||
|
||||
CustomText {
|
||||
id: subtext
|
||||
|
||||
Layout.fillWidth: true
|
||||
color: Colors.palette.m3outline
|
||||
elide: Text.ElideRight
|
||||
font.pointSize: Tokens.font.size.small
|
||||
text: toggleItem.subtext
|
||||
visible: toggleItem.subtext
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onPressed: stateLayer.press(stateLayer.mouseX, stateLayer.mouseY)
|
||||
}
|
||||
@@ -206,6 +206,15 @@ GridLayout {
|
||||
}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: "network"
|
||||
|
||||
delegate: EntryWrapper {
|
||||
NetworkWidget {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: "media"
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import ZShell.Config
|
||||
import qs.Components
|
||||
import qs.Services
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property Item bar
|
||||
required property ScreenState screenState
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
CustomRect {
|
||||
anchors.fill: parent
|
||||
anchors.margins: -1
|
||||
color: Colors.palette.m3surface
|
||||
layer.enabled: true
|
||||
|
||||
layer.effect: MultiEffect {
|
||||
maskEnabled: true
|
||||
maskInverted: true
|
||||
maskSource: mask
|
||||
maskSpreadAtMin: 1
|
||||
maskThresholdMin: 0.5
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: mask
|
||||
|
||||
anchors.fill: parent
|
||||
layer.enabled: true
|
||||
visible: false
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Config.bar.border + 1
|
||||
anchors.topMargin: root.bar.implicitHeight + 1
|
||||
radius: Config.bar.border > 0 ? Config.bar.rounding : 0
|
||||
topLeftRadius: Config.bar.rounding
|
||||
topRightRadius: Config.bar.rounding
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -105,28 +105,6 @@ WidgetBase {
|
||||
}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: "bluetooth"
|
||||
|
||||
delegate: EntryWrapper {
|
||||
name: "bluetooth"
|
||||
|
||||
BluetoothWidget {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: "network"
|
||||
|
||||
delegate: EntryWrapper {
|
||||
name: "network"
|
||||
|
||||
NetworkWidget {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: "power"
|
||||
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import ZShell.Config
|
||||
import qs.Services
|
||||
import qs.Components
|
||||
|
||||
MaterialIcon {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
animate: true
|
||||
color: Colors.palette.m3onSurface // Network.connected ? root.textColor : Colors.palette.m3error
|
||||
fill: 1
|
||||
font.pointSize: Tokens.font.size.larger
|
||||
text: "bluetooth"
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import ZShell.Config
|
||||
import qs.Services
|
||||
import qs.Components
|
||||
|
||||
MaterialIcon {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
animate: true
|
||||
color: Colors.palette.m3onSurface // Network.connected ? root.textColor : Colors.palette.m3error
|
||||
fill: 1
|
||||
font.pointSize: Tokens.font.size.larger
|
||||
text: "android_wifi_4_bar"
|
||||
}
|
||||
@@ -134,7 +134,7 @@ Item {
|
||||
node: Audio.source
|
||||
}
|
||||
|
||||
ClipRect {
|
||||
CustomClippingRect {
|
||||
anchors.fill: parent
|
||||
radius: parent.radius
|
||||
|
||||
@@ -249,7 +249,7 @@ Item {
|
||||
node: appBox.modelData
|
||||
}
|
||||
|
||||
ClipRect {
|
||||
CustomClippingRect {
|
||||
anchors.fill: parent
|
||||
radius: parent.radius
|
||||
|
||||
|
||||
@@ -1,385 +0,0 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQml.Models
|
||||
import Quickshell
|
||||
import Quickshell.Bluetooth
|
||||
import ZShell.Components
|
||||
import ZShell.Config
|
||||
import qs.Services
|
||||
import qs.Components
|
||||
import qs.Helpers
|
||||
|
||||
CustomClippingRect {
|
||||
id: root
|
||||
|
||||
readonly property list<var> btConnected: Bluetooth.devices.values.filter(d => d.connected)
|
||||
|
||||
implicitHeight: layout.implicitHeight + layout.anchors.margins * 2
|
||||
implicitWidth: 500
|
||||
|
||||
ColumnLayout {
|
||||
id: layout
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Tokens.padding.larger
|
||||
spacing: Tokens.spacing.extraSmall / 2
|
||||
|
||||
CustomText {
|
||||
Layout.rightMargin: Tokens.padding.extraSmall
|
||||
Layout.topMargin: Tokens.padding.normal
|
||||
font.pointSize: Tokens.font.size.large
|
||||
text: qsTr("Bluetooth")
|
||||
}
|
||||
|
||||
Toggle {
|
||||
checked: Bluetooth.defaultAdapter?.enabled ?? false // qmllint disable unresolved-type
|
||||
first: true
|
||||
subtext: qsTr("Toggle bluetooth device state")
|
||||
text: checked ? qsTr("Enabled") : qsTr("Disabled")
|
||||
|
||||
onToggled: {
|
||||
const adapter = Bluetooth.defaultAdapter; // qmllint disable unresolved-type
|
||||
if (adapter)
|
||||
adapter.enabled = checked;
|
||||
}
|
||||
}
|
||||
|
||||
Toggle {
|
||||
checked: Bluetooth.defaultAdapter?.discovering ?? false // qmllint disable unresolved-type
|
||||
last: root.btConnected.length === 0
|
||||
subtext: qsTr("Enable scanning for new devices")
|
||||
text: qsTr("Discover")
|
||||
|
||||
onToggled: {
|
||||
const adapter = Bluetooth.defaultAdapter; // qmllint disable unresolved-type
|
||||
if (adapter)
|
||||
adapter.discovering = checked;
|
||||
}
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: rows
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: contentHeight
|
||||
interactive: false
|
||||
section.property: "connected"
|
||||
spacing: Tokens.spacing.extraSmall / 2
|
||||
|
||||
add: Transition {
|
||||
Anim {
|
||||
from: 0
|
||||
property: "opacity"
|
||||
to: 1
|
||||
}
|
||||
}
|
||||
delegate: DelegateChooser {
|
||||
role: "isDivider"
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: true
|
||||
|
||||
delegate: StatusDivider {
|
||||
}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
delegate: BluetoothItem {
|
||||
list: rows
|
||||
width: rows.width
|
||||
}
|
||||
}
|
||||
}
|
||||
displaced: Transition {
|
||||
Anim {
|
||||
easing.type: Easing.OutCubic
|
||||
property: "y"
|
||||
}
|
||||
}
|
||||
model: ScriptModel {
|
||||
objectProp: "address"
|
||||
values: {
|
||||
const byName = (a, b) => a.name.localeCompare(b.name);
|
||||
return [...[...Bluetooth.devices.values].filter(d => d.connected).sort(byName).slice(0, 5),
|
||||
{
|
||||
isDivider: true
|
||||
},
|
||||
...[...Bluetooth.devices.values].filter(d => !d.connected).sort(byName).slice(0, 5)];
|
||||
}
|
||||
}
|
||||
move: Transition {
|
||||
Anim {
|
||||
easing.type: Easing.OutCubic
|
||||
property: "y"
|
||||
}
|
||||
}
|
||||
remove: Transition {
|
||||
Anim {
|
||||
property: "opacity"
|
||||
to: 0
|
||||
}
|
||||
}
|
||||
section.delegate: Rectangle {
|
||||
required property string section
|
||||
|
||||
color: "transparent"
|
||||
height: childrenRect.height
|
||||
width: ListView.view.width
|
||||
|
||||
CustomText {
|
||||
text: parent.section
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component BluetoothItem: ConnectedRect {
|
||||
id: btItem
|
||||
|
||||
readonly property int connectedCount: root.btConnected.length
|
||||
readonly property bool connecting: Network.connectingNetwork === modelData
|
||||
property int horizontalPadding: Tokens.padding.largeIncreased
|
||||
readonly property bool inConnectedGroup: index < connectedCount
|
||||
required property int index
|
||||
required property ListView list
|
||||
readonly property bool loading: modelData.state === BluetoothDeviceState.Connecting || modelData.state === BluetoothDeviceState.Disconnecting // qmllint disable unresolved-type
|
||||
|
||||
required property BluetoothDevice modelData
|
||||
property int verticalPadding: Tokens.padding.smaller
|
||||
|
||||
first: !inConnectedGroup && index === connectedCount + 1
|
||||
implicitHeight: rowLayout.implicitHeight + verticalPadding * 2
|
||||
last: inConnectedGroup ? index === connectedCount - 1 : index === list.count - 1
|
||||
radius: Tokens.rounding.small
|
||||
|
||||
Timer {
|
||||
interval: 50
|
||||
running: btItem.ListView.delayRemove
|
||||
|
||||
onTriggered: btItem.ListView.delayRemove = false
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onConnectedChanged(): void {
|
||||
if (!btItem.modelData.connected)
|
||||
btItem.ListView.delayRemove = true;
|
||||
}
|
||||
|
||||
function onPairedChanged(): void {
|
||||
if (btItem.modelData.paired)
|
||||
btItem.modelData.connect();
|
||||
}
|
||||
|
||||
target: btItem.modelData
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: rowLayout
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: btItem.horizontalPadding
|
||||
anchors.rightMargin: btItem.horizontalPadding
|
||||
|
||||
Column {
|
||||
id: column
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Tokens.spacing.extraSmall
|
||||
Layout.rightMargin: Tokens.spacing.extraSmall
|
||||
elide: Text.ElideRight
|
||||
text: btItem.modelData.name
|
||||
}
|
||||
|
||||
CustomText {
|
||||
id: subtext
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
color: Colors.palette.m3outline
|
||||
elide: Text.ElideRight
|
||||
font.pointSize: Tokens.font.size.small
|
||||
text: btItem.modelData.connected ? (btItem.modelData.batteryAvailable ? qsTr("%1 battery left").arg(Math.round(btItem.modelData.battery * 100) + "%") : qsTr("Battery status unknown")) : btItem.modelData.address
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
implicitHeight: icon.height
|
||||
implicitWidth: icon.width
|
||||
|
||||
MaterialIcon {
|
||||
id: icon
|
||||
|
||||
font.pointSize: Tokens.font.size.large
|
||||
opacity: btItem.connecting ? 0 : 1
|
||||
text: btItem.modelData.batteryAvailable ? Icons.getBatteryIcon(btItem.modelData.battery) : "battery_unknown"
|
||||
visible: btItem.modelData.state === BluetoothDeviceState.Connected // qmllint disable unresolved-type
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
active: opacity > 0
|
||||
anchors.fill: parent
|
||||
opacity: btItem.connecting ? 1 : 0
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
sourceComponent: CircularIndicator {
|
||||
bgColor: "transparent"
|
||||
running: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ButtonRow {
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
IconTextButton {
|
||||
font.pointSize: Tokens.font.size.small
|
||||
icon: btItem.modelData.connected ? "link_off" : btItem.modelData.paired ? "link" : "add_link"
|
||||
text: btItem.modelData.connected ? qsTr("Disconnect") : btItem.modelData.paired ? qsTr("Connect") : qsTr("Pair")
|
||||
|
||||
onClicked: {
|
||||
if (btItem.modelData.connected)
|
||||
btItem.modelData.disconnect();
|
||||
else if (btItem.modelData.paired)
|
||||
btItem.modelData.connect();
|
||||
else if (!btItem.modelData.paired)
|
||||
btItem.modelData.pair();
|
||||
else if (btItem.modelData.pairing)
|
||||
btItem.modelData.cancelPair();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
component ConnectedRect: CustomRect {
|
||||
id: bg
|
||||
|
||||
property bool first
|
||||
property bool last
|
||||
|
||||
bottomLeftRadius: last ? Tokens.rounding.large : Tokens.rounding.extraSmall
|
||||
bottomRightRadius: last ? Tokens.rounding.large : Tokens.rounding.extraSmall
|
||||
color: Colors.tPalette.m3surfaceContainer
|
||||
topLeftRadius: first ? Tokens.rounding.large : Tokens.rounding.extraSmall
|
||||
topRightRadius: first ? Tokens.rounding.large : Tokens.rounding.extraSmall
|
||||
|
||||
Behavior on bottomLeftRadius {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
Behavior on bottomRightRadius {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
Behavior on topLeftRadius {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
Behavior on topRightRadius {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
}
|
||||
component StatusDivider: CustomText {
|
||||
bottomPadding: Tokens.spacing.extraSmall
|
||||
color: Colors.palette.m3onSurfaceVariant
|
||||
font.pointSize: Tokens.font.size.smaller
|
||||
text: {
|
||||
const devices = Bluetooth.devices.values; // qmllint disable unresolved-type
|
||||
const connectedCount = devices.filter(d => d.connected).length;
|
||||
let available = qsTr("%1 device%2 available").arg(devices.length - connectedCount).arg(devices.length === 1 ? "" : "s");
|
||||
return available;
|
||||
}
|
||||
topPadding: Tokens.spacing.small
|
||||
}
|
||||
component Toggle: CustomSwitch {
|
||||
id: toggleItem
|
||||
|
||||
readonly property alias bg: bg
|
||||
property alias first: bg.first
|
||||
property alias last: bg.last
|
||||
property string subtext
|
||||
|
||||
Layout.fillWidth: true
|
||||
cLayer: 2
|
||||
horizontalPadding: Tokens.padding.largeIncreased
|
||||
implicitHeight: Math.max(implicitContentHeight, implicitIndicatorHeight) + verticalPadding * 2
|
||||
implicitWidth: implicitContentWidth + implicitIndicatorWidth + horizontalPadding * 2
|
||||
indicator.anchors.right: right
|
||||
indicator.anchors.rightMargin: toggleItem.horizontalPadding
|
||||
indicator.anchors.verticalCenter: verticalCenter
|
||||
verticalPadding: Tokens.padding.normal
|
||||
|
||||
background: ConnectedRect {
|
||||
id: bg
|
||||
|
||||
StateLayer {
|
||||
id: stateLayer
|
||||
|
||||
manualPressOverride: toggleItem.pressed
|
||||
}
|
||||
}
|
||||
contentItem: Item {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: toggleItem.horizontalPadding
|
||||
anchors.right: toggleItem.indicator.left
|
||||
anchors.rightMargin: Tokens.spacing.normal
|
||||
implicitHeight: column.implicitHeight
|
||||
implicitWidth: column.implicitWidth
|
||||
|
||||
Column {
|
||||
id: column
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 0
|
||||
|
||||
CustomText {
|
||||
id: label
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
elide: Text.ElideRight
|
||||
font: {
|
||||
const f = Qt.font(label.font);
|
||||
f.pointSize = Tokens.font.size.smaller;
|
||||
return f;
|
||||
}
|
||||
text: toggleItem.text
|
||||
}
|
||||
|
||||
CustomText {
|
||||
id: subtext
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
color: Colors.palette.m3outline
|
||||
elide: Text.ElideRight
|
||||
font: {
|
||||
const f = Qt.font(subtext.font);
|
||||
f.pointSize = Tokens.font.size.small;
|
||||
return f;
|
||||
}
|
||||
text: toggleItem.subtext
|
||||
visible: toggleItem.subtext
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onPressed: stateLayer.press(stateLayer.mouseX, stateLayer.mouseY)
|
||||
}
|
||||
}
|
||||
@@ -82,13 +82,6 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Popout {
|
||||
name: "bluetooth"
|
||||
|
||||
sourceComponent: BluetoothPopout {
|
||||
}
|
||||
}
|
||||
|
||||
Popout {
|
||||
name: "updates"
|
||||
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
import QtQuick
|
||||
import ZShell.Config
|
||||
import qs.Services
|
||||
import qs.Helpers.Network
|
||||
import qs.Components
|
||||
|
||||
CustomRect {
|
||||
id: root
|
||||
|
||||
readonly property bool closing: PopupManager.closed
|
||||
readonly property url currentPopup: PopupManager.currentPopup
|
||||
property bool shouldBeVisible: loader.status === Loader.Ready
|
||||
|
||||
color: Qt.alpha(Colors.palette.m3shadow, 0.3)
|
||||
opacity: shouldBeVisible && !closing ? 1 : 0
|
||||
visible: opacity > 0
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if (!visible) {
|
||||
PopupManager.requestClose();
|
||||
PopupManager.currentPopup = "";
|
||||
}
|
||||
}
|
||||
|
||||
CustomMouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
preventStealing: true
|
||||
propagateComposedEvents: false
|
||||
|
||||
onClicked: {
|
||||
const insideItemWidth = mouseX < loader.x + loader.item.width && mouseX > loader.x;
|
||||
const insideItemHeight = mouseY < loader.y + loader.item.height && mouseY > loader.y;
|
||||
if (insideItemHeight && insideItemWidth)
|
||||
return;
|
||||
|
||||
PopupManager.requestClose();
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: loader
|
||||
|
||||
anchors.centerIn: parent
|
||||
|
||||
Connections {
|
||||
function onCurrentPopupChanged(): void {
|
||||
if (PopupManager.currentPopup) {
|
||||
loader.setSource(PopupManager.currentPopup, PopupManager.currentPopupProps);
|
||||
} else {
|
||||
loader.source = null;
|
||||
}
|
||||
}
|
||||
|
||||
target: PopupManager
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onNetworkConnected(): void {
|
||||
PopupManager.requestClose();
|
||||
}
|
||||
|
||||
target: Network
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import ZShell.Components
|
||||
import ZShell.Config
|
||||
import qs.Services
|
||||
import qs.Components
|
||||
import qs.Helpers.Network
|
||||
|
||||
CustomClippingRect {
|
||||
id: root
|
||||
|
||||
required property var network
|
||||
|
||||
color: Colors.palette.m3surfaceContainer
|
||||
implicitHeight: layout.implicitHeight + layout.anchors.margins * 2
|
||||
implicitWidth: layout.implicitWidth + layout.anchors.margins * 2
|
||||
radius: Tokens.rounding.large
|
||||
|
||||
ColumnLayout {
|
||||
id: layout
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Tokens.padding.extraLarge
|
||||
spacing: Tokens.spacing.medium
|
||||
|
||||
MaterialIcon {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
font.pointSize: Tokens.font.size.extraLarge * 2
|
||||
text: "lock"
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
color: Colors.palette.m3onSurfaceVariant
|
||||
text: qsTr("Connect to %1").arg(root.network.name)
|
||||
}
|
||||
|
||||
CustomTextField {
|
||||
id: passwordField
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredWidth: 400
|
||||
echoMode: CustomTextField.Password
|
||||
errorText: qsTr("Failed to connect with given password")
|
||||
isError: Network.failedNetwork
|
||||
leadingIcon: "lock"
|
||||
passwordMaskDelay: 5000
|
||||
placeholderText: qsTr("Input password")
|
||||
}
|
||||
|
||||
ButtonRow {
|
||||
id: buttonRow
|
||||
|
||||
Layout.fillWidth: true
|
||||
spacing: Tokens.spacing.extraSmall
|
||||
|
||||
IconTextButton {
|
||||
fillWidth: true
|
||||
font.pointSize: Tokens.font.size.normal
|
||||
icon: "close"
|
||||
inactiveColor: Colors.layer(Colors.palette.m3surfaceContainerHighest, 2)
|
||||
inactiveOnColor: Colors.palette.m3onSurfaceVariant
|
||||
isRound: true
|
||||
isToggle: false
|
||||
shapeMorph: true
|
||||
text: "Cancel"
|
||||
|
||||
onClicked: PopupManager.requestClose()
|
||||
}
|
||||
|
||||
IconTextButton {
|
||||
fillWidth: true
|
||||
font.pointSize: Tokens.font.size.normal
|
||||
icon: Network.connectingNetwork ? "" : "check"
|
||||
inactiveColor: Network.connectingNetwork ? Colors.palette.m3primaryContainer : Network.failedNetwork ? Colors.palette.m3error : Colors.palette.m3primary
|
||||
inactiveOnColor: Network.connectingNetwork ? Colors.palette.m3onPrimaryContainer : Network.failedNetwork ? Colors.palette.m3onError : Colors.palette.m3onPrimary
|
||||
isRound: true
|
||||
isToggle: false
|
||||
shapeMorph: true
|
||||
text: Network.connectingNetwork ? "" : "Apply"
|
||||
|
||||
onClicked: Network.requestConnect(root.network, passwordField.text)
|
||||
|
||||
Loader {
|
||||
active: opacity > 0
|
||||
anchors.centerIn: parent
|
||||
opacity: Network.connectingNetwork ? 1 : 0
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
sourceComponent: CircularIndicator {
|
||||
bgColor: "transparent"
|
||||
implicitSize: buttonRow.height - Tokens.padding.normal
|
||||
running: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
pragma Singleton
|
||||
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property bool closed: true
|
||||
property url currentPopup: ""
|
||||
property var currentPopupProps: ({})
|
||||
|
||||
function requestClose(): void {
|
||||
closed = true;
|
||||
}
|
||||
|
||||
function requestOpen(source: url, properties = {}): void {
|
||||
currentPopupProps = properties;
|
||||
currentPopup = source;
|
||||
closed = false;
|
||||
}
|
||||
}
|
||||
@@ -1,142 +1,36 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import Quickshell
|
||||
import Quickshell.Networking
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import ZShell.Components
|
||||
import ZShell.Config
|
||||
import qs.Services
|
||||
import qs.Modules.Settings.Common
|
||||
import qs.Modules.Bar.Popouts.Network
|
||||
import qs.Components
|
||||
import qs.Helpers.Network
|
||||
import ZShell.Config
|
||||
import qs.Modules
|
||||
import qs.Helpers as Helpers
|
||||
|
||||
CustomClippingRect {
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property var wrapper
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
implicitHeight: networkPopContent.height + networkPopContent.anchors.margins * 2
|
||||
implicitWidth: 500 + 8 * 2
|
||||
radius: (20 - Tokens.padding.small) * Tokens.rounding.scale
|
||||
|
||||
Component.onCompleted: Network.active = true
|
||||
Component.onDestruction: Network.active = false
|
||||
|
||||
ColumnLayout {
|
||||
id: networkPopContent
|
||||
id: layout
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.margins: Tokens.padding.large
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
spacing: Tokens.spacing.extraSmall / 2
|
||||
spacing: 8
|
||||
|
||||
CustomText {
|
||||
Layout.preferredHeight: visible ? implicitHeight : 0
|
||||
Layout.rightMargin: Tokens.padding.extraSmall
|
||||
font.pointSize: Tokens.font.size.large
|
||||
text: qsTr("Wifi")
|
||||
}
|
||||
Repeater {
|
||||
model: Helpers.Network.devices
|
||||
|
||||
Toggle {
|
||||
Layout.preferredHeight: visible ? implicitHeight : 0
|
||||
checked: Network.wifiEnabled
|
||||
first: true
|
||||
last: Network.connectedNetworks.length === 0
|
||||
subtext: qsTr("Toggle WiFi device state")
|
||||
text: checked ? qsTr("Enabled") : qsTr("Disabled")
|
||||
CustomRadioButton {
|
||||
id: network
|
||||
|
||||
onToggled: Network.setWifi(checked)
|
||||
}
|
||||
required property NetworkDevice modelData
|
||||
|
||||
ListView {
|
||||
id: rows
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: contentHeight
|
||||
interactive: false
|
||||
section.property: "connected"
|
||||
spacing: Tokens.spacing.extraSmall / 2
|
||||
|
||||
add: Transition {
|
||||
Anim {
|
||||
from: 0
|
||||
property: "opacity"
|
||||
to: 1
|
||||
}
|
||||
}
|
||||
delegate: DelegateChooser {
|
||||
role: "isDivider"
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: true
|
||||
|
||||
delegate: NetworkDivider {
|
||||
}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
delegate: NetworkItem {
|
||||
list: rows
|
||||
width: rows.width
|
||||
}
|
||||
}
|
||||
}
|
||||
displaced: Transition {
|
||||
Anim {
|
||||
easing.type: Easing.OutCubic
|
||||
property: "y"
|
||||
}
|
||||
}
|
||||
model: ScriptModel {
|
||||
values: {
|
||||
const byStrengthName = (a, b) => b.signalStrength - a.signalStrength || a.name.localeCompare(b.name);
|
||||
const cNetworks = [...Network.connectedNetworks];
|
||||
const kNetworks = [...Network.knownNetworks];
|
||||
const uNetworks = [...Network.unknownNetworks];
|
||||
|
||||
return [...cNetworks.sort(byStrengthName), ...(kNetworks.length > 0 ? [
|
||||
{
|
||||
isDivider: true,
|
||||
type: "known"
|
||||
},
|
||||
...kNetworks.sort(byStrengthName)] : []), ...(uNetworks.length > 0 ? [
|
||||
{
|
||||
isDivider: true,
|
||||
type: "unknown"
|
||||
},
|
||||
...uNetworks.sort(byStrengthName)] : [])];
|
||||
}
|
||||
}
|
||||
move: Transition {
|
||||
Anim {
|
||||
easing.type: Easing.OutCubic
|
||||
property: "y"
|
||||
}
|
||||
}
|
||||
remove: Transition {
|
||||
Anim {
|
||||
property: "opacity"
|
||||
to: 0
|
||||
}
|
||||
}
|
||||
section.delegate: Rectangle {
|
||||
required property string section
|
||||
|
||||
color: "transparent"
|
||||
height: childrenRect.height
|
||||
width: ListView.view.width
|
||||
|
||||
CustomText {
|
||||
text: parent.section
|
||||
}
|
||||
checked: Helpers.Network.activeDevice?.name === modelData.name
|
||||
text: modelData.description
|
||||
visible: modelData.name !== "lo"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NetworkOverlay {
|
||||
anchors.fill: parent
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ Item {
|
||||
searchField.forceActiveFocus();
|
||||
}
|
||||
|
||||
ClipRect {
|
||||
CustomClippingRect {
|
||||
id: search
|
||||
|
||||
anchors.left: parent.left
|
||||
@@ -54,7 +54,7 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
ClipRect {
|
||||
CustomClippingRect {
|
||||
id: preview
|
||||
|
||||
anchors.bottom: parent.bottom
|
||||
@@ -228,7 +228,7 @@ Item {
|
||||
anchors.fill: parent
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
ClipRect {
|
||||
CustomClippingRect {
|
||||
id: textRect
|
||||
|
||||
Layout.fillHeight: true
|
||||
|
||||
@@ -14,7 +14,7 @@ Row {
|
||||
padding: 20
|
||||
spacing: 12
|
||||
|
||||
ClipRect {
|
||||
CustomClippingRect {
|
||||
color: Colors.layer(Colors.palette.m3surfaceContainerHigh, 2)
|
||||
implicitHeight: info.implicitHeight
|
||||
implicitWidth: info.implicitHeight
|
||||
|
||||
@@ -16,7 +16,7 @@ Item {
|
||||
implicitWidth: 1000
|
||||
implicitHeight: 320
|
||||
|
||||
ClipRect {
|
||||
CustomClippingRect {
|
||||
anchors.fill: parent
|
||||
radius: root.radius
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ CustomListView {
|
||||
anchors.bottom: parent.bottom
|
||||
spacing: Tokens.spacing.extraSmall / 2
|
||||
|
||||
ClipRect {
|
||||
CustomClippingRect {
|
||||
id: iconClip
|
||||
|
||||
Layout.fillHeight: true
|
||||
|
||||
@@ -25,7 +25,7 @@ Item {
|
||||
onClicked: root.close()
|
||||
}
|
||||
|
||||
ClipRect {
|
||||
CustomClippingRect {
|
||||
id: popupBackground
|
||||
readonly property real padding: 4
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ Item {
|
||||
visible: false
|
||||
z: 999
|
||||
|
||||
ClipRect {
|
||||
CustomClippingRect {
|
||||
id: popupBackground
|
||||
|
||||
readonly property real padding: Tokens.padding.extraSmall
|
||||
|
||||
@@ -19,7 +19,7 @@ Item {
|
||||
implicitHeight: tileHeight + Tokens.padding.large * 2
|
||||
implicitWidth: tileWidth
|
||||
|
||||
ClipRect {
|
||||
CustomClippingRect {
|
||||
id: image
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
@@ -55,7 +55,7 @@ ColumnLayout {
|
||||
text: Time.format("dddd, d MMMM yyyy")
|
||||
}
|
||||
|
||||
ClipRect {
|
||||
CustomClippingRect {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.topMargin: Tokens.spacing.largeIncreased * 2
|
||||
color: Colors.tPalette.m3surfaceContainer
|
||||
|
||||
@@ -5,7 +5,7 @@ import qs.Helpers
|
||||
import ZShell.Config
|
||||
import qs.Services
|
||||
|
||||
ClipRect {
|
||||
CustomClippingRect {
|
||||
id: root
|
||||
|
||||
required property var lock
|
||||
|
||||
@@ -2,12 +2,12 @@ import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import ZShell.Components
|
||||
import ZShell.Config
|
||||
import qs.Components
|
||||
import ZShell.Config
|
||||
import qs.Helpers
|
||||
import qs.Services
|
||||
|
||||
ClipRect {
|
||||
CustomClippingRect {
|
||||
id: root
|
||||
|
||||
required property var lock
|
||||
|
||||
@@ -24,11 +24,12 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
color: Colors.palette.m3outline
|
||||
elide: Text.ElideRight
|
||||
font: Tokens.font.mono.builders.small.weight(Font.Medium).build()
|
||||
font.family: Config.appearance.font.family.mono
|
||||
font.weight: 500
|
||||
text: NotifServer.list.length > 0 ? qsTr("%1 notification%2").arg(NotifServer.list.length).arg(NotifServer.list.length === 1 ? "" : "s") : qsTr("Notifications")
|
||||
}
|
||||
|
||||
ClipRect {
|
||||
ClippingRectangle {
|
||||
id: clipRect
|
||||
|
||||
Layout.fillHeight: true
|
||||
|
||||
@@ -85,7 +85,7 @@ CustomRect {
|
||||
}
|
||||
}
|
||||
|
||||
ClipRect {
|
||||
ClippingRectangle {
|
||||
anchors.fill: parent
|
||||
color: root.urgency === "critical" ? Colors.palette.m3error : root.urgency === "low" ? Colors.layer(Colors.palette.m3surfaceContainerHighest, 3) : Colors.palette.m3secondaryContainer
|
||||
radius: Tokens.rounding.full
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import QtQuick
|
||||
import qs.Components
|
||||
import qs.Paths
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
ClipRect {
|
||||
ClippingRectangle {
|
||||
anchors.fill: parent
|
||||
radius: Tokens.rounding.full
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ CustomRect {
|
||||
visible: root.hasImage || root.hasAppIcon
|
||||
width: Config.notifs.sizes.image
|
||||
|
||||
sourceComponent: ClipRect {
|
||||
sourceComponent: CustomClippingRect {
|
||||
color: root.modelData.urgency === NotificationUrgency.Critical ? Colors.palette.m3error : root.modelData.urgency === NotificationUrgency.Low ? Colors.layer(Colors.palette.m3surfaceContainerHighest, 2) : Colors.palette.m3secondaryContainer
|
||||
implicitHeight: Config.notifs.sizes.image
|
||||
implicitWidth: Config.notifs.sizes.image
|
||||
|
||||
@@ -5,7 +5,7 @@ import ZShell.Config
|
||||
import qs.Components
|
||||
import qs.Services
|
||||
|
||||
ClipRect {
|
||||
CustomClippingRect {
|
||||
id: root
|
||||
|
||||
property real animPerc: UPower.displayDevice.percentage
|
||||
|
||||
@@ -19,7 +19,7 @@ CustomRect {
|
||||
implicitWidth: 450
|
||||
radius: Tokens.rounding.largeIncreased
|
||||
|
||||
ClipRect {
|
||||
CustomClippingRect {
|
||||
anchors.fill: parent
|
||||
radius: parent.radius
|
||||
|
||||
|
||||
@@ -149,7 +149,7 @@ ColumnLayout {
|
||||
}
|
||||
}
|
||||
|
||||
CustomFlickable {
|
||||
Flickable {
|
||||
id: flickable
|
||||
|
||||
Layout.fillHeight: true
|
||||
|
||||
@@ -25,7 +25,7 @@ Item {
|
||||
anchors.fill: parent
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
ClipRect {
|
||||
CustomClippingRect {
|
||||
id: imgWrapper
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
@@ -100,13 +100,16 @@ Item {
|
||||
visible: opacity > 0
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {}
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
Behavior on scale {
|
||||
Anim {}
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
Behavior on y {
|
||||
Anim {}
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
@@ -117,8 +120,9 @@ Item {
|
||||
Qt.callLater(syncCropToScreen);
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: selectorLoader
|
||||
ButtonRow {
|
||||
id: screenSelector
|
||||
|
||||
anchors.bottom: imageSlot.top
|
||||
anchors.bottomMargin: Tokens.spacing.medium
|
||||
anchors.left: parent.left
|
||||
@@ -126,32 +130,25 @@ Item {
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Tokens.padding.extraLarge * 2
|
||||
anchors.top: parent.top
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
active: wrapper.screens.length > 1
|
||||
Repeater {
|
||||
model: wrapper.screens
|
||||
|
||||
sourceComponent: ButtonRow {
|
||||
id: screenSelector
|
||||
delegate: TextButton {
|
||||
required property int index
|
||||
readonly property bool isCurrent: wrapper.selectedScreenIndex === index
|
||||
required property var modelData
|
||||
|
||||
spacing: Tokens.spacing.small
|
||||
fillWidth: true
|
||||
font: Tokens.font.body.builders.large.weight(Font.DemiBold).build()
|
||||
inactiveColor: isCurrent ? Colors.palette.m3primary : Qt.alpha(Colors.palette.m3surfaceContainer, 0.7)
|
||||
inactiveOnColor: isCurrent ? Colors.palette.m3onPrimary : Qt.alpha(Colors.palette.m3onSurface, 0.7)
|
||||
isRound: true
|
||||
shapeMorph: true
|
||||
text: modelData.name
|
||||
|
||||
Repeater {
|
||||
model: wrapper.screens
|
||||
|
||||
delegate: TextButton {
|
||||
required property int index
|
||||
readonly property bool isCurrent: wrapper.selectedScreenIndex === index
|
||||
required property var modelData
|
||||
|
||||
fillWidth: true
|
||||
font: Tokens.font.body.builders.large.weight(Font.DemiBold).build()
|
||||
inactiveColor: isCurrent ? Colors.palette.m3primary : Qt.alpha(Colors.palette.m3surfaceContainer, 0.7)
|
||||
inactiveOnColor: isCurrent ? Colors.palette.m3onPrimary : Qt.alpha(Colors.palette.m3onSurface, 0.7)
|
||||
isRound: true
|
||||
shapeMorph: true
|
||||
text: modelData.name
|
||||
|
||||
onClicked: wrapper.selectScreen(index)
|
||||
}
|
||||
onClicked: wrapper.selectScreen(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -225,9 +222,9 @@ Item {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: selectorLoader.active ? wrapper.buttonRowHeight + Tokens.spacing.medium : Tokens.spacing.medium
|
||||
anchors.topMargin: wrapper.buttonRowHeight + Tokens.spacing.medium
|
||||
|
||||
ClipRect {
|
||||
CustomClippingRect {
|
||||
id: fitBox
|
||||
|
||||
readonly property real aspect: scaledImg.implicitHeight > 0 ? scaledImg.implicitWidth / scaledImg.implicitHeight : 1
|
||||
@@ -360,7 +357,8 @@ Item {
|
||||
width: Math.round(baseWidth / zoom)
|
||||
|
||||
Behavior on color {
|
||||
CAnim {}
|
||||
CAnim {
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
|
||||
@@ -38,7 +38,6 @@ Item {
|
||||
}
|
||||
|
||||
BlobInvertedRect {
|
||||
id: blobRect
|
||||
anchors.fill: parent
|
||||
borderBottom: Tokens.padding.small
|
||||
borderLeft: navPane.width + navPane.anchors.margins * 2
|
||||
@@ -66,7 +65,6 @@ Item {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: navPane.right
|
||||
anchors.leftMargin: navPane.anchors.margins + anchors.margins
|
||||
anchors.bottomMargin: blobRect.borderBottom
|
||||
anchors.margins: Tokens.padding.extraLarge
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
|
||||
@@ -2,8 +2,8 @@ pragma Singleton
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import ZShell.Config
|
||||
import qs.Components
|
||||
import ZShell.Config
|
||||
import qs.Modules.Settings.Common
|
||||
import qs.Modules.Settings.Pages
|
||||
import qs.Modules.Settings.Pages.Wallpaper
|
||||
@@ -13,7 +13,6 @@ import qs.Modules.Settings.Pages.Panels
|
||||
import qs.Modules.Settings.Pages.Panels.Bar
|
||||
import qs.Modules.Settings.Pages.Panels.Sidebar
|
||||
import qs.Modules.Settings.Pages.Services
|
||||
import qs.Modules.Settings.Pages.Network
|
||||
import qs.Services
|
||||
|
||||
QtObject {
|
||||
@@ -48,27 +47,7 @@ QtObject {
|
||||
|
||||
// Connectivity
|
||||
Component {
|
||||
StackPage {
|
||||
Component {
|
||||
NetworkPage {}
|
||||
}
|
||||
|
||||
Component {
|
||||
AllNetworks {}
|
||||
}
|
||||
|
||||
Component {
|
||||
SavedNetworks {}
|
||||
}
|
||||
|
||||
Component {
|
||||
AddNetwork {}
|
||||
}
|
||||
|
||||
Component {
|
||||
OldNetwork {}
|
||||
}
|
||||
}
|
||||
PlaceholderComp {}
|
||||
},
|
||||
Component {
|
||||
PlaceholderComp {}
|
||||
|
||||
@@ -1,256 +0,0 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import ZShell.Config
|
||||
import ZShell.Internal
|
||||
import ZShell.Blobs
|
||||
import ZShell.Services
|
||||
import ZShell.Components
|
||||
import qs.Components
|
||||
import qs.Modules.Settings.Common
|
||||
|
||||
PageBase {
|
||||
id: root
|
||||
|
||||
property bool connecting: false
|
||||
property bool failed: false
|
||||
|
||||
// Security model: index 0 = none, 1 = WPA/WPA2/WPA3 personal.
|
||||
readonly property bool secured: securitySelect.active !== noneItem
|
||||
property bool success: false
|
||||
|
||||
function submit(): void {
|
||||
const ssid = ssidField.text.trim();
|
||||
if (ssid.length === 0) {
|
||||
ssidField.isError = true;
|
||||
ssidField.forceActiveFocus();
|
||||
return;
|
||||
}
|
||||
if (root.secured && passwordField.text.length < 8) {
|
||||
passwordField.isError = true;
|
||||
passwordField.forceActiveFocus();
|
||||
return;
|
||||
}
|
||||
|
||||
root.failed = false;
|
||||
root.connecting = true;
|
||||
|
||||
Nmcli.addHiddenNetwork(ssid, root.secured ? passwordField.text : "", root.secured ? "wpa" : "none", hiddenToggle.checked, result => {
|
||||
root.connecting = false;
|
||||
if (result && result.success) {
|
||||
root.success = true;
|
||||
root.nState.closeSubPage();
|
||||
} else {
|
||||
root.failed = true;
|
||||
if (root.secured)
|
||||
passwordField.isError = true;
|
||||
// Clean up the half-created profile so a retry starts fresh.
|
||||
Nmcli.forgetNetwork(ssid);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
isSubPage: true
|
||||
title: Tr.tr("Add network")
|
||||
|
||||
ColumnLayout {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
spacing: Tokens.spacing.large
|
||||
width: root.cappedWidth
|
||||
|
||||
Connections {
|
||||
function onSubPageClosed(): void {
|
||||
if (root.success)
|
||||
return;
|
||||
|
||||
const ssid = ssidField.text.trim();
|
||||
if (ssid)
|
||||
Nmcli.forgetNetwork(ssid);
|
||||
}
|
||||
|
||||
target: root.nState
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Tokens.padding.extraSmall
|
||||
color: Colours.palette.m3onSurfaceVariant
|
||||
font: Tokens.font.body.small
|
||||
text: Tr.tr("Enter the details below to manually connect to a network.")
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
CustomTextField {
|
||||
id: ssidField
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Tokens.spacing.extraSmall
|
||||
errorText: Tr.tr("Network name is required")
|
||||
inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText
|
||||
leadingIcon: "wifi"
|
||||
// TRANSLATORS: SSID is a protocol term, leave it untranslated
|
||||
placeholderText: Tr.tr("Network name (SSID)")
|
||||
// TRANSLATORS: sample network name, translate the e.g. but the name is a placeholder
|
||||
supportingText: Tr.tr("e.g. MyHiddenNetwork")
|
||||
|
||||
onAccepted: root.secured ? passwordField.forceActiveFocus() : root.submit()
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
id: hiddenToggle
|
||||
|
||||
checked: true
|
||||
first: true
|
||||
subtext: Tr.tr("Actively probe for a network that doesn't broadcast its name")
|
||||
text: Tr.tr("Hidden network")
|
||||
}
|
||||
|
||||
SelectRow {
|
||||
id: securitySelect
|
||||
|
||||
Layout.topMargin: Tokens.spacing.extraSmall / 2 - parent.spacing
|
||||
fallbackIcon: "lock"
|
||||
// TRANSLATORS: WPA/WPA2/WPA3 are protocol names, leave them untranslated
|
||||
fallbackText: Tr.tr("WPA/WPA2/WPA3 Personal")
|
||||
// label: Tr.tr("Security")
|
||||
last: !root.secured
|
||||
|
||||
Behavior on bottomLeftRadius {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
Behavior on bottomRightRadius {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
menuItems: [
|
||||
MenuItem {
|
||||
icon: "lock"
|
||||
// TRANSLATORS: WPA/WPA2/WPA3 are protocol names, leave them untranslated
|
||||
text: Tr.tr("WPA/WPA2/WPA3 Personal")
|
||||
},
|
||||
MenuItem {
|
||||
id: noneItem
|
||||
|
||||
icon: "lock_open"
|
||||
text: Tr.trCtx("None (open)", "wifi security type")
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.bottomMargin: root.secured ? 0 : -parent.spacing
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: root.secured ? passwordField.implicitHeight : 0
|
||||
opacity: root.secured ? 1 : 0
|
||||
|
||||
Behavior on Layout.bottomMargin {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
Behavior on implicitHeight {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
|
||||
CustomTextField {
|
||||
id: passwordField
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
echoMode: TextInput.Password
|
||||
enabled: root.secured
|
||||
errorText: root.failed ? Tr.tr("Connection failed — check the password") : Tr.tr("Password must be at least 8 characters")
|
||||
leadingIcon: "key"
|
||||
placeholderText: Tr.tr("Password")
|
||||
// TRANSLATORS: WPA is a protocol name, leave it untranslated
|
||||
supportingText: Tr.tr("WPA passwords are at least 8 characters")
|
||||
|
||||
onAccepted: root.submit()
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
Layout.topMargin: Tokens.spacing.extraSmall - parent.spacing
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
TextButton {
|
||||
Layout.fillHeight: true
|
||||
horizontalPadding: Tokens.padding.extraLarge
|
||||
isRound: true
|
||||
text: Tr.trCtx("Cancel", "button")
|
||||
type: TextButton.Tonal
|
||||
|
||||
onClicked: root.nState.closeSubPage()
|
||||
}
|
||||
|
||||
// Connect button — swaps to a loading spinner while connecting,
|
||||
// matching the Wi-Fi list connect animation.
|
||||
ButtonBase {
|
||||
id: connectBtn
|
||||
|
||||
implicitHeight: connectMetrics.height + Tokens.padding.medium * 2
|
||||
implicitWidth: connectMetrics.width + Tokens.padding.extraLarge * 2
|
||||
// inactiveColour: Colours.palette.m3primary
|
||||
// inactiveOnColour: Colours.palette.m3onPrimary
|
||||
isRound: true
|
||||
shapeMorph: true
|
||||
|
||||
// stateLayer.disabled: root.connecting || ssidField.text.trim().length === 0
|
||||
|
||||
onClicked: {
|
||||
if (!root.connecting && ssidField.text.trim().length > 0)
|
||||
root.submit();
|
||||
}
|
||||
|
||||
TextMetrics {
|
||||
id: connectMetrics
|
||||
|
||||
font: connectBtn.font
|
||||
text: Tr.tr("Connect")
|
||||
}
|
||||
|
||||
Anim {
|
||||
id: connectContent
|
||||
|
||||
// anchors.centerIn: parent
|
||||
// inAnimType: Anim.SlowEffects
|
||||
// outAnimType: Anim.SlowEffects
|
||||
// sourceComp: root.connecting ? connectLoadingComp : connectTextComp
|
||||
}
|
||||
|
||||
Component {
|
||||
id: connectLoadingComp
|
||||
|
||||
LoadingIndicator {
|
||||
color: connectBtn.onColour
|
||||
implicitSize: Math.round(Tokens.font.body.medium.pointSize * 1.4)
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: connectTextComp
|
||||
|
||||
CustomText {
|
||||
color: connectBtn.onColour
|
||||
font: connectBtn.font
|
||||
text: connectMetrics.text
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,345 +0,0 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import ZShell.Config
|
||||
import qs.Components
|
||||
import qs.Helpers.Network
|
||||
import qs.Services
|
||||
import qs.Modules.Settings.Common
|
||||
|
||||
PageBase {
|
||||
id: root
|
||||
|
||||
flickable.bottomMargin: Tokens.padding.extraExtraLarge * 2 // Extra scrolling space at the bottom
|
||||
|
||||
isSubPage: true
|
||||
title: Tr.tr("All networks")
|
||||
|
||||
ColumnLayout {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
// spacing: Tokens.spacing.extraSmall / 2
|
||||
width: root.cappedWidth
|
||||
|
||||
Timer {
|
||||
interval: 300 // GlobalConfig.nexus.networkRescanInterval
|
||||
repeat: true
|
||||
running: root.visible && Nmcli.wifiEnabled
|
||||
triggeredOnStart: true
|
||||
|
||||
onTriggered: Nmcli.rescanWifi()
|
||||
}
|
||||
|
||||
ConnectedRect {
|
||||
Layout.fillWidth: true
|
||||
first: true
|
||||
implicitHeight: headerLayout.implicitHeight + Tokens.padding.medium * 2
|
||||
|
||||
RowLayout {
|
||||
id: headerLayout
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Tokens.padding.largeIncreased
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Tokens.padding.large
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.verticalCenterOffset: 1
|
||||
|
||||
// spacing: Tokens.spacing.extraSmall / 2
|
||||
|
||||
CustomText {
|
||||
font: Tokens.font.title.small
|
||||
text: Tr.trCtx("Filters", "network filter heading")
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
FilterButton {
|
||||
id: savedFilter
|
||||
|
||||
function internalFilter(ap: Nmcli.AccessPoint): bool {
|
||||
return Nmcli.hasSavedProfile(ap.ssid);
|
||||
}
|
||||
|
||||
bottomLeftRadius: pressed ? pressedRadius : implicitHeight / 2
|
||||
text: Tr.trCtx("Saved", "network filter")
|
||||
topLeftRadius: pressed ? pressedRadius : implicitHeight / 2
|
||||
|
||||
Behavior on bottomLeftRadius {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
Behavior on topLeftRadius {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FilterButton {
|
||||
id: secureFilter
|
||||
|
||||
function internalFilter(ap: Nmcli.AccessPoint): bool {
|
||||
return ap.security !== "none";
|
||||
}
|
||||
|
||||
text: Tr.trCtx("Secured", "network filter")
|
||||
}
|
||||
|
||||
FilterButton {
|
||||
id: highFreqFilter
|
||||
|
||||
function internalFilter(ap: Nmcli.AccessPoint): bool {
|
||||
return ap.frequency >= 4900 && ap.frequency <= 5900;
|
||||
}
|
||||
|
||||
text: Tr.trCtx("5 GHz", "network filter")
|
||||
}
|
||||
|
||||
FilterButton {
|
||||
id: lowFreqFilter
|
||||
|
||||
function internalFilter(ap: Nmcli.AccessPoint): bool {
|
||||
return ap.frequency >= 2400 && ap.frequency <= 2500;
|
||||
}
|
||||
|
||||
bottomRightRadius: pressed ? pressedRadius : implicitHeight / 2
|
||||
text: Tr.trCtx("2.4 GHz", "network filter")
|
||||
topRightRadius: pressed ? pressedRadius : implicitHeight / 2
|
||||
|
||||
Behavior on bottomRightRadius {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
Behavior on topRightRadius {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NetworkList {
|
||||
id: networkList
|
||||
|
||||
function networkFilter(ap: Nmcli.AccessPoint): bool {
|
||||
return savedFilter.filter(ap) && secureFilter.filter(ap) && highFreqFilter.filter(ap) && lowFreqFilter.filter(ap);
|
||||
}
|
||||
|
||||
enableFilter: true
|
||||
last: true
|
||||
// nState: root.nState
|
||||
}
|
||||
}
|
||||
|
||||
component FilterButton: IconTextButton {
|
||||
id: filter
|
||||
|
||||
property int filterState // 0 = default, 1 = on, 2 = negate
|
||||
|
||||
function filter(ap: Nmcli.AccessPoint): bool {
|
||||
if (filterState === 0)
|
||||
return true;
|
||||
if (filterState === 1)
|
||||
return internalFilter(ap);
|
||||
return !internalFilter(ap);
|
||||
}
|
||||
|
||||
function internalFilter(ap: Nmcli.AccessPoint): bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
defaultRadius: filterState > 0 ? implicitHeight / 2 : Tokens.rounding.small
|
||||
icon: ["check_indeterminate_small", "check", "close"][filterState]
|
||||
// inactiveColour: [Colours.palette.m3secondaryContainer, Colours.palette.m3secondary, Colours.palette.m3tertiary][filterState]
|
||||
// inactiveOnColour: [Colours.palette.m3onSecondaryContainer, Colours.palette.m3onSecondary, Colours.palette.m3onTertiary][filterState]
|
||||
pressedRadius: Tokens.rounding.extraSmall
|
||||
|
||||
// spacing: Tokens.spacing.extraSmall
|
||||
|
||||
onClicked: filterState = (filterState + 1) % 3
|
||||
}
|
||||
component NetworkList: ItemList {
|
||||
id: netList
|
||||
|
||||
property bool enableFilter
|
||||
property int limit: 0 // 0 = show all
|
||||
|
||||
// required property NexusState nState
|
||||
|
||||
signal networkSelected(ap: Nmcli.AccessPoint)
|
||||
|
||||
function networkFilter(ap: Nmcli.AccessPoint): bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
extraHeight: Nmcli.scanning ? Tokens.rounding.extraSmall : 0 // Inline so it isn't affected by anim
|
||||
list.anchors.top: scanningIndicator.bottom
|
||||
placeholderIcon: Nmcli.wifiEnabled ? "wifi_find" : "signal_wifi_off"
|
||||
placeholderText: Nmcli.wifiEnabled ? Tr.tr("No networks found") : Tr.tr("Wi-Fi disabled")
|
||||
showList: Nmcli.wifiEnabled
|
||||
|
||||
delegate: StateLayer {
|
||||
id: network
|
||||
|
||||
property bool currentSelected
|
||||
required property int index
|
||||
required property var modelData
|
||||
property real textOpacity: disabled ? 0.5 : 1
|
||||
|
||||
anchors.fill: undefined
|
||||
anchors.left: netList.list.contentItem.left
|
||||
anchors.right: netList.list.contentItem.right
|
||||
bottomLeftRadius: netList?.last && index === netList.list.count - 1 ? Tokens.rounding.extraLarge : radius
|
||||
bottomRightRadius: netList?.last && index === netList.list.count - 1 ? Tokens.rounding.extraLarge : radius
|
||||
// disabled: currentSelected || Nmcli.connectingSsid() === modelData.ssid
|
||||
implicitHeight: networkLayout.implicitHeight + networkLayout.anchors.margins * 2
|
||||
radius: Tokens.rounding.extraSmall
|
||||
|
||||
Behavior on textOpacity {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
if (!modelData.active) {
|
||||
NetworkConnection.handleConnect(modelData);
|
||||
currentSelected = true;
|
||||
netList.networkSelected(modelData);
|
||||
} else {
|
||||
// Active network: open its detail/settings sub-page.
|
||||
netList.nState.selectedNetworkSsid = modelData.ssid;
|
||||
netList.nState.networkDetailsFromSaved = false;
|
||||
netList.nState.openSubPage(3);
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onActiveChanged(): void {
|
||||
if (network.modelData.active)
|
||||
network.currentSelected = false;
|
||||
}
|
||||
|
||||
target: network.modelData
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onNetworkSelected(ap: Nmcli.AccessPoint): void {
|
||||
if (ap !== network.modelData)
|
||||
network.currentSelected = false;
|
||||
}
|
||||
|
||||
target: netList
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: networkLayout
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Tokens.padding.extraLarge
|
||||
anchors.margins: Tokens.padding.large
|
||||
anchors.rightMargin: Tokens.padding.extraLarge
|
||||
|
||||
// spacing: Tokens.spacing.extraSmall
|
||||
|
||||
MaterialIcon {
|
||||
color: network.modelData.active ? Colours.palette.m3primary : Colours.palette.m3onSurfaceVariant
|
||||
font.pointSize: Tokens.font.size.normal
|
||||
opacity: network.textOpacity
|
||||
text: Icons.getNetworkIcon(network.modelData.strength)
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
opacity: network.textOpacity
|
||||
|
||||
// spacing: 0
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
font: Tokens.font.body.small
|
||||
text: network.modelData.ssid
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
color: Colours.palette.m3outline
|
||||
elide: Text.ElideRight
|
||||
font: Tokens.font.label.small
|
||||
text: {
|
||||
const net = network.modelData;
|
||||
let status = "";
|
||||
if (net.active)
|
||||
status = Tr.trCtx("Connected", "network connected");
|
||||
else if (Nmcli.hasSavedProfile(net.ssid))
|
||||
status = Tr.trCtx("Saved", "network saved");
|
||||
if (status)
|
||||
// TRANSLATORS: %1 = security type, %2 = connection status
|
||||
return Tr.trCtx("Security: %1 • %2", "network security and status").arg(net.security).arg(status);
|
||||
return Tr.trCtx("Security: %1", "network security").arg(net.security);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Anim {
|
||||
// // sourceComp: Nmcli.connectingSsid() === network.modelData.ssid ? loadingComp : iconComp
|
||||
//
|
||||
// Component {
|
||||
// id: iconComp
|
||||
//
|
||||
// MaterialIcon {
|
||||
// color: network.modelData.active ? Colours.palette.m3primary : Colours.palette.m3onSurfaceVariant
|
||||
// fontStyle: Tokens.font.icon.medium
|
||||
// opacity: network.textOpacity
|
||||
// text: network.modelData.active ? "settings" : "lock"
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Component {
|
||||
// id: loadingComp
|
||||
//
|
||||
// LoadingIndicator {
|
||||
// implicitSize: Math.round(Tokens.font.icon.medium.pointSize * 1.3)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
// model: ScriptModel {
|
||||
// values: {
|
||||
// const connecting = Nmcli.connectingSsid();
|
||||
// // Lower rank sorts higher in the list
|
||||
// const rank = n => n.active ? 0 : n.ssid === connecting ? 1 : Nmcli.hasSavedProfile(n.ssid) ? 2 : 3;
|
||||
// const sorted = [...Nmcli.networks].sort((a, b) => rank(a) - rank(b) || b.strength - a.strength);
|
||||
// if (netList.limit > 0 && sorted.length > netList.limit)
|
||||
// sorted.length = netList.limit;
|
||||
// return netList.enableFilter ? sorted.filter(netList.networkFilter) : sorted;
|
||||
// }
|
||||
// }
|
||||
|
||||
// StyledProgressBar {
|
||||
// id: scanningIndicator
|
||||
//
|
||||
// anchors.left: parent.left
|
||||
// anchors.margins: 1
|
||||
// anchors.right: parent.right
|
||||
// implicitHeight: Nmcli.scanning ? Tokens.rounding.extraSmall : 0
|
||||
// indeterminate: true
|
||||
//
|
||||
// Behavior on implicitHeight {
|
||||
// Anim {
|
||||
// type: Anim.DefaultEffects
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import ZShell.Config
|
||||
import qs.Modules.Settings.Common
|
||||
import qs.Modules.Bar.Popouts.Network
|
||||
import qs.Components
|
||||
import qs.Helpers.Network
|
||||
import qs.Services
|
||||
|
||||
PageBase {
|
||||
id: root
|
||||
|
||||
title: qsTr("Begin page")
|
||||
|
||||
ColumnLayout {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
spacing: Tokens.spacing.extraSmall / 2
|
||||
width: root.cappedWidth
|
||||
|
||||
NavRow {
|
||||
first: true
|
||||
icon: "dock_to_bottom"
|
||||
// settingAnchor: "panels-bar"
|
||||
text: qsTr("Show all networks")
|
||||
|
||||
onClicked: root.sState.openSubPage(1)
|
||||
}
|
||||
|
||||
NavRow {
|
||||
icon: "dock_to_bottom"
|
||||
text: qsTr("Saved networks")
|
||||
|
||||
onClicked: root.sState.openSubPage(2)
|
||||
}
|
||||
|
||||
NavRow {
|
||||
icon: "dock_to_bottom"
|
||||
text: qsTr("Add network")
|
||||
|
||||
onClicked: root.sState.openSubPage(3)
|
||||
}
|
||||
|
||||
NavRow {
|
||||
icon: "dock_to_bottom"
|
||||
last: true
|
||||
text: qsTr("Old Network Page")
|
||||
|
||||
onClicked: root.sState.openSubPage(4)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,252 +0,0 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import ZShell.Config
|
||||
import qs.Modules.Settings.Common
|
||||
import qs.Modules.Bar.Popouts.Network
|
||||
import qs.Components
|
||||
import qs.Helpers.Network
|
||||
import qs.Services
|
||||
|
||||
PageBase {
|
||||
id: root
|
||||
|
||||
isSubPage: true
|
||||
title: qsTr("Old network page")
|
||||
|
||||
Component.onCompleted: Network.active = true
|
||||
Component.onDestruction: Network.active = false
|
||||
|
||||
ColumnLayout {
|
||||
id: networkContent
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
spacing: Tokens.spacing.extraSmall / 2
|
||||
width: root.cappedWidth
|
||||
|
||||
Toggle {
|
||||
Layout.preferredHeight: visible ? implicitHeight : 0
|
||||
checked: Network.wifiEnabled
|
||||
first: true
|
||||
last: Network.connectedNetworks.length === 0
|
||||
subtext: qsTr("Toggle WiFi device state")
|
||||
text: checked ? qsTr("Enabled") : qsTr("Disabled")
|
||||
|
||||
onToggled: Network.setWifi(checked)
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: rows
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: contentHeight
|
||||
interactive: false
|
||||
section.property: "connected"
|
||||
spacing: Tokens.spacing.extraSmall / 2
|
||||
|
||||
add: Transition {
|
||||
Anim {
|
||||
from: 0
|
||||
property: "opacity"
|
||||
to: 1
|
||||
}
|
||||
}
|
||||
delegate: DelegateChooser {
|
||||
role: "isDivider"
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: true
|
||||
|
||||
delegate: NetworkDivider {
|
||||
}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: false
|
||||
|
||||
delegate: KnownNetworkManager {
|
||||
}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
delegate: NetworkItem {
|
||||
list: rows
|
||||
width: rows.width
|
||||
}
|
||||
}
|
||||
}
|
||||
displaced: Transition {
|
||||
Anim {
|
||||
easing.type: Easing.OutCubic
|
||||
property: "y"
|
||||
}
|
||||
}
|
||||
model: ScriptModel {
|
||||
values: {
|
||||
const byStrengthName = (a, b) => b.signalStrength - a.signalStrength || a.name.localeCompare(b.name);
|
||||
const cNetworks = [...Network.connectedNetworks];
|
||||
const kNetworks = [...Network.knownNetworks];
|
||||
const uNetworks = [...Network.unknownNetworks];
|
||||
|
||||
return [...cNetworks.sort(byStrengthName), ...(kNetworks.length > 0 ? [
|
||||
{
|
||||
isDivider: true,
|
||||
type: "known"
|
||||
},
|
||||
...kNetworks.sort(byStrengthName),
|
||||
{
|
||||
isDivider: false
|
||||
}
|
||||
] : []), ...(uNetworks.length > 0 ? [
|
||||
{
|
||||
isDivider: true,
|
||||
type: "unknown"
|
||||
},
|
||||
...uNetworks.sort(byStrengthName)] : [])];
|
||||
}
|
||||
}
|
||||
move: Transition {
|
||||
Anim {
|
||||
easing.type: Easing.OutCubic
|
||||
property: "y"
|
||||
}
|
||||
}
|
||||
remove: Transition {
|
||||
Anim {
|
||||
property: "opacity"
|
||||
to: 0
|
||||
}
|
||||
}
|
||||
section.delegate: Rectangle {
|
||||
required property string section
|
||||
|
||||
color: "transparent"
|
||||
height: childrenRect.height
|
||||
width: ListView.view.width
|
||||
|
||||
CustomText {
|
||||
text: parent.section
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NetworkOverlay {
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
|
||||
component KnownNetworkManager: NetworkSelect {
|
||||
acceptLabel: qsTr("Remove")
|
||||
enabled: Object.keys(model).length > 0
|
||||
header: qsTr("Manage known networks")
|
||||
icon: "Network_Manage"
|
||||
label: qsTr("Manage known networks")
|
||||
model: Network.networks.filter(n => n.known && !n.connected).map(n => ({
|
||||
id: n.name,
|
||||
label: n.name
|
||||
}))
|
||||
rootParent: root.flickable
|
||||
width: rows.width
|
||||
|
||||
onAccepted: {
|
||||
if (!selectedItem)
|
||||
return;
|
||||
|
||||
const networkToForget = Network.networks.find(n => n.name === selectedItem);
|
||||
Network.forgetNetwork(networkToForget);
|
||||
}
|
||||
}
|
||||
component NetworkSelect: DialogRowButton {
|
||||
id: klist
|
||||
|
||||
required property var model
|
||||
property var selectedItem
|
||||
|
||||
function keyFor(item: var): string {
|
||||
return item.id;
|
||||
}
|
||||
|
||||
function labelFor(item: var): string {
|
||||
return item.label;
|
||||
}
|
||||
|
||||
acceptAllowed: !!selectedItem
|
||||
horizontalContentMargin: -Tokens.padding.extraSmall
|
||||
separateContent: true
|
||||
|
||||
content: Component {
|
||||
VerticalFadeListView {
|
||||
bottomMargin: Tokens.padding.large
|
||||
model: klist.model
|
||||
spacing: 0
|
||||
topMargin: Tokens.padding.large
|
||||
|
||||
delegate: CustomRect {
|
||||
id: item
|
||||
|
||||
required property var modelData
|
||||
readonly property bool selected: klist.selectedItem === klist.keyFor(modelData)
|
||||
|
||||
anchors.left: ListView.view.contentItem.left
|
||||
anchors.margins: 1 // Gets cut off for some reason without this
|
||||
anchors.right: ListView.view.contentItem.right
|
||||
color: Qt.alpha(Colors.palette.m3tertiaryContainer, selected ? 1 : 0)
|
||||
implicitHeight: label.implicitHeight + Tokens.padding.small * 2
|
||||
radius: stateLayer.pressed ? Tokens.rounding.extraSmall : selected ? Tokens.rounding.largeIncreased : Tokens.rounding.medium
|
||||
|
||||
Behavior on radius {
|
||||
Anim {
|
||||
type: Anim.SlowEffects
|
||||
}
|
||||
}
|
||||
|
||||
StateLayer {
|
||||
id: stateLayer
|
||||
|
||||
onClicked: klist.selectedItem = klist.keyFor(item.modelData)
|
||||
}
|
||||
|
||||
CustomText {
|
||||
id: label
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.margins: Tokens.padding.large
|
||||
anchors.right: item.selected ? checkIcon.left : parent.right
|
||||
anchors.rightMargin: item.selected ? Tokens.spacing.medium : anchors.margins
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: item.selected ? Colors.palette.m3onTertiaryContainer : Colors.palette.m3onSurface
|
||||
elide: Text.ElideRight
|
||||
font.pointSize: Tokens.font.size.smaller
|
||||
text: klist.labelFor(item.modelData)
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
id: checkIcon
|
||||
|
||||
anchors.margins: Tokens.padding.large
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: Colors.palette.m3onTertiaryContainer
|
||||
font.pointSize: Tokens.font.size.normal
|
||||
opacity: item.selected ? 1 : 0
|
||||
text: "check"
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
type: Anim.SlowEffects
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onOpenChanged: {
|
||||
if (open)
|
||||
selectedItem = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import ZShell.Config
|
||||
import ZShell.Internal
|
||||
import ZShell.Blobs
|
||||
import ZShell.Services
|
||||
import ZShell.Components
|
||||
import qs.Components
|
||||
import qs.Modules.Settings.Common
|
||||
|
||||
PageBase {
|
||||
id: root
|
||||
|
||||
isSubPage: true
|
||||
title: Tr.tr("Saved networks")
|
||||
|
||||
Component.onCompleted: Nmcli.loadSavedConnections(() => {})
|
||||
|
||||
ColumnLayout {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
spacing: Tokens.spacing.extraSmall / 2
|
||||
width: root.cappedWidth
|
||||
|
||||
ItemList {
|
||||
id: savedList
|
||||
|
||||
first: true
|
||||
last: true
|
||||
placeholderIcon: "wifi_find"
|
||||
placeholderText: Tr.tr("No saved networks")
|
||||
showList: true
|
||||
|
||||
delegate: StateLayer {
|
||||
id: saved
|
||||
|
||||
readonly property var ap: Nmcli.findNetwork(modelData)
|
||||
required property int index
|
||||
readonly property bool isActive: !!Nmcli.active && Nmcli.active.ssid === modelData
|
||||
required property var modelData
|
||||
|
||||
anchors.fill: undefined
|
||||
anchors.left: savedList.list.contentItem.left
|
||||
anchors.right: savedList.list.contentItem.right
|
||||
bottomLeftRadius: index === savedList?.list.count - 1 ? Tokens.rounding.extraLarge : radius
|
||||
bottomRightRadius: index === savedList?.list.count - 1 ? Tokens.rounding.extraLarge : radius
|
||||
implicitHeight: savedLayout.implicitHeight + savedLayout.anchors.margins * 2
|
||||
radius: Tokens.rounding.extraSmall
|
||||
topLeftRadius: index === 0 ? Tokens.rounding.extraLarge : radius
|
||||
topRightRadius: index === 0 ? Tokens.rounding.extraLarge : radius
|
||||
|
||||
onClicked: {
|
||||
root.nState.selectedNetworkSsid = saved.modelData;
|
||||
root.nState.networkDetailsFromSaved = true;
|
||||
root.nState.openSubPage(3); // Shared network detail/edit sub-page
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: savedLayout
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Tokens.padding.extraLarge
|
||||
anchors.margins: Tokens.padding.large
|
||||
anchors.rightMargin: Tokens.padding.extraLarge
|
||||
spacing: Tokens.spacing.medium
|
||||
|
||||
MaterialIcon {
|
||||
color: saved.isActive ? Colours.palette.m3primary : Colours.palette.m3onSurfaceVariant
|
||||
font.pointSize: Tokens.font.size.normal
|
||||
text: saved.ap ? Icons.getNetworkIcon(saved.ap.strength, !["", "none"].includes(Nmcli.savedSecurityFor(saved.modelData))) : "signal_wifi_off"
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
font: Tokens.font.body.small
|
||||
text: saved.modelData
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
color: saved.isActive ? Colours.palette.m3primary : Colours.palette.m3outline
|
||||
elide: Text.ElideRight
|
||||
font: Tokens.font.label.small
|
||||
text: {
|
||||
let security;
|
||||
if (saved.ap)
|
||||
security = saved.ap.security || Tr.trCtx("Open", "wifi security type");
|
||||
else
|
||||
security = Nmcli.securityLabel(Nmcli.savedSecurityFor(saved.modelData)) || Tr.trCtx("Unknown", "unknown wifi security");
|
||||
if (saved.isActive)
|
||||
// TRANSLATORS: %1 = security type
|
||||
return Tr.trCtx("Connected • %1", "network connected with security").arg(security);
|
||||
return security;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
color: Colours.palette.m3onSurfaceVariant
|
||||
font.pointSize: Tokens.font.size.normal
|
||||
text: "chevron_right"
|
||||
}
|
||||
}
|
||||
}
|
||||
model: ScriptModel {
|
||||
values: [...Nmcli.savedConnectionSsids].sort((a, b) => a.localeCompare(b))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,9 +8,7 @@ PageBase {
|
||||
readonly property var builtinIcons: ({
|
||||
audio: qsTr("Speakers"),
|
||||
microphone: qsTr("Microphone"),
|
||||
power: qsTr("Battery"),
|
||||
network: qsTr("Network"),
|
||||
bluetooth: qsTr("Bluetooth")
|
||||
power: qsTr("Battery")
|
||||
})
|
||||
|
||||
isSubPage: true
|
||||
@@ -95,15 +93,5 @@ PageBase {
|
||||
|
||||
onToggled: Config.bar.popouts.statusIcons = checked
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
checked: Config.bar.popouts.network
|
||||
last: true
|
||||
settingAnchor: "bar-status-network-popout"
|
||||
subtext: qsTr("Show a details popout when hovering the network icon")
|
||||
text: qsTr("Network popout on hover")
|
||||
|
||||
onToggled: Config.bar.popouts.network = checked
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ pragma ComponentBehavior: Bound
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import QtQuick.Layouts
|
||||
import ZShell.Services
|
||||
import ZShell.Components
|
||||
import ZShell.Config
|
||||
import ZShell.Llm
|
||||
@@ -91,9 +90,7 @@ Item {
|
||||
signal requestClose
|
||||
|
||||
readonly property bool atNewestEnd: !listLoader.item || listLoader.item.contentY < 4
|
||||
readonly property bool importing: importIds.length > 0
|
||||
|
||||
property var importIds: []
|
||||
property var pendingAttachments: []
|
||||
|
||||
function attachFiles(): void {
|
||||
@@ -439,7 +436,7 @@ Item {
|
||||
width: attachments.cellWidth
|
||||
height: width
|
||||
opacity: modelData ? 1 : 0
|
||||
ClipRect {
|
||||
CustomClippingRect {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Tokens.spacing.extraSmall
|
||||
|
||||
@@ -544,7 +541,7 @@ Item {
|
||||
bg.border.color: Colors.palette.m3outlineVariant
|
||||
bg.color: Colors.tPalette.m3surfaceContainerLowest
|
||||
fastGlow: LlmClient.busy
|
||||
glow.opacity: !ChatState.narrowSidebarExpanded ? (input.activeFocus && (!LlmClient.busy || ChatState.isWindow) ? 1 : 0) : 0
|
||||
glow.opacity: input.activeFocus && (!LlmClient.busy || ChatState.isWindow)
|
||||
font: Tokens.font.body.small
|
||||
implicitHeight: Math.min(root.height / 5, contentHeight + topPadding + bottomPadding)
|
||||
focus: true
|
||||
@@ -581,52 +578,4 @@ Item {
|
||||
root.send(text);
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: DropImporter
|
||||
|
||||
function onImportFinished(id: int, paths: var, errors: var): void {
|
||||
if (!root.importIds.includes(id))
|
||||
return;
|
||||
root.importIds = root.importIds.filter(i => i !== id);
|
||||
|
||||
if (paths.length > 0) {
|
||||
const added = root.chatData.addAttachments(paths);
|
||||
root.pendingAttachments = root.pendingAttachments.concat(added);
|
||||
}
|
||||
for (const e of errors)
|
||||
console.warn("Drop import:", e);
|
||||
}
|
||||
}
|
||||
|
||||
DropArea {
|
||||
id: dropArea
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
onEntered: drag => {
|
||||
drag.accepted = drag.hasUrls || drag.hasText || drag.hasHtml || drag.formats.some(f => f.startsWith("image/"));
|
||||
}
|
||||
|
||||
onDropped: drop => {
|
||||
const formats = drop.formats;
|
||||
const urls = drop.hasUrls ? drop.urls : [];
|
||||
const text = drop.hasText ? drop.text : "";
|
||||
const html = drop.hasHtml ? drop.html : "";
|
||||
|
||||
const imgFormat = formats.find(f => f.startsWith("image/"));
|
||||
const raw = imgFormat ? drop.getDataAsArrayBuffer(imgFormat) : new ArrayBuffer(0);
|
||||
|
||||
switch (DropImporter.decide(urls, text, html, raw.byteLength > 0)) {
|
||||
case DropImporter.Attach:
|
||||
root.importIds = [...root.importIds, DropImporter.importAttachments(urls, html, raw)];
|
||||
break;
|
||||
case DropImporter.InsertText:
|
||||
input.insert(input.cursorPosition, text || urls.map(u => u.toString()).join("\n"));
|
||||
root.focusInput();
|
||||
break;
|
||||
}
|
||||
drop.acceptProposedAction();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,50 +91,6 @@ CustomRect {
|
||||
|
||||
opacity: root.slim ? 0 : 1
|
||||
|
||||
state: titleText.readOnly ? "static" : "editing"
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "static"
|
||||
PropertyChanges {
|
||||
staticText.opacity: 1
|
||||
}
|
||||
PropertyChanges {
|
||||
titleText.opacity: 0
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "editing"
|
||||
PropertyChanges {
|
||||
staticText.opacity: 0
|
||||
}
|
||||
PropertyChanges {
|
||||
titleText.opacity: 1
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
transitions: [
|
||||
// Going static: titleText fades out (animated), staticText snaps in
|
||||
Transition {
|
||||
to: "static"
|
||||
Anim {
|
||||
target: titleText
|
||||
property: "opacity"
|
||||
type: Anim.SlowEffects
|
||||
}
|
||||
},
|
||||
// Going editing: staticText fades out (animated), titleText snaps in
|
||||
Transition {
|
||||
to: "editing"
|
||||
Anim {
|
||||
target: staticText
|
||||
property: "opacity"
|
||||
type: Anim.SlowEffects
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
@@ -150,7 +106,7 @@ CustomRect {
|
||||
color: Colors.layer(Colors.palette.m3surfaceContainerHighest, 1)
|
||||
implicitHeight: titleText.implicitHeight + Tokens.padding.extraSmall * 2
|
||||
radius: Tokens.rounding.full
|
||||
implicitWidth: titleText.width + Tokens.padding.large * 2
|
||||
implicitWidth: titleText.implicitWidth + Tokens.padding.large * 2
|
||||
opacity: titleText.readOnly ? 0 : 1
|
||||
|
||||
Behavior on opacity {
|
||||
@@ -166,6 +122,11 @@ CustomRect {
|
||||
anchors.right: parent.right
|
||||
implicitHeight: titleText.implicitHeight + Tokens.padding.small * 2
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: OpacityMask {
|
||||
maskSource: fadeMask
|
||||
}
|
||||
|
||||
TextFieldBase {
|
||||
id: titleText
|
||||
|
||||
@@ -173,12 +134,10 @@ CustomRect {
|
||||
anchors.leftMargin: readOnly ? 0 : Tokens.padding.large
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: readOnly ? Tokens.padding.small : Tokens.padding.extraSmall * 2
|
||||
width: Math.min(implicitWidth, parent.width - Tokens.padding.large * 2)
|
||||
font: Tokens.font.body.medium
|
||||
text: root.modelData.title || qsTr("New chat")
|
||||
readOnly: true
|
||||
color: Colors.palette.m3onSurface
|
||||
opacity: 0
|
||||
|
||||
Behavior on anchors.leftMargin {
|
||||
Anim {}
|
||||
@@ -187,11 +146,6 @@ CustomRect {
|
||||
Anim {}
|
||||
}
|
||||
|
||||
function cancelTitleEdit(): void {
|
||||
text = root.modelData.title;
|
||||
readOnly = true;
|
||||
}
|
||||
|
||||
onEditingFinished: root.modelData.title = text
|
||||
onReadOnlyChanged: {
|
||||
if (!readOnly) {
|
||||
@@ -202,24 +156,27 @@ CustomRect {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CustomText {
|
||||
id: staticText
|
||||
CustomRect {
|
||||
id: fadeMask
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: titleText.readOnly ? 0 : Tokens.padding.large
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: titleText.readOnly ? Tokens.padding.small : Tokens.padding.extraSmall * 2
|
||||
anchors.right: parent.right
|
||||
font: titleText.font
|
||||
text: titleText.text
|
||||
elide: CustomText.ElideRight
|
||||
anchors.fill: titleWrapper
|
||||
|
||||
Behavior on anchors.leftMargin {
|
||||
Anim {}
|
||||
layer.enabled: true
|
||||
visible: false
|
||||
|
||||
gradient: Gradient {
|
||||
orientation: Gradient.Horizontal
|
||||
|
||||
GradientStop {
|
||||
color: Qt.rgba(1, 1, 1, 1.0)
|
||||
position: 0.85
|
||||
}
|
||||
Behavior on anchors.topMargin {
|
||||
Anim {}
|
||||
|
||||
GradientStop {
|
||||
color: Qt.rgba(1, 1, 1, 0)
|
||||
position: 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -366,13 +323,7 @@ CustomRect {
|
||||
inactiveColor: Colors.layer(Colors.palette.m3surfaceContainerHighest, 3)
|
||||
inactiveOnColor: Colors.palette.m3onSurfaceVariant
|
||||
|
||||
onClicked: {
|
||||
if (!titleText.readOnly) {
|
||||
titleText.cancelTitleEdit();
|
||||
return;
|
||||
}
|
||||
root.remove(root.modelData);
|
||||
}
|
||||
onClicked: root.remove(root.modelData)
|
||||
}
|
||||
|
||||
IconButton {
|
||||
@@ -415,10 +366,7 @@ CustomRect {
|
||||
}
|
||||
|
||||
StateLayer {
|
||||
onClicked: {
|
||||
root.expanded = !root.expanded;
|
||||
titleText.readOnly = true;
|
||||
}
|
||||
onClicked: root.expanded = !root.expanded
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
|
||||
@@ -140,7 +140,7 @@ Item {
|
||||
label.transform: Rotation {
|
||||
origin.y: fabRoot.label.height / 2
|
||||
origin.x: fabRoot.label.width / 2
|
||||
angle: ChatState.fabExpanded && (ChatState.narrowSidebarExpanded || !ChatState.isWide || !ChatState.isWindow) ? 135 : 0
|
||||
angle: ChatState.fabExpanded && (ChatState.narrowSidebarExpanded && !ChatState.isWide || !ChatState.isWindow) ? 135 : 0
|
||||
|
||||
Behavior on angle {
|
||||
Anim {}
|
||||
@@ -148,9 +148,8 @@ Item {
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
if ((root.slim && !ChatState.narrowSidebarExpanded) || ChatState.shrinkSidebarOverride) {
|
||||
if (root.slim && !ChatState.narrowSidebarExpanded) {
|
||||
root.requestExpand();
|
||||
ChatState.shrinkSidebarOverride = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -184,10 +183,6 @@ Item {
|
||||
name: "pop out in window"
|
||||
icon: "open_in_new"
|
||||
}
|
||||
ListElement {
|
||||
name: "shrink sidebar"
|
||||
icon: "left_panel_close"
|
||||
}
|
||||
}
|
||||
|
||||
CustomRect {
|
||||
@@ -201,18 +196,7 @@ Item {
|
||||
|
||||
radius: sLayer.pressed ? Tokens.rounding.small : implicitHeight / 2
|
||||
color: Colors.palette.m3primaryContainer
|
||||
visible: {
|
||||
const n = modelData.name;
|
||||
const w = ChatState.isWindow;
|
||||
const wide = ChatState.isWide;
|
||||
|
||||
if (n === "pop out in window" && w)
|
||||
return false;
|
||||
if (n === "shrink sidebar" && (!w || !wide))
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
visible: !(modelData.name === "pop out in window" && ChatState.isWindow)
|
||||
clip: true
|
||||
|
||||
Behavior on radius {
|
||||
@@ -313,8 +297,6 @@ Item {
|
||||
root.newChatRequest();
|
||||
else if (name === "pop out in window")
|
||||
Detach.create();
|
||||
else if (name === "shrink sidebar")
|
||||
ChatState.shrinkSidebarOverride = true;
|
||||
|
||||
ChatState.fabExpanded = false;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ Singleton {
|
||||
property bool settledSlim: false
|
||||
property bool settledWide: false
|
||||
property bool narrowSidebarExpanded: false
|
||||
property bool shrinkSidebarOverride: false
|
||||
property bool windowIsWide: false
|
||||
property ShellScreen screen
|
||||
property bool sidebarVisible: ShellState.forActive().sidebar
|
||||
|
||||
@@ -28,9 +28,6 @@ Singleton {
|
||||
root.currentIdx = -1;
|
||||
}
|
||||
|
||||
onIsWideChanged: if (isWide)
|
||||
narrowSidebarExpanded = false
|
||||
|
||||
onSidebarVisibleChanged: if (!sidebarVisible)
|
||||
fabExpanded = false
|
||||
}
|
||||
|
||||
@@ -337,7 +337,7 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
ClipRect {
|
||||
CustomClippingRect {
|
||||
id: expandedTool
|
||||
|
||||
anchors.top: collapsedRow.bottom
|
||||
|
||||
@@ -220,8 +220,9 @@ CustomRect {
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
color: root.codeHeaderColor
|
||||
font: Tokens.font.label.builders.small.capitalization(Font.Capitalize).build()
|
||||
font: Tokens.font.label.small
|
||||
text: CodeIcons.name(root.language)
|
||||
visible: root.language.length > 0
|
||||
}
|
||||
|
||||
Item {
|
||||
@@ -251,7 +252,7 @@ CustomRect {
|
||||
}
|
||||
}
|
||||
|
||||
CustomRect {
|
||||
CustomClippingRect {
|
||||
id: codeRect
|
||||
|
||||
anchors.left: parent.left
|
||||
@@ -264,7 +265,6 @@ CustomRect {
|
||||
implicitWidth: codeText.implicitWidth + codeFlick.anchors.margins * 2
|
||||
implicitHeight: codeText.implicitHeight + codeFlick.anchors.margins * 2
|
||||
color: CodeColors.active.bg
|
||||
clip: true
|
||||
|
||||
Flickable {
|
||||
id: codeFlick
|
||||
@@ -272,9 +272,6 @@ CustomRect {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Tokens.padding.small
|
||||
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
contentWidth: codeText.implicitWidth
|
||||
|
||||
CustomScrollBar.horizontal: CustomScrollBar {
|
||||
flickable: codeFlick
|
||||
parent: codeFlick.parent
|
||||
@@ -283,13 +280,16 @@ CustomRect {
|
||||
anchors.right: parent.right
|
||||
}
|
||||
|
||||
CustomText {
|
||||
TextAreaBase.flickable: TextAreaBase {
|
||||
id: codeText
|
||||
|
||||
color: CodeColors.active.normal
|
||||
layer.enabled: true
|
||||
clip: false
|
||||
font: Tokens.font.mono.small
|
||||
textFormat: Text.RichText
|
||||
textFormat: root.colored ? Text.RichText : Text.PlainText
|
||||
text: root.colored ? root.html : root.plainForDisplay(root.code)
|
||||
readOnly: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -125,7 +125,7 @@ Item {
|
||||
current: root.current
|
||||
}
|
||||
|
||||
ClipRect {
|
||||
CustomClippingRect {
|
||||
id: expandedRect
|
||||
|
||||
anchors.left: parent.left
|
||||
|
||||
@@ -21,8 +21,12 @@ Item {
|
||||
property bool chatOpen: ChatState.chatSession !== null
|
||||
|
||||
readonly property bool isWide: root.width >= root.breakpoint
|
||||
readonly property bool wideExpandedSidebar: isWide && !ChatState.shrinkSidebarOverride
|
||||
readonly property bool narrowExpandedSidebar: !isWide && ChatState.narrowSidebarExpanded
|
||||
|
||||
readonly property bool showList: isWide || !chatOpen
|
||||
readonly property bool showContent: !isWide && chatOpen
|
||||
|
||||
function openConversation(conv: ChatSession, index: int): void {
|
||||
}
|
||||
|
||||
BlobGroup {
|
||||
id: blobGroup
|
||||
@@ -50,9 +54,9 @@ Item {
|
||||
anchors.left: parent.left
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.margins: Tokens.padding.medium
|
||||
implicitWidth: root.wideExpandedSidebar || root.narrowExpandedSidebar ? Config.sidebar.sizes.width : root.iconSize
|
||||
implicitWidth: root.isWide || ChatState.narrowSidebarExpanded ? Config.sidebar.sizes.width : root.iconSize
|
||||
highlight: true
|
||||
slim: !root.wideExpandedSidebar && !root.narrowExpandedSidebar
|
||||
slim: !root.isWide && !ChatState.narrowSidebarExpanded
|
||||
z: 1
|
||||
|
||||
Behavior on implicitWidth {
|
||||
@@ -82,13 +86,13 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
CustomClippingRect {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: invertedRect.borderLeft
|
||||
anchors.topMargin: invertedRect.borderTop
|
||||
anchors.bottomMargin: invertedRect.borderBottom
|
||||
anchors.rightMargin: invertedRect.borderRight
|
||||
clip: true
|
||||
radius: invertedRect.radius
|
||||
|
||||
children: [contentArea, dim]
|
||||
}
|
||||
@@ -96,10 +100,9 @@ Item {
|
||||
CustomRect {
|
||||
id: dim
|
||||
anchors.fill: parent
|
||||
radius: invertedRect.radius
|
||||
|
||||
color: Colors.palette.m3shadow
|
||||
opacity: root.narrowExpandedSidebar ? 0.3 : 0
|
||||
opacity: ChatState.narrowSidebarExpanded && !root.isWide ? 0.3 : 0
|
||||
z: 1
|
||||
|
||||
Behavior on opacity {
|
||||
@@ -152,7 +155,7 @@ Item {
|
||||
id: dimArea
|
||||
anchors.fill: parent
|
||||
preventStealing: true
|
||||
enabled: root.narrowExpandedSidebar
|
||||
enabled: !root.isWide && ChatState.narrowSidebarExpanded
|
||||
cursorShape: undefined
|
||||
|
||||
onClicked: {
|
||||
@@ -172,6 +175,12 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Binding {
|
||||
target: ChatState
|
||||
property: "windowIsWide"
|
||||
value: root.showList
|
||||
}
|
||||
|
||||
Binding {
|
||||
target: ChatState
|
||||
property: "isWide"
|
||||
|
||||
@@ -17,6 +17,76 @@ Item {
|
||||
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: flickable.contentHeight
|
||||
layer.enabled: true
|
||||
layer.smooth: true
|
||||
|
||||
layer.effect: Mask {
|
||||
maskSource: gradientMask
|
||||
}
|
||||
|
||||
Item {
|
||||
id: gradientMask
|
||||
|
||||
anchors.fill: parent
|
||||
layer.enabled: true
|
||||
visible: false
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
|
||||
gradient: Gradient {
|
||||
orientation: Gradient.Horizontal
|
||||
|
||||
GradientStop {
|
||||
color: Qt.rgba(0, 0, 0, 0)
|
||||
position: 0
|
||||
}
|
||||
|
||||
GradientStop {
|
||||
color: Qt.rgba(0, 0, 0, 1)
|
||||
position: 0.1
|
||||
}
|
||||
|
||||
GradientStop {
|
||||
color: Qt.rgba(0, 0, 0, 1)
|
||||
position: 0.9
|
||||
}
|
||||
|
||||
GradientStop {
|
||||
color: Qt.rgba(0, 0, 0, 0)
|
||||
position: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
implicitWidth: parent.width / 2
|
||||
opacity: flickable.contentX > 0 ? 0 : 1
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
implicitWidth: parent.width / 2
|
||||
opacity: flickable.contentX < flickable.contentWidth - parent.width ? 0 : 1
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CustomFlickable {
|
||||
id: flickable
|
||||
|
||||
@@ -112,7 +112,7 @@ CustomRect {
|
||||
}
|
||||
}
|
||||
|
||||
ClipRect {
|
||||
CustomClippingRect {
|
||||
anchors.fill: parent
|
||||
color: root.urgency === NotificationUrgency.Critical ? Colors.palette.m3error : root.urgency === NotificationUrgency.Low ? Colors.layer(Colors.palette.m3surfaceContainerHigh, 3) : Colors.palette.m3secondaryContainer
|
||||
radius: Tokens.rounding.full
|
||||
|
||||
@@ -218,7 +218,7 @@ void BlobInvertedRect::outerCornerRadii(float out[4]) const {
|
||||
const auto base = std::min(static_cast<float>(m_outerRadius), maxR);
|
||||
out[0] = std::min(
|
||||
m_outerTopRightRadius >= 0 ? static_cast<float>(m_outerTopRightRadius)
|
||||
: base,
|
||||
: base,
|
||||
maxR);
|
||||
out[1] = std::min(
|
||||
m_outerBottomRightRadius >= 0
|
||||
|
||||
@@ -60,11 +60,15 @@ class BlobInvertedRect : public BlobShape {
|
||||
void setOuterRadius(qreal v);
|
||||
|
||||
// Per-corner outer radii. -1 falls back to outerRadius (mirrors BlobRect).
|
||||
[[nodiscard]] qreal outerTopLeftRadius() const { return m_outerTopLeftRadius; }
|
||||
[[nodiscard]] qreal outerTopLeftRadius() const {
|
||||
return m_outerTopLeftRadius;
|
||||
}
|
||||
|
||||
void setOuterTopLeftRadius(qreal v);
|
||||
|
||||
[[nodiscard]] qreal outerTopRightRadius() const { return m_outerTopRightRadius; }
|
||||
[[nodiscard]] qreal outerTopRightRadius() const {
|
||||
return m_outerTopRightRadius;
|
||||
}
|
||||
|
||||
void setOuterTopRightRadius(qreal v);
|
||||
|
||||
|
||||
@@ -240,8 +240,7 @@ void BlobShape::updatePolish() {
|
||||
m_cachedInvertedRadius = 0;
|
||||
memset(m_cachedInvertedOuter, 0, sizeof(m_cachedInvertedOuter));
|
||||
memset(m_cachedInvertedInner, 0, sizeof(m_cachedInvertedInner));
|
||||
memset(
|
||||
m_cachedInvertedOuterRadii, 0, sizeof(m_cachedInvertedOuterRadii));
|
||||
memset(m_cachedInvertedOuterRadii, 0, sizeof(m_cachedInvertedOuterRadii));
|
||||
|
||||
auto* inv = m_group->invertedRect();
|
||||
if (inv) {
|
||||
|
||||
@@ -52,7 +52,7 @@ FontBuilder FontBuilder::letterSpacing(qreal spacing, bool absolute) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
FontBuilder FontBuilder::capitalization(QFont::Capitalization cap) {
|
||||
FontBuilder FontBuilder::capitalisation(QFont::Capitalization cap) {
|
||||
m_font.setCapitalization(cap);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ class FontBuilder {
|
||||
[[nodiscard]] Q_INVOKABLE FontBuilder
|
||||
letterSpacing(qreal spacing, bool absolute = true);
|
||||
[[nodiscard]] Q_INVOKABLE FontBuilder
|
||||
capitalization(QFont::Capitalization cap);
|
||||
capitalisation(QFont::Capitalization cap);
|
||||
[[nodiscard]] Q_INVOKABLE FontBuilder vaxis(const QString& tag, float value);
|
||||
[[nodiscard]] Q_INVOKABLE FontBuilder vaxes(const QVariantMap& axes);
|
||||
[[nodiscard]] Q_INVOKABLE QFont build() const;
|
||||
|
||||
@@ -662,17 +662,6 @@ void WallpaperService::drawSurface(WallpaperSurface* surface) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Release the context when this scope ends (see FrameService::drawSurface).
|
||||
// Holding it across the draw loop's sleep serializes the two raw-EGL clients on
|
||||
// NVIDIA, which makes eglSwapBuffers fail and starves other processes' contexts.
|
||||
struct ContextRelease {
|
||||
EGLDisplay display;
|
||||
~ContextRelease() {
|
||||
eglMakeCurrent(
|
||||
display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||
}
|
||||
} contextRelease{m_eglDisplay};
|
||||
|
||||
QVariantMap crops;
|
||||
QSize size;
|
||||
bool upload = false;
|
||||
@@ -712,9 +701,7 @@ void WallpaperService::drawSurface(WallpaperSurface* surface) {
|
||||
glDrawArrays(GL_TRIANGLES, 0, 3);
|
||||
|
||||
if (!eglSwapBuffers(m_eglDisplay, target))
|
||||
qWarning()
|
||||
<< "WallpaperService: eglSwapBuffers failed, eglGetError = 0x"
|
||||
<< QString::number(unsigned(eglGetError()), 16);
|
||||
qWarning() << "WallpaperService: eglSwapBuffers failed";
|
||||
}
|
||||
|
||||
void WallpaperService::addSurface(const QString& screen) {
|
||||
|
||||
@@ -22,12 +22,10 @@ qml_module(ZShell-services
|
||||
networkusage.hpp networkusage.cpp
|
||||
lyriccandidate.hpp lyriccandidate.cpp
|
||||
lyrics.hpp lyrics.cpp
|
||||
dropimporter.hpp dropimporter.cpp
|
||||
LIBRARIES
|
||||
Qt6::Core
|
||||
Qt6::Qml
|
||||
Qt6::Concurrent
|
||||
Qt6::Network
|
||||
PkgConfig::Pipewire
|
||||
PkgConfig::Aubio
|
||||
PkgConfig::Cava
|
||||
|
||||
@@ -1,263 +0,0 @@
|
||||
#include "dropimporter.hpp"
|
||||
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QMetaObject>
|
||||
#include <QMimeDatabase>
|
||||
#include <QNetworkReply>
|
||||
#include <QRegularExpression>
|
||||
|
||||
namespace {
|
||||
constexpr qint64 kMaxBytes = 100 * 1024 * 1024;
|
||||
|
||||
bool isHttp(const QUrl& u) {
|
||||
return u.scheme() == QLatin1String("http") ||
|
||||
u.scheme() == QLatin1String("https");
|
||||
}
|
||||
|
||||
QString firstImgSrc(const QString& html) {
|
||||
static const QRegularExpression re(
|
||||
R"(<img\b[^>]*?\bsrc\s*=\s*["']([^"']+)["'])",
|
||||
QRegularExpression::CaseInsensitiveOption);
|
||||
const auto m = re.match(html);
|
||||
if (!m.hasMatch()) return {};
|
||||
return m.captured(1).replace(QLatin1String("&"), QLatin1String("&"));
|
||||
}
|
||||
|
||||
bool looksLikeUrl(const QString& text) {
|
||||
const QString t = text.trimmed();
|
||||
if (t.isEmpty() || t.contains(QRegularExpression(QStringLiteral("\\s"))))
|
||||
return false;
|
||||
const QUrl u(t);
|
||||
return u.isValid() && (isHttp(u) || u.isLocalFile());
|
||||
}
|
||||
|
||||
QString sanitize(QString name) {
|
||||
name = QFileInfo(name).fileName();
|
||||
name.replace(
|
||||
QRegularExpression(QStringLiteral("[\\\\/:*?\"<>|\\x00-\\x1f]")),
|
||||
QStringLiteral("_"));
|
||||
return name.left(120);
|
||||
}
|
||||
|
||||
QString fileNameFor(QNetworkReply* reply, const QUrl& url) {
|
||||
static const QRegularExpression cd(
|
||||
QStringLiteral("filename\\*?=(?:UTF-8'')?\"?([^\";]+)\"?"),
|
||||
QRegularExpression::CaseInsensitiveOption);
|
||||
const auto m =
|
||||
cd.match(QString::fromUtf8(reply->rawHeader("Content-Disposition")));
|
||||
QString name = m.hasMatch()
|
||||
? QUrl::fromPercentEncoding(m.captured(1).toUtf8())
|
||||
: url.fileName();
|
||||
name = sanitize(name);
|
||||
return name.isEmpty() ? QStringLiteral("download") : name;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace ZShell::Services {
|
||||
|
||||
DropImporter::DropImporter(QObject* parent) : QObject(parent) {}
|
||||
|
||||
DropImporter::Decision DropImporter::decide(
|
||||
const QList<QUrl>& urls,
|
||||
const QString& text,
|
||||
const QString& html,
|
||||
bool hasRawImage) const {
|
||||
for (const QUrl& u : urls)
|
||||
if (u.isLocalFile()) return Attach;
|
||||
|
||||
const QString src = firstImgSrc(html);
|
||||
if (hasRawImage ||
|
||||
(!src.isEmpty() && (text.trimmed().isEmpty() || looksLikeUrl(text))))
|
||||
return Attach;
|
||||
|
||||
QMimeDatabase db;
|
||||
for (const QUrl& u : urls) {
|
||||
if (!isHttp(u) || QFileInfo(u.path()).suffix().isEmpty()) continue;
|
||||
const QMimeType mt = db.mimeTypeForUrl(u);
|
||||
if (!mt.isDefault() && !mt.inherits(QStringLiteral("text/html")))
|
||||
return Attach;
|
||||
}
|
||||
|
||||
if (!text.isEmpty() || !urls.isEmpty()) return InsertText;
|
||||
return Ignore;
|
||||
}
|
||||
|
||||
int DropImporter::importAttachments(
|
||||
const QList<QUrl>& urls, const QString& html, const QByteArray& rawImage) {
|
||||
const int id = ++m_nextId;
|
||||
m_requests[id].pending = 1;
|
||||
|
||||
if (!rawImage.isEmpty()) {
|
||||
saveBytes(id, rawImage);
|
||||
} else if (const QString src = firstImgSrc(html); !src.isEmpty()) {
|
||||
if (src.startsWith(QLatin1String("data:"), Qt::CaseInsensitive)) {
|
||||
const int comma = src.indexOf(QLatin1Char(','));
|
||||
if (comma < 0) {
|
||||
failWith(id, tr("Malformed data URI"));
|
||||
} else {
|
||||
const bool b64 = src.left(comma).contains(
|
||||
QLatin1String(";base64"), Qt::CaseInsensitive);
|
||||
const QString payload = src.mid(comma + 1);
|
||||
saveBytes(
|
||||
id,
|
||||
b64 ? QByteArray::fromBase64(payload.toLatin1())
|
||||
: QByteArray::fromPercentEncoding(payload.toUtf8()));
|
||||
}
|
||||
} else {
|
||||
const QUrl u(src);
|
||||
if (u.isLocalFile())
|
||||
addLocal(id, u);
|
||||
else if (isHttp(u))
|
||||
download(id, u);
|
||||
else
|
||||
failWith(id, tr("Unsupported image source"));
|
||||
}
|
||||
} else {
|
||||
for (const QUrl& u : urls) {
|
||||
if (u.isLocalFile())
|
||||
addLocal(id, u);
|
||||
else if (isHttp(u))
|
||||
download(id, u);
|
||||
else
|
||||
failWith(id, tr("Unsupported URL: %1").arg(u.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
QMetaObject::invokeMethod(
|
||||
this, [this, id] { finishUnit(id); }, Qt::QueuedConnection);
|
||||
return id;
|
||||
}
|
||||
|
||||
void DropImporter::addLocal(int id, const QUrl& url) {
|
||||
const QFileInfo fi(url.toLocalFile());
|
||||
if (!fi.exists())
|
||||
failWith(id, tr("File not found: %1").arg(fi.filePath()));
|
||||
else if (fi.isDir())
|
||||
failWith(id, tr("Folders can't be attached: %1").arg(fi.fileName()));
|
||||
else if (fi.size() > kMaxBytes)
|
||||
failWith(id, tr("File too large: %1").arg(fi.fileName()));
|
||||
else
|
||||
succeed(id, fi.absoluteFilePath());
|
||||
}
|
||||
|
||||
void DropImporter::saveBytes(int id, const QByteArray& bytes) {
|
||||
if (bytes.isEmpty() || bytes.size() > kMaxBytes) {
|
||||
failWith(id, tr("Empty or oversized data"));
|
||||
return;
|
||||
}
|
||||
const QString dir = newSubdir();
|
||||
if (dir.isEmpty()) {
|
||||
failWith(id, tr("No temp directory"));
|
||||
return;
|
||||
}
|
||||
|
||||
const QString suffix =
|
||||
QMimeDatabase().mimeTypeForData(bytes).preferredSuffix();
|
||||
const QString path =
|
||||
dir + QStringLiteral("/dropped") +
|
||||
(suffix.isEmpty() ? QString() : QLatin1Char('.') + suffix);
|
||||
|
||||
QFile f(path);
|
||||
if (!f.open(QIODevice::WriteOnly) || f.write(bytes) != bytes.size()) {
|
||||
failWith(id, tr("Could not write temp file"));
|
||||
return;
|
||||
}
|
||||
f.close();
|
||||
succeed(id, path);
|
||||
}
|
||||
|
||||
void DropImporter::download(int id, const QUrl& url) {
|
||||
const QString dir = newSubdir();
|
||||
if (dir.isEmpty()) {
|
||||
failWith(id, tr("No temp directory"));
|
||||
return;
|
||||
}
|
||||
|
||||
++m_requests[id].pending;
|
||||
|
||||
QNetworkRequest req(url);
|
||||
req.setAttribute(
|
||||
QNetworkRequest::RedirectPolicyAttribute,
|
||||
QNetworkRequest::NoLessSafeRedirectPolicy);
|
||||
QNetworkReply* reply = m_nam.get(req);
|
||||
auto* file = new QFile(dir + QStringLiteral("/download.part"), reply);
|
||||
|
||||
connect(reply, &QNetworkReply::readyRead, this, [reply, file] {
|
||||
if (!file->isOpen() && !file->open(QIODevice::WriteOnly)) {
|
||||
reply->abort();
|
||||
return;
|
||||
}
|
||||
file->write(reply->readAll());
|
||||
if (file->size() > kMaxBytes) reply->abort();
|
||||
});
|
||||
|
||||
connect(
|
||||
reply,
|
||||
&QNetworkReply::finished,
|
||||
this,
|
||||
[this, reply, file, id, dir, url] {
|
||||
reply->deleteLater();
|
||||
const bool tooBig = file->size() > kMaxBytes;
|
||||
const bool wrote = file->isOpen();
|
||||
file->close();
|
||||
|
||||
if (tooBig) {
|
||||
failWith(id, tr("Download too large: %1").arg(url.toString()));
|
||||
} else if (reply->error() != QNetworkReply::NoError) {
|
||||
failWith(
|
||||
id,
|
||||
tr("Download failed (%1): %2")
|
||||
.arg(url.toString(), reply->errorString()));
|
||||
} else if (!wrote) {
|
||||
failWith(id, tr("Empty response: %1").arg(url.toString()));
|
||||
} else {
|
||||
const QString name = fileNameFor(reply, url);
|
||||
QString finalPath = dir + QLatin1Char('/') + name;
|
||||
if (QFile::rename(file->fileName(), finalPath)) {
|
||||
if (QFileInfo(name).suffix().isEmpty()) {
|
||||
const QString sfx =
|
||||
QMimeDatabase()
|
||||
.mimeTypeForFile(
|
||||
finalPath, QMimeDatabase::MatchContent)
|
||||
.preferredSuffix();
|
||||
if (!sfx.isEmpty() &&
|
||||
QFile::rename(
|
||||
finalPath, finalPath + QLatin1Char('.') + sfx))
|
||||
finalPath += QLatin1Char('.') + sfx;
|
||||
}
|
||||
succeed(id, finalPath);
|
||||
} else {
|
||||
failWith(id, tr("Could not store download"));
|
||||
}
|
||||
}
|
||||
finishUnit(id);
|
||||
});
|
||||
}
|
||||
|
||||
void DropImporter::succeed(int id, const QString& path) {
|
||||
if (auto it = m_requests.find(id); it != m_requests.end())
|
||||
it->files.append(path);
|
||||
}
|
||||
|
||||
void DropImporter::failWith(int id, const QString& message) {
|
||||
if (auto it = m_requests.find(id); it != m_requests.end())
|
||||
it->errors.append(message);
|
||||
}
|
||||
|
||||
void DropImporter::finishUnit(int id) {
|
||||
auto it = m_requests.find(id);
|
||||
if (it == m_requests.end() || --it->pending > 0) return;
|
||||
const Request r = *it;
|
||||
m_requests.erase(it);
|
||||
emit importFinished(id, r.files, r.errors);
|
||||
}
|
||||
|
||||
QString DropImporter::newSubdir() {
|
||||
if (!m_tempDir.isValid()) return {};
|
||||
const QString p = m_tempDir.filePath(QString::number(++m_counter));
|
||||
return QDir().mkpath(p) ? p : QString();
|
||||
}
|
||||
|
||||
} // namespace ZShell::Services
|
||||
@@ -1,62 +0,0 @@
|
||||
#pragma once
|
||||
#include <QHash>
|
||||
|
||||
#include <QList>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QObject>
|
||||
#include <QQmlEngine>
|
||||
#include <QStringList>
|
||||
#include <QTemporaryDir>
|
||||
#include <QUrl>
|
||||
|
||||
namespace ZShell::Services {
|
||||
|
||||
class DropImporter : public QObject {
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
QML_SINGLETON
|
||||
|
||||
public:
|
||||
enum Decision { Ignore, InsertText, Attach };
|
||||
Q_ENUM(Decision)
|
||||
|
||||
explicit DropImporter(QObject* parent = nullptr);
|
||||
|
||||
Q_INVOKABLE Decision decide(
|
||||
const QList<QUrl>& urls,
|
||||
const QString& text,
|
||||
const QString& html,
|
||||
bool hasRawImage) const;
|
||||
|
||||
Q_INVOKABLE int importAttachments(
|
||||
const QList<QUrl>& urls,
|
||||
const QString& html,
|
||||
const QByteArray& rawImage);
|
||||
|
||||
signals:
|
||||
void importFinished(
|
||||
int id, const QStringList& paths, const QStringList& errors);
|
||||
|
||||
private:
|
||||
struct Request {
|
||||
int pending = 0;
|
||||
QStringList files;
|
||||
QStringList errors;
|
||||
};
|
||||
|
||||
void addLocal(int id, const QUrl& url);
|
||||
void download(int id, const QUrl& url);
|
||||
void saveBytes(int id, const QByteArray& bytes);
|
||||
void succeed(int id, const QString& path);
|
||||
void failWith(int id, const QString& message);
|
||||
void finishUnit(int id);
|
||||
QString newSubdir();
|
||||
|
||||
QNetworkAccessManager m_nam;
|
||||
QTemporaryDir m_tempDir;
|
||||
QHash<int, Request> m_requests;
|
||||
int m_nextId = 0;
|
||||
int m_counter = 0;
|
||||
};
|
||||
|
||||
} // namespace ZShell::Services
|
||||
-1834
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user