mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
TrayMenuPanel: fix submenu width calculation
This commit is contained in:
@@ -18,38 +18,14 @@ SmartPanel {
|
||||
|
||||
// Internal
|
||||
property int menuWidth: 280
|
||||
readonly property int maxMenuWidth: 400 // Maximum width before text elides
|
||||
readonly property int minMenuWidth: 280
|
||||
readonly property int maxMenuWidth: 600
|
||||
preferredWidth: menuWidth
|
||||
// Height is content-driven via panelContent
|
||||
|
||||
// Open positioned relative to button
|
||||
function openAt(buttonItem) {
|
||||
open(buttonItem)
|
||||
// Recalculate width when opening
|
||||
Qt.callLater(() => {
|
||||
if (panelContent) {
|
||||
const calculatedWidth = panelContent.calculateMaxEntryWidth()
|
||||
if (calculatedWidth > 0) {
|
||||
// Remove outer padding to get entry width
|
||||
panelContent.maxEntryWidth = Math.max(0, calculatedWidth - (Style.marginM * 2))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Recalculate width when panel becomes visible
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
Qt.callLater(() => {
|
||||
if (panelContent) {
|
||||
const calculatedWidth = panelContent.calculateMaxEntryWidth()
|
||||
if (calculatedWidth > 0) {
|
||||
// Remove outer padding to get entry width
|
||||
panelContent.maxEntryWidth = Math.max(0, calculatedWidth - (Style.marginM * 2))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
panelContent: Item {
|
||||
@@ -61,74 +37,6 @@ SmartPanel {
|
||||
// Only show submenu in place (replace main menu)
|
||||
property bool inPlaceSubmenu: true
|
||||
|
||||
// TextMetrics for measuring text widths
|
||||
TextMetrics {
|
||||
id: textMetrics
|
||||
font.pointSize: Style.fontSizeS
|
||||
}
|
||||
|
||||
// Track maximum entry width
|
||||
property real maxEntryWidth: 280
|
||||
|
||||
// Calculate maximum entry width
|
||||
function calculateMaxEntryWidth() {
|
||||
if (!opener || !opener.children) {
|
||||
return 280 // fallback
|
||||
}
|
||||
|
||||
let maxWidth = 0
|
||||
const entries = [...opener.children.values]
|
||||
|
||||
for (var i = 0; i < entries.length; i++) {
|
||||
const entry = entries[i]
|
||||
if (!entry || entry.isSeparator)
|
||||
continue
|
||||
|
||||
// Calculate entry width
|
||||
let entryWidth = 0
|
||||
|
||||
// Left margin
|
||||
entryWidth += Style.marginM
|
||||
|
||||
// Text width
|
||||
const text = entry.text !== "" ? entry.text.replace(/[\n\r]+/g, ' ') : "..."
|
||||
textMetrics.text = text
|
||||
entryWidth += textMetrics.width
|
||||
|
||||
// Spacing
|
||||
entryWidth += Style.marginS
|
||||
|
||||
// Icon (if visible)
|
||||
if (entry.icon && entry.icon !== "") {
|
||||
entryWidth += Style.marginL
|
||||
entryWidth += Style.marginS
|
||||
}
|
||||
|
||||
// Menu icon (if has children)
|
||||
if (entry.hasChildren) {
|
||||
// Approximate icon width based on font size
|
||||
entryWidth += Style.fontSizeS * 1.2 // Approximate icon width
|
||||
entryWidth += Style.marginS
|
||||
}
|
||||
|
||||
// Right margin
|
||||
entryWidth += Style.marginM
|
||||
|
||||
if (entryWidth > maxWidth) {
|
||||
maxWidth = entryWidth
|
||||
}
|
||||
}
|
||||
|
||||
// Add outer padding (Style.marginM * 2)
|
||||
return Math.max(280, maxWidth + (Style.marginM * 2))
|
||||
}
|
||||
|
||||
// Update menu width when maxEntryWidth changes
|
||||
onMaxEntryWidthChanged: {
|
||||
const calculatedWidth = maxEntryWidth + (Style.marginM * 2)
|
||||
root.menuWidth = Math.min(root.maxMenuWidth, Math.max(280, calculatedWidth))
|
||||
}
|
||||
|
||||
// Let Panel size to our content
|
||||
readonly property real contentPreferredWidth: root.menuWidth
|
||||
readonly property real contentPreferredHeight: {
|
||||
@@ -146,33 +54,110 @@ SmartPanel {
|
||||
QsMenuOpener {
|
||||
id: opener
|
||||
menu: root.menu
|
||||
onChildrenChanged: Qt.callLater(() => calculateMenuWidth(opener))
|
||||
}
|
||||
|
||||
// Update menu width when children change
|
||||
onChildrenChanged: {
|
||||
// Text metrics for measuring text widths
|
||||
TextMetrics {
|
||||
id: textMetrics
|
||||
font.pointSize: Style.fontSizeS
|
||||
}
|
||||
|
||||
// Watch for submenu changes
|
||||
onActiveSubMenuChanged: {
|
||||
if (activeSubMenu && inPlaceSubmenu) {
|
||||
Qt.callLater(() => {
|
||||
const calculatedWidth = content.calculateMaxEntryWidth()
|
||||
if (calculatedWidth > 0) {
|
||||
// Remove outer padding to get entry width
|
||||
content.maxEntryWidth = Math.max(0, calculatedWidth - (Style.marginM * 2))
|
||||
// Find the subMenuOpener in the loaded component
|
||||
if (inPlaceSubMenuLoader.item) {
|
||||
const flickable = inPlaceSubMenuLoader.item.children[1] // Flickable
|
||||
if (flickable && flickable.children.length > 0) {
|
||||
const columnLayout = flickable.children[0]
|
||||
if (columnLayout && columnLayout.children.length > 0) {
|
||||
const subOpener = columnLayout.children[0]
|
||||
if (subOpener) {
|
||||
calculateMenuWidth(subOpener)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
} else if (!activeSubMenu) {
|
||||
// Reset to main menu width when submenu closes
|
||||
Qt.callLater(() => calculateMenuWidth(opener))
|
||||
}
|
||||
}
|
||||
|
||||
// Update width when menu changes
|
||||
Connections {
|
||||
target: root
|
||||
function onMenuChanged() {
|
||||
Qt.callLater(() => {
|
||||
const calculatedWidth = content.calculateMaxEntryWidth()
|
||||
if (calculatedWidth > 0) {
|
||||
// Remove outer padding to get entry width
|
||||
content.maxEntryWidth = Math.max(0, calculatedWidth - (Style.marginM * 2))
|
||||
}
|
||||
})
|
||||
// Calculate required menu width based on menu entries
|
||||
function calculateMenuWidth(menuOpener) {
|
||||
let maxWidth = root.minMenuWidth
|
||||
|
||||
// For submenus, also measure the "Back" button
|
||||
if (menuOpener !== opener && content.inPlaceSubmenu) {
|
||||
textMetrics.text = I18n.tr("settings.bar.tray.back")
|
||||
const backWidth = (Style.marginM * 4) + Style.fontSizeS + Style.marginS + textMetrics.width
|
||||
maxWidth = Math.max(maxWidth, backWidth)
|
||||
}
|
||||
|
||||
// Check all menu entries
|
||||
if (menuOpener && menuOpener.children) {
|
||||
try {
|
||||
const entries = menuOpener.children.values ? [...menuOpener.children.values] : []
|
||||
|
||||
for (var i = 0; i < entries.length; i++) {
|
||||
const entry = entries[i]
|
||||
if (!entry || entry.isSeparator)
|
||||
continue
|
||||
|
||||
const text = entry.text || ""
|
||||
if (text === "")
|
||||
continue
|
||||
|
||||
// Measure the text
|
||||
textMetrics.text = text.replace(/[\n\r]+/g, ' ')
|
||||
const textWidth = textMetrics.width
|
||||
|
||||
// Calculate total width:
|
||||
// - Outer inset: Style.marginM * 2 (parent width reduction)
|
||||
// - RowLayout margins: Style.marginM * 2 (left + right)
|
||||
// - Text width
|
||||
// - Spacing: Style.marginS
|
||||
// - Icon: Style.marginL (if present)
|
||||
// - Submenu arrow: ~20px (if present)
|
||||
let requiredWidth = (Style.marginM * 4) + textWidth
|
||||
|
||||
if (entry.icon && entry.icon !== "") {
|
||||
requiredWidth += Style.marginS + Style.marginL
|
||||
}
|
||||
|
||||
if (entry.hasChildren) {
|
||||
requiredWidth += Style.marginS + 20
|
||||
}
|
||||
|
||||
maxWidth = Math.max(maxWidth, requiredWidth)
|
||||
}
|
||||
} catch (e) {
|
||||
// Silently ignore errors during width calculation
|
||||
}
|
||||
}
|
||||
|
||||
// Check pin/unpin button (only for main menu)
|
||||
if (menuOpener === opener && root.trayItem !== null && root.widgetSection !== "" && root.widgetIndex >= 0) {
|
||||
textMetrics.text = I18n.tr("settings.bar.tray.pin-application")
|
||||
let pinWidth = (Style.marginM * 4) + textMetrics.width + Style.marginS + 20
|
||||
maxWidth = Math.max(maxWidth, pinWidth)
|
||||
|
||||
textMetrics.text = I18n.tr("settings.bar.tray.unpin-application")
|
||||
let unpinWidth = (Style.marginM * 4) + textMetrics.width + Style.marginS + 20
|
||||
maxWidth = Math.max(maxWidth, unpinWidth)
|
||||
}
|
||||
|
||||
// Clamp to min/max and apply
|
||||
const finalWidth = Math.min(root.maxMenuWidth, Math.max(root.minMenuWidth, Math.ceil(maxWidth)))
|
||||
root.menuWidth = finalWidth
|
||||
}
|
||||
|
||||
Component.onCompleted: Qt.callLater(() => calculateMenuWidth(opener))
|
||||
|
||||
Component {
|
||||
id: subMenuComponent
|
||||
|
||||
@@ -233,6 +218,7 @@ SmartPanel {
|
||||
QsMenuOpener {
|
||||
id: subMenuOpener
|
||||
menu: content.activeSubMenu
|
||||
onChildrenChanged: Qt.callLater(() => content.calculateMenuWidth(subMenuOpener))
|
||||
}
|
||||
|
||||
// Back button (only shown when submenu is in-place)
|
||||
@@ -321,46 +307,6 @@ SmartPanel {
|
||||
|
||||
color: Color.transparent
|
||||
|
||||
// Calculate and update max entry width
|
||||
Component.onCompleted: {
|
||||
if (!modelData?.isSeparator) {
|
||||
Qt.callLater(() => {
|
||||
let entryWidth = 0
|
||||
|
||||
// Left margin
|
||||
entryWidth += Style.marginM
|
||||
|
||||
// Text width
|
||||
const text = modelData?.text !== "" ? modelData.text.replace(/[\n\r]+/g, ' ') : "..."
|
||||
content.textMetrics.text = text
|
||||
entryWidth += content.textMetrics.width
|
||||
|
||||
// Spacing
|
||||
entryWidth += Style.marginS
|
||||
|
||||
// Icon (if visible)
|
||||
if (modelData?.icon && modelData.icon !== "") {
|
||||
entryWidth += Style.marginL
|
||||
entryWidth += Style.marginS
|
||||
}
|
||||
|
||||
// Menu icon (if has children)
|
||||
if (modelData?.hasChildren) {
|
||||
entryWidth += Style.fontSizeS * 1.2 // Approximate icon width
|
||||
entryWidth += Style.marginL // Submenu uses marginL for right margin on icon
|
||||
}
|
||||
|
||||
// Right margin
|
||||
entryWidth += Style.marginM
|
||||
|
||||
// Update maxEntryWidth if this entry is wider
|
||||
if (entryWidth > content.maxEntryWidth) {
|
||||
content.maxEntryWidth = entryWidth
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - (Style.marginM * 2)
|
||||
@@ -497,46 +443,6 @@ SmartPanel {
|
||||
|
||||
color: Color.transparent
|
||||
|
||||
// Calculate and update max entry width
|
||||
Component.onCompleted: {
|
||||
if (!modelData?.isSeparator) {
|
||||
Qt.callLater(() => {
|
||||
let entryWidth = 0
|
||||
|
||||
// Left margin
|
||||
entryWidth += Style.marginM
|
||||
|
||||
// Text width
|
||||
const text = modelData?.text !== "" ? modelData.text.replace(/[\n\r]+/g, ' ') : "..."
|
||||
content.textMetrics.text = text
|
||||
entryWidth += content.textMetrics.width
|
||||
|
||||
// Spacing
|
||||
entryWidth += Style.marginS
|
||||
|
||||
// Icon (if visible)
|
||||
if (modelData?.icon && modelData.icon !== "") {
|
||||
entryWidth += Style.marginL
|
||||
entryWidth += Style.marginS
|
||||
}
|
||||
|
||||
// Menu icon (if has children)
|
||||
if (modelData?.hasChildren) {
|
||||
entryWidth += Style.fontSizeS * 1.2 // Approximate icon width
|
||||
entryWidth += Style.marginS
|
||||
}
|
||||
|
||||
// Right margin
|
||||
entryWidth += Style.marginM
|
||||
|
||||
// Update maxEntryWidth if this entry is wider
|
||||
if (entryWidth > content.maxEntryWidth) {
|
||||
content.maxEntryWidth = entryWidth
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - Style.marginL
|
||||
@@ -652,37 +558,6 @@ SmartPanel {
|
||||
Layout.preferredHeight: visible ? 28 : 0
|
||||
color: Color.transparent
|
||||
|
||||
// Calculate and update max entry width
|
||||
Component.onCompleted: {
|
||||
if (visible) {
|
||||
Qt.callLater(() => {
|
||||
let entryWidth = 0
|
||||
|
||||
// Left margin
|
||||
entryWidth += Style.marginM
|
||||
|
||||
// Icon width
|
||||
entryWidth += Style.fontSizeS * 1.2 // Approximate icon width
|
||||
|
||||
// Spacing
|
||||
entryWidth += Style.marginS
|
||||
|
||||
// Text width
|
||||
const text = isFavorite ? I18n.tr("settings.bar.tray.unpin-application") : I18n.tr("settings.bar.tray.pin-application")
|
||||
content.textMetrics.text = text
|
||||
entryWidth += content.textMetrics.width
|
||||
|
||||
// Right margin
|
||||
entryWidth += Style.marginM
|
||||
|
||||
// Update maxEntryWidth if this entry is wider
|
||||
if (entryWidth > content.maxEntryWidth) {
|
||||
content.maxEntryWidth = entryWidth
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
readonly property bool isFavorite: {
|
||||
if (!root.trayItem || root.widgetSection === "" || root.widgetIndex < 0)
|
||||
return false
|
||||
|
||||
Reference in New Issue
Block a user