Typography++
NText is now fontWeightRegular Transformed a few Text into NText Checked boldness Improved DemoPanel scaling and look
This commit is contained in:
parent
6ce7c7d55d
commit
c64d14319e
16 changed files with 584 additions and 180 deletions
|
|
@ -32,105 +32,13 @@ NLoader {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
NText {
|
||||||
anchors.fill: parent
|
text: "Audio Device Selector"
|
||||||
anchors.margins: Style.marginXL * scaling
|
|
||||||
spacing: Style.marginSmall * scaling
|
|
||||||
|
|
||||||
// NIconButton
|
|
||||||
ColumnLayout {
|
|
||||||
spacing: 16 * scaling
|
|
||||||
NText {
|
|
||||||
text: "NIconButton"
|
|
||||||
color: Colors.accentSecondary
|
|
||||||
}
|
|
||||||
|
|
||||||
NIconButton {
|
|
||||||
id: myIconButton
|
|
||||||
icon: "refresh"
|
|
||||||
}
|
|
||||||
|
|
||||||
NDivider {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NToggle
|
|
||||||
ColumnLayout {
|
|
||||||
spacing: Style.marginLarge * scaling
|
|
||||||
NText {
|
|
||||||
text: "NToggle"
|
|
||||||
color: Colors.accentSecondary
|
|
||||||
}
|
|
||||||
|
|
||||||
NToggle {
|
|
||||||
label: "Label"
|
|
||||||
description: "Description"
|
|
||||||
onToggled: function (value) {
|
|
||||||
console.log("NToggle: " + value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NDivider {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NSlider
|
|
||||||
ColumnLayout {
|
|
||||||
spacing: 16 * scaling
|
|
||||||
NText {
|
|
||||||
text: "Scaling"
|
|
||||||
color: Colors.accentSecondary
|
|
||||||
}
|
|
||||||
RowLayout {
|
|
||||||
spacing: Style.marginSmall * scaling
|
|
||||||
NText {
|
|
||||||
text: `${Math.round(Scaling.overrideScale * 100)}%`
|
|
||||||
Layout.alignment: Qt.AlignVCenter
|
|
||||||
}
|
|
||||||
NSlider {
|
|
||||||
id: scaleSlider
|
|
||||||
from: 0.6
|
|
||||||
to: 1.8
|
|
||||||
stepSize: 0.01
|
|
||||||
value: Scaling.overrideScale
|
|
||||||
onMoved: function () {
|
|
||||||
Scaling.overrideScale = value
|
|
||||||
}
|
|
||||||
onPressedChanged: function () {
|
|
||||||
Scaling.overrideEnabled = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
NIconButton {
|
|
||||||
icon: "restart_alt"
|
|
||||||
sizeMultiplier: 0.7
|
|
||||||
onClicked: function () {
|
|
||||||
Scaling.overrideEnabled = false
|
|
||||||
Scaling.overrideScale = 1.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
NDivider {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} // NPanel {// id: ioSelector
|
} // NPanel {// id: ioSelector// property int tabIndex: 0// property Item anchorItem: null// signal panelClosed()// function sinkNodes() {// let nodes = Pipewire.nodes && Pipewire.nodes.values ? Pipewire.nodes.values.filter(function(n) {// return n.isSink && n.audio && n.isStream === false;// }) : [];// if (Pipewire.defaultAudioSink)
|
||||||
|
|
||||||
// property int tabIndex: 0
|
|
||||||
// property Item anchorItem: null
|
|
||||||
|
|
||||||
// signal panelClosed()
|
|
||||||
|
|
||||||
// function sinkNodes() {
|
|
||||||
// let nodes = Pipewire.nodes && Pipewire.nodes.values ? Pipewire.nodes.values.filter(function(n) {
|
|
||||||
// return n.isSink && n.audio && n.isStream === false;
|
|
||||||
// }) : [];
|
|
||||||
// if (Pipewire.defaultAudioSink)
|
|
||||||
// nodes = nodes.slice().sort(function(a, b) {
|
// nodes = nodes.slice().sort(function(a, b) {
|
||||||
// if (a.id === Pipewire.defaultAudioSink.id)
|
// if (a.id === Pipewire.defaultAudioSink.id)
|
||||||
// return -1;
|
// return -1;
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@ Variants {
|
||||||
NText {
|
NText {
|
||||||
text: screen.name
|
text: screen.name
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
font.weight: Style.fontWeightBold
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,31 +8,26 @@ import Quickshell.Widgets
|
||||||
import qs.Services
|
import qs.Services
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Row {
|
Item {
|
||||||
readonly property real scaling: Scaling.scale(screen)
|
readonly property real scaling: Scaling.scale(screen)
|
||||||
property bool containsMouse: false
|
readonly property real itemSize: 24 * scaling
|
||||||
property var systemTray: SystemTray
|
|
||||||
|
|
||||||
spacing: 8
|
width: tray.width
|
||||||
Layout.alignment: Qt.AlignVCenter
|
height: itemSize
|
||||||
|
|
||||||
Repeater {
|
Row {
|
||||||
model: systemTray.items
|
id: tray
|
||||||
delegate: Item {
|
|
||||||
width: 24 * scaling
|
|
||||||
height: 24 * scaling
|
|
||||||
|
|
||||||
visible: modelData
|
spacing: Style.marginSmall * scaling
|
||||||
property bool isHovered: trayMouseArea.containsMouse
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
|
||||||
// No animations - static display
|
Repeater {
|
||||||
Rectangle {
|
id: repeater
|
||||||
anchors.centerIn: parent
|
model: SystemTray.items
|
||||||
width: 16 * scaling
|
delegate: Item {
|
||||||
height: 16 * scaling
|
width: itemSize
|
||||||
radius: 6
|
height: itemSize
|
||||||
color: "transparent"
|
visible: modelData
|
||||||
clip: true
|
|
||||||
|
|
||||||
IconImage {
|
IconImage {
|
||||||
id: trayIcon
|
id: trayIcon
|
||||||
|
|
@ -50,6 +45,7 @@ Row {
|
||||||
|
|
||||||
// Process icon path
|
// Process icon path
|
||||||
if (icon.includes("?path=")) {
|
if (icon.includes("?path=")) {
|
||||||
|
// Seems qmlfmt does not support the following ES6 syntax: const[name, path] = icon.split
|
||||||
const chunks = icon.split("?path=")
|
const chunks = icon.split("?path=")
|
||||||
const name = chunks[0]
|
const name = chunks[0]
|
||||||
const path = chunks[1]
|
const path = chunks[1]
|
||||||
|
|
@ -60,63 +56,68 @@ Row {
|
||||||
}
|
}
|
||||||
opacity: status === Image.Ready ? 1 : 0
|
opacity: status === Image.Ready ? 1 : 0
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: trayMouseArea
|
anchors.fill: parent
|
||||||
anchors.fill: parent
|
hoverEnabled: true
|
||||||
hoverEnabled: true
|
cursorShape: Qt.PointingHandCursor
|
||||||
cursorShape: Qt.PointingHandCursor
|
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
onClicked: mouse => {
|
||||||
onClicked: mouse => {
|
if (!modelData) {
|
||||||
if (!modelData)
|
|
||||||
return
|
|
||||||
|
|
||||||
if (mouse.button === Qt.LeftButton) {
|
|
||||||
// Close any open menu first
|
|
||||||
if (trayMenu && trayMenu.visible) {
|
|
||||||
trayMenu.hideMenu()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!modelData.onlyMenu) {
|
|
||||||
modelData.activate()
|
|
||||||
}
|
|
||||||
} else if (mouse.button === Qt.MiddleButton) {
|
|
||||||
// Close any open menu first
|
|
||||||
if (trayMenu && trayMenu.visible) {
|
|
||||||
trayMenu.hideMenu()
|
|
||||||
}
|
|
||||||
|
|
||||||
modelData.secondaryActivate && modelData.secondaryActivate()
|
|
||||||
} else if (mouse.button === Qt.RightButton) {
|
|
||||||
trayTooltip.tooltipVisible = false
|
|
||||||
// If menu is already visible, close it
|
|
||||||
if (trayMenu && trayMenu.visible) {
|
|
||||||
trayMenu.hideMenu()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (modelData.hasMenu && modelData.menu && trayMenu) {
|
if (mouse.button === Qt.LeftButton) {
|
||||||
// Anchor the menu to the tray icon item (parent) and position it below the icon
|
// Close any open menu first
|
||||||
const menuX = (width / 2) - (trayMenu.width / 2)
|
if (trayMenu && trayMenu.visible) {
|
||||||
const menuY = height + 20 * scaling
|
trayMenu.hideMenu()
|
||||||
trayMenu.menu = modelData.menu
|
}
|
||||||
trayMenu.showAt(parent, menuX, menuY)
|
|
||||||
} else {
|
|
||||||
|
|
||||||
console.log("Tray: no menu available for", modelData.id, "or trayMenu not set")
|
if (!modelData.onlyMenu) {
|
||||||
|
modelData.activate()
|
||||||
|
}
|
||||||
|
} else if (mouse.button === Qt.MiddleButton) {
|
||||||
|
// Close any open menu first
|
||||||
|
if (trayMenu && trayMenu.visible) {
|
||||||
|
trayMenu.hideMenu()
|
||||||
|
}
|
||||||
|
|
||||||
|
modelData.secondaryActivate && modelData.secondaryActivate()
|
||||||
|
} else if (mouse.button === Qt.RightButton) {
|
||||||
|
trayTooltip.hide()
|
||||||
|
// If menu is already visible, close it
|
||||||
|
if (trayMenu && trayMenu.visible) {
|
||||||
|
trayMenu.hideMenu()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modelData.hasMenu && modelData.menu && trayMenu) {
|
||||||
|
// Anchor the menu to the tray icon item (parent) and position it below the icon
|
||||||
|
const menuX = (width / 2) - (trayMenu.width / 2)
|
||||||
|
const menuY = height + 20 * scaling
|
||||||
|
trayMenu.menu = modelData.menu
|
||||||
|
trayMenu.showAt(parent, menuX, menuY)
|
||||||
|
} else {
|
||||||
|
|
||||||
|
console.log("Tray: no menu available for", modelData.id, "or trayMenu not set")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
onEntered: trayTooltip.show()
|
||||||
onEntered: trayTooltip.show()
|
onExited: trayTooltip.hide()
|
||||||
onExited: trayTooltip.hide()
|
}
|
||||||
}
|
|
||||||
|
|
||||||
NTooltip {
|
NTooltip {
|
||||||
id: trayTooltip
|
id: trayTooltip
|
||||||
target: trayIcon
|
target: trayIcon
|
||||||
text: modelData.tooltipTitle || modelData.name || modelData.id || "Tray Item"
|
text: modelData.tooltipTitle || modelData.name || modelData.id || "Tray Item"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Attached TrayMenu
|
||||||
|
TrayMenu {
|
||||||
|
id: trayMenu
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
480
Modules/Bar/TrayMenu.qml
Normal file
480
Modules/Bar/TrayMenu.qml
Normal file
|
|
@ -0,0 +1,480 @@
|
||||||
|
pragma ComponentBehavior
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
import Quickshell
|
||||||
|
import qs.Services
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
PopupWindow {
|
||||||
|
id: trayMenu
|
||||||
|
|
||||||
|
readonly property real scaling: Scaling.scale(screen)
|
||||||
|
property QsMenuHandle menu
|
||||||
|
property var anchorItem: null
|
||||||
|
property real anchorX
|
||||||
|
property real anchorY
|
||||||
|
|
||||||
|
implicitWidth: 180
|
||||||
|
implicitHeight: Math.max(40, listView.contentHeight + 12)
|
||||||
|
visible: false
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
|
anchor.item: anchorItem ? anchorItem : null
|
||||||
|
anchor.rect.x: anchorX
|
||||||
|
anchor.rect.y: anchorY - 4
|
||||||
|
|
||||||
|
// Recursive function to destroy all open submenus in delegate tree, safely avoiding infinite recursion
|
||||||
|
function destroySubmenusRecursively(item) {
|
||||||
|
if (!item || !item.contentItem)
|
||||||
|
return
|
||||||
|
var children = item.contentItem.children
|
||||||
|
for (var i = 0; i < children.length; ++i) {
|
||||||
|
var child = children[i]
|
||||||
|
if (child.subMenu) {
|
||||||
|
child.subMenu.hideMenu()
|
||||||
|
child.subMenu.destroy()
|
||||||
|
child.subMenu = null
|
||||||
|
}
|
||||||
|
// Recursively destroy submenus only if the child has contentItem to prevent issues
|
||||||
|
if (child.contentItem) {
|
||||||
|
destroySubmenusRecursively(child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showAt(item, x, y) {
|
||||||
|
if (!item) {
|
||||||
|
console.warn("CustomTrayMenu: anchorItem is undefined, won't show menu.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
anchorItem = item
|
||||||
|
anchorX = x
|
||||||
|
anchorY = y
|
||||||
|
visible = true
|
||||||
|
forceActiveFocus()
|
||||||
|
Qt.callLater(() => trayMenu.anchor.updateAnchor())
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideMenu() {
|
||||||
|
visible = false
|
||||||
|
destroySubmenusRecursively(listView)
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
anchors.fill: parent
|
||||||
|
Keys.onEscapePressed: trayMenu.hideMenu()
|
||||||
|
}
|
||||||
|
|
||||||
|
QsMenuOpener {
|
||||||
|
id: opener
|
||||||
|
menu: trayMenu.menu
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: bg
|
||||||
|
anchors.fill: parent
|
||||||
|
color: Colors.backgroundSecondary
|
||||||
|
border.color: Colors.outline
|
||||||
|
border.width: 1
|
||||||
|
radius: 12
|
||||||
|
z: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
id: listView
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 6
|
||||||
|
spacing: 2
|
||||||
|
interactive: false
|
||||||
|
enabled: trayMenu.visible
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
model: ScriptModel {
|
||||||
|
values: opener.children ? [...opener.children.values] : []
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate: Rectangle {
|
||||||
|
id: entry
|
||||||
|
required property var modelData
|
||||||
|
|
||||||
|
width: listView.width
|
||||||
|
height: (modelData?.isSeparator) ? 8 : 32
|
||||||
|
color: "transparent"
|
||||||
|
radius: 12
|
||||||
|
|
||||||
|
property var subMenu: null
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: parent.width - 20
|
||||||
|
height: 1
|
||||||
|
color: Qt.darker(Colors.backgroundPrimary, 1.4)
|
||||||
|
visible: modelData?.isSeparator ?? false
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: bg
|
||||||
|
anchors.fill: parent
|
||||||
|
color: mouseArea.containsMouse ? Colors.highlight : "transparent"
|
||||||
|
radius: 8
|
||||||
|
visible: !(modelData?.isSeparator ?? false)
|
||||||
|
property color hoverTextColor: mouseArea.containsMouse ? Colors.onAccent : Colors.textPrimary
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.leftMargin: 12
|
||||||
|
anchors.rightMargin: 12
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
NText {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
color: (modelData?.enabled ?? true) ? bg.hoverTextColor : Colors.textDisabled
|
||||||
|
text: modelData?.text ?? ""
|
||||||
|
font.pointSize: Colors.fontSizeSmall * scaling
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
|
||||||
|
Image {
|
||||||
|
Layout.preferredWidth: 16
|
||||||
|
Layout.preferredHeight: 16
|
||||||
|
source: modelData?.icon ?? ""
|
||||||
|
visible: (modelData?.icon ?? "") !== ""
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chevron right for optional submenu
|
||||||
|
Text {
|
||||||
|
text: modelData?.hasChildren ? "menu" : ""
|
||||||
|
font.family: "Material Symbols Outlined"
|
||||||
|
font.pointSize: Colors.fontSizeMedium * scaling
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
visible: modelData?.hasChildren ?? false
|
||||||
|
color: Colors.textPrimary
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: mouseArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
enabled: (modelData?.enabled ?? true) && !(modelData?.isSeparator ?? false) && trayMenu.visible
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
if (modelData && !modelData.isSeparator) {
|
||||||
|
if (modelData.hasChildren) {
|
||||||
|
// Submenus open on hover; ignore click here
|
||||||
|
return
|
||||||
|
}
|
||||||
|
modelData.triggered()
|
||||||
|
trayMenu.hideMenu()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onEntered: {
|
||||||
|
if (!trayMenu.visible)
|
||||||
|
return
|
||||||
|
|
||||||
|
if (modelData?.hasChildren) {
|
||||||
|
// Close sibling submenus immediately
|
||||||
|
for (var i = 0; i < listView.contentItem.children.length; i++) {
|
||||||
|
const sibling = listView.contentItem.children[i]
|
||||||
|
if (sibling !== entry && sibling.subMenu) {
|
||||||
|
sibling.subMenu.hideMenu()
|
||||||
|
sibling.subMenu.destroy()
|
||||||
|
sibling.subMenu = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (entry.subMenu) {
|
||||||
|
entry.subMenu.hideMenu()
|
||||||
|
entry.subMenu.destroy()
|
||||||
|
entry.subMenu = null
|
||||||
|
}
|
||||||
|
var globalPos = entry.mapToGlobal(0, 0)
|
||||||
|
var submenuWidth = 180
|
||||||
|
var gap = 12
|
||||||
|
var openLeft = (globalPos.x + entry.width + submenuWidth > Screen.width)
|
||||||
|
var anchorX = openLeft ? -submenuWidth - gap : entry.width + gap
|
||||||
|
|
||||||
|
entry.subMenu = subMenuComponent.createObject(trayMenu, {
|
||||||
|
"menu": modelData,
|
||||||
|
"anchorItem": entry,
|
||||||
|
"anchorX": anchorX,
|
||||||
|
"anchorY": 0
|
||||||
|
})
|
||||||
|
entry.subMenu.showAt(entry, anchorX, 0)
|
||||||
|
} else {
|
||||||
|
// Hovered item without submenu; close siblings
|
||||||
|
for (var i = 0; i < listView.contentItem.children.length; i++) {
|
||||||
|
const sibling = listView.contentItem.children[i]
|
||||||
|
if (sibling.subMenu) {
|
||||||
|
sibling.subMenu.hideMenu()
|
||||||
|
sibling.subMenu.destroy()
|
||||||
|
sibling.subMenu = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (entry.subMenu) {
|
||||||
|
entry.subMenu.hideMenu()
|
||||||
|
entry.subMenu.destroy()
|
||||||
|
entry.subMenu = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onExited: {
|
||||||
|
if (entry.subMenu && !entry.subMenu.containsMouse()) {
|
||||||
|
entry.subMenu.hideMenu()
|
||||||
|
entry.subMenu.destroy()
|
||||||
|
entry.subMenu = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simplified containsMouse without recursive calls to avoid stack overflow
|
||||||
|
function containsMouse() {
|
||||||
|
return mouseArea.containsMouse
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onDestruction: {
|
||||||
|
if (subMenu) {
|
||||||
|
subMenu.destroy()
|
||||||
|
subMenu = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: subMenuComponent
|
||||||
|
|
||||||
|
PopupWindow {
|
||||||
|
id: subMenu
|
||||||
|
implicitWidth: 180
|
||||||
|
implicitHeight: Math.max(40, listView.contentHeight + 12)
|
||||||
|
visible: false
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
|
property QsMenuHandle menu
|
||||||
|
property var anchorItem: null
|
||||||
|
property real anchorX
|
||||||
|
property real anchorY
|
||||||
|
|
||||||
|
anchor.item: anchorItem ? anchorItem : null
|
||||||
|
anchor.rect.x: anchorX
|
||||||
|
anchor.rect.y: anchorY
|
||||||
|
|
||||||
|
function showAt(item, x, y) {
|
||||||
|
if (!item) {
|
||||||
|
console.warn("subMenuComponent: anchorItem is undefined, not showing menu.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
anchorItem = item
|
||||||
|
anchorX = x
|
||||||
|
anchorY = y
|
||||||
|
visible = true
|
||||||
|
Qt.callLater(() => subMenu.anchor.updateAnchor())
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideMenu() {
|
||||||
|
visible = false
|
||||||
|
// Close all submenus recursively in this submenu
|
||||||
|
for (var i = 0; i < listView.contentItem.children.length; i++) {
|
||||||
|
const child = listView.contentItem.children[i]
|
||||||
|
if (child.subMenu) {
|
||||||
|
child.subMenu.hideMenu()
|
||||||
|
child.subMenu.destroy()
|
||||||
|
child.subMenu = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simplified containsMouse avoiding recursive calls
|
||||||
|
function containsMouse() {
|
||||||
|
return subMenu.containsMouse
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
anchors.fill: parent
|
||||||
|
Keys.onEscapePressed: subMenu.hideMenu()
|
||||||
|
}
|
||||||
|
|
||||||
|
QsMenuOpener {
|
||||||
|
id: opener
|
||||||
|
menu: subMenu.menu
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: bg
|
||||||
|
anchors.fill: parent
|
||||||
|
color: Colors.backgroundPrimary
|
||||||
|
border.color: Colors.outline
|
||||||
|
border.width: 1
|
||||||
|
radius: 12
|
||||||
|
z: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
id: listView
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 6
|
||||||
|
spacing: 2
|
||||||
|
interactive: false
|
||||||
|
enabled: subMenu.visible
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
model: ScriptModel {
|
||||||
|
values: opener.children ? [...opener.children.values] : []
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate: Rectangle {
|
||||||
|
id: entry
|
||||||
|
required property var modelData
|
||||||
|
|
||||||
|
width: listView.width
|
||||||
|
height: (modelData?.isSeparator) ? 8 : 32
|
||||||
|
color: "transparent"
|
||||||
|
radius: 12
|
||||||
|
|
||||||
|
property var subMenu: null
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: parent.width - 20
|
||||||
|
height: 1
|
||||||
|
color: Qt.darker(Colors.surfaceVariant, 1.4)
|
||||||
|
visible: modelData?.isSeparator ?? false
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: bg
|
||||||
|
anchors.fill: parent
|
||||||
|
color: mouseArea.containsMouse ? Colors.highlight : "transparent"
|
||||||
|
radius: 8
|
||||||
|
visible: !(modelData?.isSeparator ?? false)
|
||||||
|
property color hoverTextColor: mouseArea.containsMouse ? Colors.onAccent : Colors.textPrimary
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.leftMargin: 12
|
||||||
|
anchors.rightMargin: 12
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
NText {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
color: (modelData?.enabled ?? true) ? bg.hoverTextColor : Colors.textDisabled
|
||||||
|
text: modelData?.text ?? ""
|
||||||
|
font.pointSize: Colors.fontSizeSmall * Colors.scale(screen)
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
|
||||||
|
Image {
|
||||||
|
Layout.preferredWidth: 16
|
||||||
|
Layout.preferredHeight: 16
|
||||||
|
source: modelData?.icon ?? ""
|
||||||
|
visible: (modelData?.icon ?? "") !== ""
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
}
|
||||||
|
|
||||||
|
NText {
|
||||||
|
text: modelData?.hasChildren ? "\uE5CC" : ""
|
||||||
|
font.family: "Material Symbols Outlined"
|
||||||
|
font.pointSize: Colors.fontSizeMedium * scaling
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
visible: modelData?.hasChildren ?? false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: mouseArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
enabled: (modelData?.enabled ?? true) && !(modelData?.isSeparator ?? false) && subMenu.visible
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
if (modelData && !modelData.isSeparator) {
|
||||||
|
if (modelData.hasChildren) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
modelData.triggered()
|
||||||
|
trayMenu.hideMenu()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onEntered: {
|
||||||
|
if (!subMenu.visible)
|
||||||
|
return
|
||||||
|
|
||||||
|
if (modelData?.hasChildren) {
|
||||||
|
for (var i = 0; i < listView.contentItem.children.length; i++) {
|
||||||
|
const sibling = listView.contentItem.children[i]
|
||||||
|
if (sibling !== entry && sibling.subMenu) {
|
||||||
|
sibling.subMenu.hideMenu()
|
||||||
|
sibling.subMenu.destroy()
|
||||||
|
sibling.subMenu = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (entry.subMenu) {
|
||||||
|
entry.subMenu.hideMenu()
|
||||||
|
entry.subMenu.destroy()
|
||||||
|
entry.subMenu = null
|
||||||
|
}
|
||||||
|
var globalPos = entry.mapToGlobal(0, 0)
|
||||||
|
var submenuWidth = 180
|
||||||
|
var gap = 12
|
||||||
|
var openLeft = (globalPos.x + entry.width + submenuWidth > Screen.width)
|
||||||
|
var anchorX = openLeft ? -submenuWidth - gap : entry.width + gap
|
||||||
|
|
||||||
|
entry.subMenu = subMenuComponent.createObject(subMenu, {
|
||||||
|
"menu": modelData,
|
||||||
|
"anchorItem": entry,
|
||||||
|
"anchorX": anchorX,
|
||||||
|
"anchorY": 0
|
||||||
|
})
|
||||||
|
entry.subMenu.showAt(entry, anchorX, 0)
|
||||||
|
} else {
|
||||||
|
for (var i = 0; i < listView.contentItem.children.length; i++) {
|
||||||
|
const sibling = listView.contentItem.children[i]
|
||||||
|
if (sibling.subMenu) {
|
||||||
|
sibling.subMenu.hideMenu()
|
||||||
|
sibling.subMenu.destroy()
|
||||||
|
sibling.subMenu = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (entry.subMenu) {
|
||||||
|
entry.subMenu.hideMenu()
|
||||||
|
entry.subMenu.destroy()
|
||||||
|
entry.subMenu = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onExited: {
|
||||||
|
if (entry.subMenu && !entry.subMenu.containsMouse()) {
|
||||||
|
entry.subMenu.hideMenu()
|
||||||
|
entry.subMenu.destroy()
|
||||||
|
entry.subMenu = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simplified & safe containsMouse avoiding recursion
|
||||||
|
function containsMouse() {
|
||||||
|
return mouseArea.containsMouse
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onDestruction: {
|
||||||
|
if (subMenu) {
|
||||||
|
subMenu.destroy()
|
||||||
|
subMenu = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -57,6 +57,7 @@ NLoader {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
font.pointSize: Style.fontSizeMedium * scaling
|
font.pointSize: Style.fontSizeMedium * scaling
|
||||||
|
font.weight: Style.fontWeightBold
|
||||||
color: Colors.accentPrimary
|
color: Colors.accentPrimary
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -85,6 +86,7 @@ NLoader {
|
||||||
text: shortName
|
text: shortName
|
||||||
color: Colors.accentSecondary
|
color: Colors.accentSecondary
|
||||||
font.pointSize: Style.fontSizeMedium * scaling
|
font.pointSize: Style.fontSizeMedium * scaling
|
||||||
|
font.weight: Style.fontWeightBold
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
width: Style.baseWidgetSize * scaling
|
width: Style.baseWidgetSize * scaling
|
||||||
}
|
}
|
||||||
|
|
@ -124,7 +126,7 @@ NLoader {
|
||||||
color: model.today ? Colors.onAccent : Colors.textPrimary
|
color: model.today ? Colors.onAccent : Colors.textPrimary
|
||||||
opacity: model.month === grid.month ? Style.opacityHeavy : Style.opacityLight
|
opacity: model.month === grid.month ? Style.opacityHeavy : Style.opacityLight
|
||||||
font.pointSize: Style.fontSizeMedium * scaling
|
font.pointSize: Style.fontSizeMedium * scaling
|
||||||
font.bold: model.today ? true : false
|
font.weight: model.today ? Style.fontWeightBold : Style.fontWeightRegular
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on color {
|
Behavior on color {
|
||||||
|
|
|
||||||
|
|
@ -23,12 +23,13 @@ NLoader {
|
||||||
Component.onCompleted: show()
|
Component.onCompleted: show()
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
id: bgRect
|
||||||
color: Colors.backgroundPrimary
|
color: Colors.backgroundPrimary
|
||||||
radius: Style.radiusMedium * scaling
|
radius: Style.radiusMedium * scaling
|
||||||
border.color: Colors.backgroundTertiary
|
border.color: Colors.backgroundTertiary
|
||||||
border.width: Math.min(1, Style.borderMedium * scaling)
|
border.width: Math.min(1, Style.borderMedium * scaling)
|
||||||
width: 500 * scaling
|
width: 500 * scaling
|
||||||
height: 400
|
height: 400 * scaling
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
|
|
||||||
// Prevent closing when clicking in the panel bg
|
// Prevent closing when clicking in the panel bg
|
||||||
|
|
@ -47,11 +48,14 @@ NLoader {
|
||||||
NText {
|
NText {
|
||||||
text: "NIconButton"
|
text: "NIconButton"
|
||||||
color: Colors.accentSecondary
|
color: Colors.accentSecondary
|
||||||
|
font.weight: Style.fontWeightBold
|
||||||
}
|
}
|
||||||
|
|
||||||
NIconButton {
|
NIconButton {
|
||||||
id: myIconButton
|
id: myIconButton
|
||||||
icon: "refresh"
|
icon: "celebration"
|
||||||
|
sizeMultiplier: 1.0
|
||||||
|
fontPointSize: Style.fontSizeXL * scaling
|
||||||
}
|
}
|
||||||
|
|
||||||
NDivider {
|
NDivider {
|
||||||
|
|
@ -65,6 +69,7 @@ NLoader {
|
||||||
NText {
|
NText {
|
||||||
text: "NToggle"
|
text: "NToggle"
|
||||||
color: Colors.accentSecondary
|
color: Colors.accentSecondary
|
||||||
|
font.weight: Style.fontWeightBold
|
||||||
}
|
}
|
||||||
|
|
||||||
NToggle {
|
NToggle {
|
||||||
|
|
@ -86,6 +91,7 @@ NLoader {
|
||||||
NText {
|
NText {
|
||||||
text: "Scaling"
|
text: "Scaling"
|
||||||
color: Colors.accentSecondary
|
color: Colors.accentSecondary
|
||||||
|
font.weight: Style.fontWeightBold
|
||||||
}
|
}
|
||||||
RowLayout {
|
RowLayout {
|
||||||
spacing: Style.marginSmall * scaling
|
spacing: Style.marginSmall * scaling
|
||||||
|
|
@ -99,6 +105,7 @@ NLoader {
|
||||||
to: 1.8
|
to: 1.8
|
||||||
stepSize: 0.01
|
stepSize: 0.01
|
||||||
value: Scaling.overrideScale
|
value: Scaling.overrideScale
|
||||||
|
implicitWidth: bgRect.width * 0.75
|
||||||
onMoved: function () {
|
onMoved: function () {
|
||||||
Scaling.overrideScale = value
|
Scaling.overrideScale = value
|
||||||
}
|
}
|
||||||
|
|
@ -107,8 +114,9 @@ NLoader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NIconButton {
|
NIconButton {
|
||||||
icon: "restart_alt"
|
icon: "refresh"
|
||||||
sizeMultiplier: 0.7
|
sizeMultiplier: 1.0
|
||||||
|
fontPointSize: Style.fontSizeXL * scaling
|
||||||
onClicked: function () {
|
onClicked: function () {
|
||||||
Scaling.overrideEnabled = false
|
Scaling.overrideEnabled = false
|
||||||
Scaling.overrideScale = 1.0
|
Scaling.overrideScale = 1.0
|
||||||
|
|
|
||||||
|
|
@ -155,7 +155,7 @@ PanelWindow {
|
||||||
NText {
|
NText {
|
||||||
text: model.summary || "No summary"
|
text: model.summary || "No summary"
|
||||||
font.pointSize: Style.fontSizeLarge
|
font.pointSize: Style.fontSizeLarge
|
||||||
font.bold: true
|
font.weight: Style.fontWeightBold
|
||||||
color: Colors.textPrimary
|
color: Colors.textPrimary
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
width: 300 * scaling
|
width: 300 * scaling
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ NBox {
|
||||||
spacing: 2 * scaling
|
spacing: 2 * scaling
|
||||||
NText {
|
NText {
|
||||||
text: Quickshell.env("USER") || "user"
|
text: Quickshell.env("USER") || "user"
|
||||||
|
font.weight: Style.fontWeightBold
|
||||||
}
|
}
|
||||||
NText {
|
NText {
|
||||||
text: "System Uptime: —"
|
text: "System Uptime: —"
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ NBox {
|
||||||
Text {
|
Text {
|
||||||
text: "sunny"
|
text: "sunny"
|
||||||
font.family: "Material Symbols Outlined"
|
font.family: "Material Symbols Outlined"
|
||||||
font.pointSize: Style.fontSizeXL * scaling
|
font.pointSize: Style.fontSizeXXL * 1.25 * scaling
|
||||||
color: Colors.accentSecondary
|
color: Colors.accentSecondary
|
||||||
}
|
}
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
|
|
@ -36,6 +36,7 @@ NBox {
|
||||||
NText {
|
NText {
|
||||||
text: "26°C"
|
text: "26°C"
|
||||||
font.pointSize: (Style.fontSizeXL + 6) * scaling
|
font.pointSize: (Style.fontSizeXL + 6) * scaling
|
||||||
|
font.weight: Style.fontWeightBold
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -55,10 +56,12 @@ NBox {
|
||||||
spacing: 2 * scaling
|
spacing: 2 * scaling
|
||||||
NText {
|
NText {
|
||||||
text: ["Sun", "Mon", "Tue", "Wed", "Thu"][index]
|
text: ["Sun", "Mon", "Tue", "Wed", "Thu"][index]
|
||||||
|
font.weight: Style.fontWeightBold
|
||||||
}
|
}
|
||||||
Text {
|
NText {
|
||||||
text: index % 2 === 0 ? "wb_sunny" : "cloud"
|
text: index % 2 === 0 ? "wb_sunny" : "cloud"
|
||||||
font.family: "Material Symbols Outlined"
|
font.family: "Material Symbols Outlined"
|
||||||
|
font.weight: Style.fontWeightBold
|
||||||
color: Colors.textSecondary
|
color: Colors.textSecondary
|
||||||
}
|
}
|
||||||
NText {
|
NText {
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ Singleton {
|
||||||
property real fontSizeMedium: 11
|
property real fontSizeMedium: 11
|
||||||
property real fontSizeLarge: 13
|
property real fontSizeLarge: 13
|
||||||
property real fontSizeXL: 18
|
property real fontSizeXL: 18
|
||||||
|
property real fontSizeXXL: 24
|
||||||
|
|
||||||
// Font weight / Unsure if we keep em?
|
// Font weight / Unsure if we keep em?
|
||||||
property int fontWeightRegular: 400
|
property int fontWeightRegular: 400
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ Rectangle {
|
||||||
id: textItem
|
id: textItem
|
||||||
text: Time.time
|
text: Time.time
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
|
font.weight: Style.fontWeightBold
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ Rectangle {
|
||||||
|
|
||||||
color: root.hovering ? Colors.accentPrimary : "transparent"
|
color: root.hovering ? Colors.accentPrimary : "transparent"
|
||||||
|
|
||||||
Text {
|
NText {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
anchors.horizontalCenterOffset: 0
|
anchors.horizontalCenterOffset: 0
|
||||||
anchors.verticalCenterOffset: 0
|
anchors.verticalCenterOffset: 0
|
||||||
|
|
|
||||||
|
|
@ -52,13 +52,12 @@ Item {
|
||||||
bottomLeftRadius: pillHeight * 0.5
|
bottomLeftRadius: pillHeight * 0.5
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
Text {
|
NText {
|
||||||
id: textItem
|
id: textItem
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: root.text
|
text: root.text
|
||||||
font.pointSize: Colors.fontSizeSmall * scaling
|
font.pointSize: Colors.fontSizeSmall * scaling
|
||||||
font.family: Settings.data.ui.fontFamily
|
font.weight: Style.fontWeightBold
|
||||||
font.weight: Font.Bold
|
|
||||||
color: textColor
|
color: textColor
|
||||||
visible: showPill
|
visible: showPill
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,6 @@ Text {
|
||||||
|
|
||||||
font.family: Settings.data.ui.fontFamily
|
font.family: Settings.data.ui.fontFamily
|
||||||
font.pointSize: Style.fontSizeMedium * scaling
|
font.pointSize: Style.fontSizeMedium * scaling
|
||||||
font.weight: Font.Bold
|
font.weight: Style.fontWeightRegular
|
||||||
color: Colors.textPrimary
|
color: Colors.textPrimary
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,14 +19,14 @@ RowLayout {
|
||||||
spacing: 2 * scaling
|
spacing: 2 * scaling
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
Text {
|
NText {
|
||||||
text: label
|
text: label
|
||||||
font.pointSize: Style.fontSizeMedium * scaling
|
font.pointSize: Style.fontSizeMedium * scaling
|
||||||
font.bold: true
|
font.weight: Style.fontWeightBold
|
||||||
color: Colors.textPrimary
|
color: Colors.textPrimary
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
NText {
|
||||||
text: description
|
text: description
|
||||||
font.pointSize: Style.fontSizeSmall * scaling
|
font.pointSize: Style.fontSizeSmall * scaling
|
||||||
color: Colors.textSecondary
|
color: Colors.textSecondary
|
||||||
|
|
|
||||||
|
|
@ -112,7 +112,6 @@ Window {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: root.text
|
text: root.text
|
||||||
font.pointSize: Style.fontSizeMedium * scaling
|
font.pointSize: Style.fontSizeMedium * scaling
|
||||||
font.weight: Style.fontWeightRegular
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue