mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-14 23:34:01 -04:00
it doesnt interfere, with a setting to turn it off Some compositors like kwin/labwc/sway cannot have this without breaking some built in layering they do. niri can have this, but for focus follow mouse it may need to be disabled. fixes #3717
302 lines
9.6 KiB
QML
302 lines
9.6 KiB
QML
pragma ComponentBehavior: Bound
|
|
|
|
import QtQuick
|
|
import qs.Common
|
|
import qs.Modules.ControlCenter
|
|
import qs.Services
|
|
import qs.DCommon.Widgets
|
|
import qs.Widgets
|
|
import "../utils/widgets.js" as WidgetUtils
|
|
|
|
DBottomSheet {
|
|
id: root
|
|
|
|
property var widgets: []
|
|
readonly property var addable: widgets.filter(widget => widget.enabled !== false)
|
|
readonly property string query: searchField.text.trim()
|
|
readonly property var matches: WidgetUtils.filterWidgets(addable, query)
|
|
readonly property var categories: [
|
|
{
|
|
"id": "user",
|
|
"title": I18n.tr("User"),
|
|
"icon": "account_circle"
|
|
},
|
|
{
|
|
"id": "network",
|
|
"title": I18n.tr("Network"),
|
|
"icon": "hub"
|
|
},
|
|
{
|
|
"id": "audio",
|
|
"title": I18n.tr("Audio"),
|
|
"icon": "tune"
|
|
},
|
|
{
|
|
"id": "display",
|
|
"title": I18n.tr("Display"),
|
|
"icon": "display_settings"
|
|
},
|
|
{
|
|
"id": "system",
|
|
"title": I18n.tr("System"),
|
|
"icon": "dns"
|
|
},
|
|
{
|
|
"id": "plugins",
|
|
"title": I18n.tr("Plugins"),
|
|
"icon": "extension"
|
|
}
|
|
]
|
|
readonly property var shownCategories: categories.filter(category => addable.some(widget => (widget.category ?? "plugins") === category.id))
|
|
|
|
signal chosen(string widgetId)
|
|
|
|
function toggleCategory(id) {
|
|
const collapsed = CacheData.controlCenterCollapsedCategories;
|
|
CacheData.set("controlCenterCollapsedCategories", collapsed.includes(id) ? collapsed.filter(entry => entry !== id) : collapsed.concat([id]));
|
|
}
|
|
|
|
Accessible.name: I18n.tr("Widgets")
|
|
initialFocusItem: searchField
|
|
sheetHeight: height * CcMetrics.widgetSheetHeightRatio
|
|
contentSpacing: Theme.spacingM
|
|
|
|
onOpenedChanged: {
|
|
if (opened)
|
|
searchField.text = "";
|
|
}
|
|
|
|
component PreviewGrid: Grid {
|
|
id: grid
|
|
|
|
property var entries: []
|
|
|
|
signal chosen(string widgetId)
|
|
|
|
columns: Math.max(1, Math.floor((width + columnSpacing) / (CcMetrics.previewSize + columnSpacing)))
|
|
columnSpacing: Theme.spacingM
|
|
rowSpacing: Theme.spacingL
|
|
|
|
Repeater {
|
|
model: grid.entries
|
|
|
|
StyledButton {
|
|
id: item
|
|
|
|
required property var modelData
|
|
|
|
width: (grid.width - grid.columnSpacing * (grid.columns - 1)) / grid.columns
|
|
height: preview.height + Theme.spacingS + label.implicitHeight
|
|
Accessible.name: modelData.text
|
|
Accessible.description: modelData.description ?? ""
|
|
onClicked: grid.chosen(modelData.id)
|
|
|
|
Rectangle {
|
|
id: preview
|
|
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
width: CcMetrics.previewSize
|
|
height: width
|
|
radius: item.modelData.id === "user" ? Theme.fullRadius(width, height) : Theme.cornerRadiusLIncreased
|
|
color: CcMetrics.iconBoxInactiveColor
|
|
|
|
DIcon {
|
|
anchors.centerIn: parent
|
|
name: item.modelData.icon
|
|
size: Theme.iconSizeLarge
|
|
color: Theme.primary
|
|
visible: item.modelData.id !== "user" || PortalService.profileImageUrl === ""
|
|
}
|
|
|
|
DCircularImage {
|
|
anchors.fill: parent
|
|
imageSource: PortalService.profileImageUrl
|
|
fallbackIcon: "material:person"
|
|
visible: item.modelData.id === "user" && PortalService.profileImageUrl !== ""
|
|
}
|
|
|
|
FocusRing {
|
|
visible: item.visualFocus
|
|
}
|
|
|
|
StateLayer {
|
|
control: item
|
|
stateColor: Theme.primary
|
|
}
|
|
}
|
|
|
|
StyledText {
|
|
id: label
|
|
anchors.top: preview.bottom
|
|
anchors.topMargin: Theme.spacingS
|
|
width: parent.width
|
|
text: item.modelData.text
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
color: Theme.surfaceText
|
|
horizontalAlignment: Text.AlignHCenter
|
|
wrapMode: Text.NoWrap
|
|
elide: Text.ElideRight
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
component CategoryCard: Rectangle {
|
|
id: card
|
|
|
|
required property var category
|
|
property var entries: []
|
|
property bool expanded: true
|
|
readonly property real collapsedHeight: Theme.avatarSize + Theme.spacingL * 2
|
|
|
|
signal chosen(string widgetId)
|
|
signal toggleRequested
|
|
|
|
height: expanded ? itemsClip.y + items.implicitHeight + Theme.spacingL : collapsedHeight
|
|
radius: Theme.cornerRadiusXL
|
|
color: CcMetrics.rowColor
|
|
|
|
Behavior on height {
|
|
enabled: CcMetrics.animationsEnabled
|
|
NumberAnimation {
|
|
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
|
easing.type: Easing.BezierSpline
|
|
easing.bezierCurve: Theme.expressiveCurves.expressiveDefaultSpatial
|
|
}
|
|
}
|
|
|
|
StyledButton {
|
|
id: header
|
|
|
|
width: parent.width
|
|
height: card.collapsedHeight
|
|
radius: card.radius
|
|
bottomLeftRadius: card.expanded ? 0 : card.radius
|
|
bottomRightRadius: card.expanded ? 0 : card.radius
|
|
Accessible.name: card.category.title
|
|
onClicked: card.toggleRequested()
|
|
|
|
Rectangle {
|
|
id: badge
|
|
anchors.left: parent.left
|
|
anchors.leftMargin: Theme.spacingL
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
width: Theme.avatarSize
|
|
height: width
|
|
radius: Theme.fullRadius(width, height)
|
|
color: CcMetrics.iconBoxInactiveColor
|
|
|
|
DIcon {
|
|
anchors.centerIn: parent
|
|
name: card.category.icon
|
|
size: Theme.iconSize
|
|
color: Theme.primary
|
|
}
|
|
}
|
|
|
|
StyledText {
|
|
anchors.left: badge.right
|
|
anchors.leftMargin: Theme.spacingL
|
|
anchors.right: chevron.left
|
|
anchors.rightMargin: Theme.spacingM
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
text: card.category.title
|
|
font.pixelSize: Theme.fontSizeMedium
|
|
font.weight: Theme.fontWeightMedium
|
|
color: Theme.surfaceText
|
|
elide: Text.ElideRight
|
|
}
|
|
|
|
DIcon {
|
|
id: chevron
|
|
anchors.right: parent.right
|
|
anchors.rightMargin: Theme.spacingL
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
name: "expand_more"
|
|
size: Theme.iconSize
|
|
color: Theme.onSurfaceVariant
|
|
rotation: card.expanded ? 180 : 0
|
|
|
|
Behavior on rotation {
|
|
enabled: CcMetrics.animationsEnabled
|
|
NumberAnimation {
|
|
duration: Theme.expressiveDurations.expressiveFastSpatial
|
|
easing.type: Easing.BezierSpline
|
|
easing.bezierCurve: Theme.expressiveCurves.expressiveFastSpatial
|
|
}
|
|
}
|
|
}
|
|
|
|
FocusRing {
|
|
visible: header.visualFocus
|
|
}
|
|
|
|
StateLayer {
|
|
control: header
|
|
}
|
|
}
|
|
|
|
Item {
|
|
id: itemsClip
|
|
x: Theme.spacingL
|
|
y: header.height + Theme.spacingM
|
|
width: parent.width - Theme.spacingL * 2
|
|
height: Math.max(0, card.height - y)
|
|
visible: card.height > card.collapsedHeight
|
|
clip: true
|
|
|
|
PreviewGrid {
|
|
id: items
|
|
width: parent.width
|
|
entries: card.entries
|
|
enabled: card.expanded
|
|
onChosen: widgetId => card.chosen(widgetId)
|
|
}
|
|
}
|
|
}
|
|
|
|
DSearchField {
|
|
id: searchField
|
|
width: parent.width
|
|
height: Theme.fieldHeightLarge
|
|
placeholderText: I18n.tr("Search widgets...")
|
|
onAccepted: {
|
|
if (root.matches.length > 0)
|
|
root.chosen(root.matches[0].id);
|
|
}
|
|
}
|
|
|
|
Repeater {
|
|
model: root.query === "" ? root.shownCategories : []
|
|
|
|
CategoryCard {
|
|
required property var modelData
|
|
|
|
width: parent?.width ?? 0
|
|
category: modelData
|
|
entries: root.addable.filter(widget => (widget.category ?? "plugins") === modelData.id)
|
|
expanded: !CacheData.controlCenterCollapsedCategories.includes(modelData.id)
|
|
onChosen: widgetId => root.chosen(widgetId)
|
|
onToggleRequested: root.toggleCategory(modelData.id)
|
|
}
|
|
}
|
|
|
|
PreviewGrid {
|
|
width: parent.width
|
|
visible: root.query !== ""
|
|
entries: root.query !== "" ? root.matches : []
|
|
onChosen: widgetId => root.chosen(widgetId)
|
|
}
|
|
|
|
StyledText {
|
|
width: parent.width
|
|
visible: root.matches.length === 0
|
|
topPadding: Theme.spacingL
|
|
bottomPadding: Theme.spacingL
|
|
text: I18n.tr("No widgets available")
|
|
font.pixelSize: Theme.fontSizeMedium
|
|
color: Theme.surfaceVariantText
|
|
horizontalAlignment: Text.AlignHCenter
|
|
}
|
|
}
|