Compare commits
38
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
74261f0a49 | ||
|
|
eb65e44ac8 | ||
|
|
e76350fa7f | ||
|
|
de44096132 | ||
|
|
59213cf30d | ||
|
|
8ec454306f | ||
|
|
015c1fb27d | ||
|
|
968055a24e | ||
|
|
f6cb7e44c2 | ||
|
|
5a8939bc1e | ||
|
|
46855b6a7f | ||
|
|
6071223009 | ||
|
|
2d40ce33d1 | ||
|
|
954dc10fbb | ||
|
|
15ccfd24c6 | ||
|
|
33b08523e9 | ||
|
|
742bece30b | ||
|
|
a64eadd14a | ||
|
|
f9eeb793db | ||
|
|
6bb193c78c | ||
|
|
aadecbfe44 | ||
|
|
53f0aa80ae | ||
|
|
d9856c9409 | ||
|
|
8dc9753e2b | ||
|
|
2a4bd3dfdd | ||
|
|
f75f51ef50 | ||
|
|
e0873304d1 | ||
|
|
99a0155c17 | ||
|
|
f309debea5 | ||
|
|
0979fd014d | ||
|
|
656bd0a54d | ||
|
|
f01001dcfa | ||
|
|
bc6b0d50ab | ||
|
|
f27c9afc25 | ||
|
|
5962fc8354 | ||
|
|
1adcb9841c | ||
|
|
0e0f8555ed | ||
|
|
1d63e67554 |
@@ -15,7 +15,7 @@ CustomRect {
|
||||
property color activeOnColor
|
||||
property bool checked
|
||||
property real checkedRadius: Tokens.rounding.medium
|
||||
property real defaultRadius: Tokens.rounding.large
|
||||
property real defaultRadius: Math.min(width, height) / 2
|
||||
property color disabledColor: Qt.alpha(Colors.palette.m3onSurface, 0.1)
|
||||
property color disabledOnColor: Qt.alpha(Colors.palette.m3onSurface, 0.38)
|
||||
property bool fillWidth
|
||||
|
||||
@@ -34,6 +34,7 @@ BusyIndicator {
|
||||
anchors.fill: parent
|
||||
bgColor: root.bgColor
|
||||
fgColor: root.fgColor
|
||||
hasEndIndicator: false
|
||||
padding: root.padding
|
||||
rotation: manager.rotation
|
||||
startAngle: manager.startFraction * 360
|
||||
@@ -53,6 +54,7 @@ BusyIndicator {
|
||||
Anim {
|
||||
duration: manager.completeEndDuration * Tokens.anim.durations.scale
|
||||
properties: "opacity,internalStrokeWidth"
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ TextFieldBase {
|
||||
property string leadingIcon
|
||||
readonly property int leadingOffset: leadingIcon ? leadingIconLoader.width + leadingIconLoader.anchors.leftMargin : 0
|
||||
property int radius: Tokens.rounding.small
|
||||
property alias sLayer: stateLayer
|
||||
readonly property real smallFontScale: smallFontSize / font.pointSize
|
||||
property int smallFontSize: Tokens.font.size.small
|
||||
property string supportingText
|
||||
@@ -145,6 +146,7 @@ TextFieldBase {
|
||||
|
||||
sourceComponent: MaterialIcon {
|
||||
color: Colors.palette.m3onSurfaceVariant
|
||||
font.pointSize: Tokens.font.size.larger
|
||||
text: root.leadingIcon
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,7 +236,7 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
if (Config.dock.enable && Config.dock.hoverToReveal && !root.visibilities.dock && !root.visibilities.launcher && root.inBottomPanel(root.panels.dock, x, y))
|
||||
if (Config.dock.enabled && Config.dock.hoverToReveal && !root.visibilities.dock && !root.visibilities.launcher && root.inBottomPanel(root.panels.dock, x, y))
|
||||
root.visibilities.dock = true;
|
||||
|
||||
if (root.geometry.barContains(x, y))
|
||||
|
||||
+12
-1
@@ -11,6 +11,7 @@ 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
|
||||
@@ -144,7 +145,17 @@ CustomWindow {
|
||||
HyprlandFocusGrab {
|
||||
id: focusGrab
|
||||
|
||||
active: visibilities.dock || visibilities.resources || visibilities.launcher || visibilities.sidebar || visibilities.dashboard || visibilities.settings || visibilities.clipboard || (panels.popouts.hasCurrent && panels.popouts.currentName.startsWith("traymenu") && (!Config.bar.tray.showOnHover || (panels.popouts.current as StackView)?.depth > 1))
|
||||
active: {
|
||||
const v = visibilities;
|
||||
const conf = Config;
|
||||
if (!N.PopupManager.closed && panels.popouts.hasCurrent)
|
||||
return true;
|
||||
if ((v.launcher && conf.launcher.enabled) || (v.sidebar && conf.sidebar.enabled) || (v.resources && conf.dashboard.performance.enabled) || (v.dashboard && conf.dashboard.enabled) || v.settings || (v.clipboard && conf.clipboard.enabled) || (v.dock && conf.dock.enabled))
|
||||
return true;
|
||||
if (panels.popouts.hasCurrent && panels.popouts.currentName.startsWith("traymenu") && (!conf.bar.tray.showOnHover || (panels.popouts.current as StackView)?.depth > 1))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
windows: [root]
|
||||
|
||||
onCleared: {
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property real radius
|
||||
|
||||
Rectangle {
|
||||
id: shadowRect
|
||||
|
||||
anchors.fill: root
|
||||
color: "black"
|
||||
layer.enabled: true
|
||||
radius: root.radius
|
||||
visible: false
|
||||
}
|
||||
|
||||
MultiEffect {
|
||||
id: effects
|
||||
|
||||
anchors.fill: shadowRect
|
||||
autoPaddingEnabled: true
|
||||
maskEnabled: true
|
||||
maskInverted: true
|
||||
maskSource: shadowRect
|
||||
shadowBlur: 2.0
|
||||
shadowColor: "black"
|
||||
shadowEnabled: true
|
||||
shadowOpacity: 1
|
||||
source: shadowRect
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
pragma Singleton
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Components
|
||||
import ZShell.Config
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property list<var> apps: {
|
||||
var map = new Map();
|
||||
|
||||
const pinnedApps = Config.dock?.pinnedApps ?? [];
|
||||
for (const appId of pinnedApps) {
|
||||
if (!map.has(appId.toLowerCase()))
|
||||
map.set(appId.toLowerCase(), ({
|
||||
pinned: true,
|
||||
toplevels: []
|
||||
}));
|
||||
}
|
||||
|
||||
if (pinnedApps.length > 0) {
|
||||
map.set("SEPARATOR", {
|
||||
pinned: false,
|
||||
toplevels: []
|
||||
});
|
||||
}
|
||||
|
||||
var values = [];
|
||||
|
||||
for (const [key, value] of map) {
|
||||
values.push(appEntryComp.createObject(null, {
|
||||
appId: key,
|
||||
toplevels: value.toplevels,
|
||||
pinned: value.pinned
|
||||
}));
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
function isPinned(appId) {
|
||||
return Config.dock.pinnedApps.indexOf(appId) !== -1;
|
||||
}
|
||||
|
||||
function togglePin(appId) {
|
||||
if (root.isPinned(appId)) {
|
||||
Config.dock.pinnedApps = Config.dock.pinnedApps.filter(id => id !== appId);
|
||||
} else {
|
||||
Config.dock.pinnedApps = Config.dock.pinnedApps.concat([appId]);
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: appEntryComp
|
||||
|
||||
TaskbarAppEntry {
|
||||
}
|
||||
}
|
||||
|
||||
component TaskbarAppEntry: QtObject {
|
||||
id: wrapper
|
||||
|
||||
required property string appId
|
||||
required property bool pinned
|
||||
required property list<var> toplevels
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
// FetchPresets.qml
|
||||
pragma Singleton
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property var parsedPresets: ({})
|
||||
readonly property var presets: parsedPresets
|
||||
property bool ready: false
|
||||
|
||||
function accents(presetName, variantName) {
|
||||
const variant = parsedPresets[presetName]?.variants?.[variantName];
|
||||
|
||||
return variant?.accents ?? [];
|
||||
}
|
||||
|
||||
function defaultAccent(presetName, variantName) {
|
||||
const variant = parsedPresets[presetName]?.variants?.[variantName];
|
||||
|
||||
return variant?.default_accent ?? "";
|
||||
}
|
||||
|
||||
function modes(presetName, variantName) {
|
||||
const variant = parsedPresets[presetName]?.variants?.[variantName];
|
||||
|
||||
return variant?.modes ?? [];
|
||||
}
|
||||
|
||||
function presetNames() {
|
||||
return Object.keys(parsedPresets);
|
||||
}
|
||||
|
||||
function variantNames(presetName) {
|
||||
const preset = parsedPresets[presetName];
|
||||
|
||||
if (!preset || !preset.variants)
|
||||
return [];
|
||||
|
||||
return Object.keys(preset.variants);
|
||||
}
|
||||
|
||||
Process {
|
||||
command: ["zshell-cli", "scheme", "list-presets", "--json"]
|
||||
running: true
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
try {
|
||||
const parsed = JSON.parse(text);
|
||||
|
||||
root.parsedPresets = parsed.presets ?? {};
|
||||
root.ready = true;
|
||||
} catch (e) {
|
||||
console.error("Failed to parse presets JSON:", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,16 +77,6 @@ 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")
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -205,15 +205,6 @@ GridLayout {
|
||||
}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: "network"
|
||||
|
||||
delegate: EntryWrapper {
|
||||
NetworkWidget {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: "media"
|
||||
|
||||
|
||||
@@ -105,6 +105,28 @@ WidgetBase {
|
||||
}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: "bluetooth"
|
||||
|
||||
delegate: EntryWrapper {
|
||||
name: "bluetooth"
|
||||
|
||||
BluetoothWidget {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: "network"
|
||||
|
||||
delegate: EntryWrapper {
|
||||
name: "network"
|
||||
|
||||
NetworkWidget {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: "power"
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
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"
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
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"
|
||||
}
|
||||
@@ -0,0 +1,385 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -80,6 +80,13 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Popout {
|
||||
name: "bluetooth"
|
||||
|
||||
sourceComponent: BluetoothPopout {
|
||||
}
|
||||
}
|
||||
|
||||
Popout {
|
||||
name: "updates"
|
||||
|
||||
@@ -96,10 +103,10 @@ Item {
|
||||
readonly property bool shouldBeActive: root.popouts.currentName === name
|
||||
|
||||
active: false
|
||||
// anchors.horizontalCenter: parent.horizontalCenter
|
||||
// anchors.top: parent.top
|
||||
// anchors.topMargin: 5
|
||||
anchors.centerIn: parent
|
||||
anchors.centerIn: opacity === 1 ? undefined : parent
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: opacity === 1 ? parent.top : undefined
|
||||
anchors.topMargin: 5
|
||||
opacity: 0
|
||||
scale: 0.8
|
||||
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import QtQuick
|
||||
import ZShell.Config
|
||||
import qs.Services
|
||||
import qs.Helpers
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import ZShell.Components
|
||||
import ZShell.Config
|
||||
import qs.Services
|
||||
import qs.Components
|
||||
import qs.Helpers
|
||||
|
||||
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.normal
|
||||
|
||||
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.smaller
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
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,36 +1,362 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import Quickshell
|
||||
import Quickshell.Networking
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import qs.Components
|
||||
import Quickshell
|
||||
import ZShell.Components
|
||||
import ZShell.Config
|
||||
import qs.Modules
|
||||
import qs.Helpers as Helpers
|
||||
import qs.Services
|
||||
import qs.Modules.Bar.Popouts.Network
|
||||
import qs.Components
|
||||
import qs.Helpers
|
||||
|
||||
Item {
|
||||
CustomClippingRect {
|
||||
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: layout
|
||||
id: networkPopContent
|
||||
|
||||
spacing: 8
|
||||
anchors.left: parent.left
|
||||
anchors.margins: Tokens.padding.large
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
spacing: Tokens.spacing.extraSmall / 2
|
||||
|
||||
Repeater {
|
||||
model: Helpers.Network.devices
|
||||
CustomText {
|
||||
Layout.preferredHeight: visible ? implicitHeight : 0
|
||||
Layout.rightMargin: Tokens.padding.extraSmall
|
||||
font.pointSize: Tokens.font.size.large
|
||||
text: qsTr("Wifi")
|
||||
}
|
||||
|
||||
CustomRadioButton {
|
||||
id: network
|
||||
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")
|
||||
|
||||
required property NetworkDevice modelData
|
||||
onToggled: Network.setWifi(checked)
|
||||
}
|
||||
|
||||
checked: Helpers.Network.activeDevice?.name === modelData.name
|
||||
text: modelData.description
|
||||
visible: modelData.name !== "lo"
|
||||
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: 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NetworkOverlay {
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
component NetworkItem: 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
|
||||
}
|
||||
|
||||
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("Network/PSKOverlay.qml"), {
|
||||
network: networkItem.modelData
|
||||
});
|
||||
} else {
|
||||
Network.requestConnect(networkItem.modelData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
component StatusDivider: 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
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ Item {
|
||||
Comp {
|
||||
id: content
|
||||
|
||||
anchors.centerIn: parent
|
||||
anchors.fill: parent
|
||||
shouldBeActive: root.hasCurrent
|
||||
|
||||
sourceComponent: Content {
|
||||
|
||||
@@ -12,7 +12,7 @@ Item {
|
||||
property real offsetScale: shouldBeActive ? 0 : 1
|
||||
required property var panels
|
||||
required property ShellScreen screen
|
||||
readonly property bool shouldBeActive: visibilities.dock && Config.dock.enable
|
||||
readonly property bool shouldBeActive: visibilities.dock && Config.dock.enabled
|
||||
required property PersistentProperties visibilities
|
||||
|
||||
anchors.bottomMargin: (-implicitHeight - 5) * offsetScale
|
||||
|
||||
@@ -15,6 +15,10 @@ CarouselView {
|
||||
readonly property var values: Wallpapers.query(root.search.text.split(" ").slice(1).join(" "))
|
||||
required property var visibilities
|
||||
|
||||
currentIndex: {
|
||||
const idx = Wallpapers.list.findIndex(w => w.path === Wallpapers.actualCurrent);
|
||||
return idx >= 0 ? idx : 0;
|
||||
}
|
||||
delegateProperties: ({
|
||||
visibilities: root.visibilities
|
||||
})
|
||||
@@ -41,13 +45,9 @@ CarouselView {
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
const idx = Wallpapers.list.findIndex(w => w.path === Wallpapers.actualCurrent);
|
||||
if (idx >= 0)
|
||||
jumpToIndex(idx);
|
||||
}
|
||||
Component.onDestruction: Wallpapers.stopPreview()
|
||||
onPreviewIndexChanged: (realIndex, modelData) => {
|
||||
Wallpapers.preview(modelData.path);
|
||||
onCurrentItemChanged: {
|
||||
if (currentItem)
|
||||
Wallpapers.preview(currentItem.modelData.path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ pragma Singleton
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import qs.Components
|
||||
import ZShell.Config
|
||||
import qs.Components
|
||||
import qs.Modules.Settings.Common
|
||||
import qs.Modules.Settings.Pages
|
||||
import qs.Modules.Settings.Pages.Wallpaper
|
||||
@@ -45,7 +45,11 @@ QtObject {
|
||||
|
||||
// Connectivity
|
||||
Component {
|
||||
PlaceholderComp {
|
||||
StackPage {
|
||||
Component {
|
||||
NetworkPage {
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Component {
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
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
|
||||
import qs.Services
|
||||
|
||||
PageBase {
|
||||
id: root
|
||||
|
||||
title: qsTr("Network")
|
||||
|
||||
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: connected.count === 0
|
||||
subtext: qsTr("Toggle WiFi device state")
|
||||
text: checked ? qsTr("Enabled") : qsTr("Disabled")
|
||||
|
||||
onToggled: Network.setWifi(checked)
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: connected
|
||||
|
||||
visible: Network.connectedNetworks.length > 0
|
||||
|
||||
model: ScriptModel {
|
||||
values: [...Network.connectedNetworks].sort((a, b) => {
|
||||
return b.signalStrength - a.signalStrength;
|
||||
})
|
||||
}
|
||||
|
||||
NetworkItem {
|
||||
first: false
|
||||
known: true
|
||||
repeater: connected
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component NetworkItem: ConnectedRect {
|
||||
id: networkItem
|
||||
|
||||
readonly property bool connecting: Network.connectingNetwork === modelData
|
||||
property int horizontalPadding: Tokens.padding.largeIncreased
|
||||
required property int index
|
||||
readonly property bool isSecure: Network.isSecure(modelData.security)
|
||||
required property bool known
|
||||
required property var modelData
|
||||
required property Repeater repeater
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: visible ? rowLayout.implicitHeight + Tokens.padding.smaller * 2 : 0
|
||||
first: index === 0
|
||||
last: index === (repeater.count - 1)
|
||||
radius: Tokens.rounding.small
|
||||
|
||||
RowLayout {
|
||||
id: rowLayout
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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: 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("Network/PSKOverlay.qml"), {
|
||||
network: networkItem.modelData
|
||||
});
|
||||
} else {
|
||||
Network.requestConnect(networkItem.modelData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
component Spacer: Item {
|
||||
id: spacer
|
||||
|
||||
required property Repeater repeater
|
||||
|
||||
Layout.preferredHeight: repeater.count > 0 ? Tokens.spacing.extraSmall : 0
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,9 @@ PageBase {
|
||||
readonly property var builtinIcons: ({
|
||||
audio: qsTr("Speakers"),
|
||||
microphone: qsTr("Microphone"),
|
||||
power: qsTr("Battery")
|
||||
power: qsTr("Battery"),
|
||||
network: qsTr("Network"),
|
||||
bluetooth: qsTr("Bluetooth")
|
||||
})
|
||||
|
||||
isSubPage: true
|
||||
@@ -91,5 +93,15 @@ 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
#include "carouselview.hpp"
|
||||
|
||||
#include <QSignalBlocker>
|
||||
#include <QQmlContext>
|
||||
#include <QQmlProperty>
|
||||
#include <QMouseEvent>
|
||||
#include <QWheelEvent>
|
||||
|
||||
#include <cmath>
|
||||
#include <qobject.h>
|
||||
|
||||
namespace ZShell::components {
|
||||
|
||||
@@ -81,6 +83,33 @@ void CarouselView::setDelegate(QQmlComponent* c) {
|
||||
relayout();
|
||||
}
|
||||
|
||||
void CarouselView::notifyCurrentIndexChanged() {
|
||||
if (!m_completed) return;
|
||||
emit currentIndexChanged();
|
||||
}
|
||||
|
||||
void CarouselView::notifyCurrentItemChanged() {
|
||||
if (!m_completed) return;
|
||||
emit currentItemChanged();
|
||||
}
|
||||
|
||||
void CarouselView::notifyPreviewIndexChanged(int realIndex) {
|
||||
if (!m_completed || realIndex < 0) return;
|
||||
emit previewIndexChanged(realIndex, m_model.at(realIndex));
|
||||
}
|
||||
|
||||
void CarouselView::classBegin() {}
|
||||
|
||||
void CarouselView::componentComplete() {
|
||||
m_completed = true;
|
||||
emit currentIndexChanged();
|
||||
if (m_currentRealIndex >= 0 && m_currentRealIndex < m_model.size())
|
||||
emit previewIndexChanged(
|
||||
m_currentRealIndex, m_model.at(m_currentRealIndex));
|
||||
|
||||
emit currentItemChanged();
|
||||
}
|
||||
|
||||
bool CarouselView::childMouseEventFilter(QQuickItem* item, QEvent* event) {
|
||||
Q_UNUSED(item);
|
||||
|
||||
@@ -163,7 +192,7 @@ void CarouselView::setModel(const QVariantList& m) {
|
||||
if (newCount == 0) {
|
||||
m_currentRealIndex = -1;
|
||||
m_currentItem = nullptr;
|
||||
emit currentItemChanged();
|
||||
notifyCurrentItemChanged();
|
||||
|
||||
releaseAllItems();
|
||||
rebuildArrangement();
|
||||
@@ -173,19 +202,23 @@ void CarouselView::setModel(const QVariantList& m) {
|
||||
|
||||
if (!m_initializedLayout) {
|
||||
m_centerBlockStart = 0;
|
||||
m_currentVirtualIndex = 0;
|
||||
|
||||
m_contentX = contentXForIndex(0);
|
||||
const int startVirtual =
|
||||
m_pendingCurrentIndex >= 0
|
||||
? qBound(0, m_pendingCurrentIndex, newCount - 1)
|
||||
: 0;
|
||||
|
||||
m_pendingCurrentIndex = -1;
|
||||
|
||||
m_currentVirtualIndex = startVirtual;
|
||||
m_contentX = contentXForIndex(startVirtual);
|
||||
m_currentRealIndex = realIndexOf(m_currentVirtualIndex);
|
||||
|
||||
m_previewRealIndex = m_currentRealIndex;
|
||||
|
||||
m_initializedLayout = true;
|
||||
|
||||
m_layoutCapacity =
|
||||
qMax(m_layoutCapacity, layoutCapacityForWidth(m_maxWidth));
|
||||
|
||||
setImplicitWidth(targetWidthForMaxWidth(m_maxWidth));
|
||||
|
||||
rebuildArrangement();
|
||||
@@ -320,6 +353,22 @@ void CarouselView::setDelegateProperties(const QVariantMap& v) {
|
||||
}
|
||||
|
||||
void CarouselView::setCurrentIndex(int idx) {
|
||||
if (m_model.isEmpty()) {
|
||||
m_pendingCurrentIndex = idx;
|
||||
|
||||
if (m_currentRealIndex != idx) {
|
||||
m_currentRealIndex = idx;
|
||||
notifyCurrentIndexChanged();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_completed) {
|
||||
centerInstantlyOnReal(idx);
|
||||
return;
|
||||
}
|
||||
|
||||
goToIndex(idx);
|
||||
}
|
||||
|
||||
@@ -336,7 +385,7 @@ void CarouselView::updateCurrentItem() {
|
||||
if (item == m_currentItem) return;
|
||||
|
||||
m_currentItem = item;
|
||||
emit currentItemChanged();
|
||||
notifyCurrentItemChanged();
|
||||
}
|
||||
|
||||
void CarouselView::geometryChange(const QRectF& newGeo, const QRectF& oldGeo) {
|
||||
@@ -676,15 +725,12 @@ void CarouselView::updateCurrentIndexFromContentX(bool emitPreview) {
|
||||
if (newReal != m_currentRealIndex) {
|
||||
m_currentRealIndex = newReal;
|
||||
|
||||
emit currentIndexChanged();
|
||||
notifyCurrentIndexChanged();
|
||||
}
|
||||
|
||||
if (emitPreview && newReal != m_previewRealIndex) {
|
||||
m_previewRealIndex = newReal;
|
||||
|
||||
if (newReal >= 0) {
|
||||
emit previewIndexChanged(newReal, m_model.at(newReal));
|
||||
}
|
||||
notifyPreviewIndexChanged(newReal);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1029,13 +1075,14 @@ void CarouselView::glideTo(qreal dest) {
|
||||
|
||||
cancelAnimations();
|
||||
|
||||
m_glideAnim->setDuration(m_glideDuration);
|
||||
{
|
||||
QSignalBlocker blocker(m_glideAnim);
|
||||
|
||||
m_glideAnim->setStartValue(m_contentX);
|
||||
|
||||
m_glideAnim->setEndValue(dest);
|
||||
|
||||
m_glideAnim->start();
|
||||
m_glideAnim->setDuration(m_glideDuration);
|
||||
m_glideAnim->setStartValue(m_contentX);
|
||||
m_glideAnim->setEndValue(dest);
|
||||
m_glideAnim->start();
|
||||
}
|
||||
}
|
||||
|
||||
void CarouselView::snapToNearest() {
|
||||
@@ -1078,10 +1125,6 @@ void CarouselView::decrementCurrentIndex() {
|
||||
glideTo(contentXForIndex(m_currentVirtualIndex - 1));
|
||||
}
|
||||
|
||||
void CarouselView::jumpToIndex(int realIndex) {
|
||||
centerInstantlyOnReal(realIndex);
|
||||
}
|
||||
|
||||
void CarouselView::centerInstantlyOnReal(int realIndex) {
|
||||
const int count = m_model.size();
|
||||
|
||||
@@ -1115,7 +1158,7 @@ void CarouselView::centerInstantlyOnReal(int realIndex) {
|
||||
|
||||
if (changed) emit currentIndexChanged();
|
||||
|
||||
emit previewIndexChanged(realIndex, m_model.at(realIndex));
|
||||
notifyPreviewIndexChanged(realIndex);
|
||||
|
||||
relayout();
|
||||
}
|
||||
@@ -1219,12 +1262,16 @@ void CarouselView::mouseReleaseEvent(QMouseEvent* event) {
|
||||
|
||||
cancelAnimations();
|
||||
|
||||
m_flickAnim->setDuration(
|
||||
static_cast<int>(qBound<qreal>(120, duration, 900)));
|
||||
m_flickAnim->setEasingCurve(QEasingCurve::OutQuad);
|
||||
m_flickAnim->setStartValue(m_contentX);
|
||||
m_flickAnim->setEndValue(dest);
|
||||
m_flickAnim->start();
|
||||
{
|
||||
QSignalBlocker blocker(m_flickAnim);
|
||||
|
||||
m_flickAnim->setDuration(
|
||||
static_cast<int>(qBound<qreal>(120, duration, 900)));
|
||||
m_flickAnim->setEasingCurve(QEasingCurve::OutQuad);
|
||||
m_flickAnim->setStartValue(m_contentX);
|
||||
m_flickAnim->setEndValue(dest);
|
||||
m_flickAnim->start();
|
||||
}
|
||||
|
||||
event->accept();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <QQmlParserStatus>
|
||||
#include <QElapsedTimer>
|
||||
#include <QEasingCurve>
|
||||
#include <QList>
|
||||
@@ -100,8 +101,6 @@ class CarouselView : public QQuickItem {
|
||||
Q_INVOKABLE void incrementCurrentIndex();
|
||||
Q_INVOKABLE void decrementCurrentIndex();
|
||||
|
||||
Q_INVOKABLE void jumpToIndex(int realIndex);
|
||||
|
||||
signals:
|
||||
void delegateChanged();
|
||||
void modelChanged();
|
||||
@@ -120,10 +119,10 @@ class CarouselView : public QQuickItem {
|
||||
void previewIndexChanged(int realIndex, QVariant modelData);
|
||||
|
||||
protected:
|
||||
void classBegin() override;
|
||||
void componentComplete() override;
|
||||
bool childMouseEventFilter(QQuickItem* item, QEvent* event) override;
|
||||
|
||||
void geometryChange(const QRectF& newGeo, const QRectF& oldGeo) override;
|
||||
|
||||
void mousePressEvent(QMouseEvent* event) override;
|
||||
void mouseMoveEvent(QMouseEvent* event) override;
|
||||
void mouseReleaseEvent(QMouseEvent* event) override;
|
||||
@@ -200,6 +199,10 @@ class CarouselView : public QQuickItem {
|
||||
void addVelocitySample(qint64 t, qreal x);
|
||||
qreal sampledVelocity() const;
|
||||
|
||||
void notifyCurrentIndexChanged();
|
||||
void notifyCurrentItemChanged();
|
||||
void notifyPreviewIndexChanged(int realIndex);
|
||||
|
||||
QQmlComponent* m_delegate = nullptr;
|
||||
QVariantList m_model;
|
||||
QVariantMap m_delegateProperties;
|
||||
@@ -242,6 +245,9 @@ class CarouselView : public QQuickItem {
|
||||
|
||||
QVariantAnimation* m_flickAnim = nullptr;
|
||||
|
||||
bool m_completed = false;
|
||||
int m_pendingCurrentIndex = -1;
|
||||
|
||||
bool m_initializedLayout = false;
|
||||
bool m_relayoutQueued = false;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user