mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
Launcher: remove redundant NIcopnTabButton
This commit is contained in:
@@ -1049,7 +1049,7 @@
|
||||
"keybinds-left": "Move left",
|
||||
"keybinds-modifier-description": "Shortcuts must include a modifier key (Ctrl or Alt).",
|
||||
"keybinds-modifier-title": "Modifier required",
|
||||
"keybinds-remove": "Remove/Delete",
|
||||
"keybinds-remove": "Remove / Delete",
|
||||
"keybinds-right": "Move right",
|
||||
"keybinds-title": "Navigation keybinds",
|
||||
"keybinds-up": "Move up",
|
||||
|
||||
@@ -749,7 +749,7 @@ Rectangle {
|
||||
|
||||
Repeater {
|
||||
model: root.providerCategories
|
||||
NIconTabButton {
|
||||
NTabButton {
|
||||
required property string modelData
|
||||
required property int index
|
||||
icon: root.currentProvider.categoryIcons ? (root.currentProvider.categoryIcons[modelData] || "star") : "star"
|
||||
|
||||
@@ -4,6 +4,7 @@ import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Services.Notifications
|
||||
import Quickshell.Wayland
|
||||
import "../../../Helpers/Keybinds.js" as Keybinds
|
||||
import qs.Commons
|
||||
import qs.Modules.MainScreen
|
||||
import qs.Services.System
|
||||
@@ -23,6 +24,215 @@ SmartPanel {
|
||||
panelContent: Rectangle {
|
||||
id: panelContent
|
||||
color: "transparent"
|
||||
focus: true
|
||||
|
||||
// Force focus when opened
|
||||
Connections {
|
||||
target: root
|
||||
function onOpened() {
|
||||
panelContent.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onPressed: event => {
|
||||
// Tab navigation for categories
|
||||
if (event.key === Qt.Key_Tab) {
|
||||
currentRange = (currentRange + 1) % 4;
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === Qt.Key_Backtab) { // Shift+Tab
|
||||
currentRange = (currentRange - 1 + 4) % 4;
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Navigation Up/Down
|
||||
if (checkKey(event, 'up')) {
|
||||
moveSelection(-1);
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
if (checkKey(event, 'down')) {
|
||||
moveSelection(1);
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Action Navigation Left/Right
|
||||
if (checkKey(event, 'left')) {
|
||||
moveAction(-1);
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
if (checkKey(event, 'right')) {
|
||||
moveAction(1);
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Activation (Enter)
|
||||
if (checkKey(event, 'enter')) {
|
||||
activateSelection();
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Removal (Delete/Remove)
|
||||
if (checkKey(event, 'keyRemove') || event.key === Qt.Key_Delete) {
|
||||
removeSelection();
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function moveSelection(dir) {
|
||||
var m = NotificationService.historyList;
|
||||
if (!m || m.count === 0)
|
||||
return;
|
||||
|
||||
var newIndex = focusIndex;
|
||||
var found = false;
|
||||
var count = m.count;
|
||||
|
||||
// If no selection yet, start from beginning (or end if up)
|
||||
if (focusIndex === -1) {
|
||||
if (dir > 0)
|
||||
newIndex = -1;
|
||||
else
|
||||
newIndex = count;
|
||||
}
|
||||
|
||||
// Loop to find next visible item
|
||||
var loopCount = 0;
|
||||
while (loopCount < count) {
|
||||
newIndex += dir;
|
||||
|
||||
// Bounds check
|
||||
if (newIndex < 0 || newIndex >= count) {
|
||||
break; // Stop at edges
|
||||
}
|
||||
|
||||
var item = m.get(newIndex);
|
||||
if (item && isInCurrentRange(item.timestamp)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
loopCount++;
|
||||
}
|
||||
|
||||
if (found) {
|
||||
focusIndex = newIndex;
|
||||
actionIndex = -1; // Reset action selection
|
||||
scrollToItem(focusIndex);
|
||||
}
|
||||
}
|
||||
|
||||
function moveAction(dir) {
|
||||
if (focusIndex === -1)
|
||||
return;
|
||||
var item = NotificationService.historyList.get(focusIndex);
|
||||
if (!item)
|
||||
return;
|
||||
|
||||
// Parse actions
|
||||
var actions = [];
|
||||
try {
|
||||
actions = JSON.parse(item.actionsJson || "[]");
|
||||
} catch (e) {}
|
||||
|
||||
if (actions.length === 0)
|
||||
return;
|
||||
|
||||
var newActionIndex = actionIndex + dir;
|
||||
|
||||
// Clamp between -1 (body) and actions.length - 1
|
||||
if (newActionIndex < -1)
|
||||
newActionIndex = -1;
|
||||
if (newActionIndex >= actions.length)
|
||||
newActionIndex = actions.length - 1;
|
||||
|
||||
actionIndex = newActionIndex;
|
||||
}
|
||||
|
||||
function activateSelection() {
|
||||
if (focusIndex === -1)
|
||||
return;
|
||||
var item = NotificationService.historyList.get(focusIndex);
|
||||
if (!item)
|
||||
return;
|
||||
|
||||
if (actionIndex >= 0) {
|
||||
// Invoke action
|
||||
var actions = [];
|
||||
try {
|
||||
actions = JSON.parse(item.actionsJson || "[]");
|
||||
} catch (e) {}
|
||||
if (actionIndex < actions.length) {
|
||||
NotificationService.invokeAction(item.id, actions[actionIndex].identifier);
|
||||
}
|
||||
} else {
|
||||
// Toggle expansion or open?
|
||||
// User request didn't specify. Let's toggle expansion logic if we can access it.
|
||||
// We can communicate with the delegate via a property or signal?
|
||||
// Delegates read 'scrollView.expandedId'.
|
||||
if (scrollView.expandedId === item.id) {
|
||||
scrollView.expandedId = "";
|
||||
} else {
|
||||
scrollView.expandedId = item.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function removeSelection() {
|
||||
if (focusIndex === -1)
|
||||
return;
|
||||
var item = NotificationService.historyList.get(focusIndex);
|
||||
if (!item)
|
||||
return;
|
||||
|
||||
NotificationService.removeFromHistory(item.id);
|
||||
// selection updates automatically?
|
||||
// If we remove item at index i, the next item becomes index i.
|
||||
// So focusIndex is still valid (unless it was last item).
|
||||
// But we should re-verify if it exists.
|
||||
// Actually NotificationService removal might be async or immediate.
|
||||
// If immediate, model count decreases.
|
||||
// We might need to clamp focusIndex.
|
||||
// Let's handle this in a helper or just let the user navigate again.
|
||||
// Better UX: select next available or previous if last.
|
||||
}
|
||||
|
||||
function scrollToItem(index) {
|
||||
// Find the delegate item
|
||||
if (index < 0 || index >= notificationColumn.children.length)
|
||||
return;
|
||||
|
||||
var item = notificationColumn.children[index];
|
||||
if (item && item.visible) {
|
||||
// Use the internal flickable from NScrollView for accurate scrolling
|
||||
var flickable = scrollView._internalFlickable;
|
||||
if (!flickable || !flickable.contentItem)
|
||||
return;
|
||||
|
||||
var pos = flickable.contentItem.mapFromItem(item, 0, 0);
|
||||
var itemY = pos.y;
|
||||
var itemHeight = item.height;
|
||||
|
||||
var currentContentY = flickable.contentY;
|
||||
var viewHeight = flickable.height;
|
||||
|
||||
// Check if above visible area
|
||||
if (itemY < currentContentY) {
|
||||
flickable.contentY = Math.max(0, itemY - Style.marginM);
|
||||
} else
|
||||
// Check if below visible area
|
||||
if (itemY + itemHeight > currentContentY + viewHeight) {
|
||||
flickable.contentY = (itemY + itemHeight) - viewHeight + Style.marginM;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate content height based on header + tabs (if visible) + content
|
||||
property real calculatedHeight: {
|
||||
@@ -41,6 +251,20 @@ SmartPanel {
|
||||
// 0 = All, 1 = Today, 2 = Yesterday, 3 = Earlier
|
||||
property int currentRange: 1 // start on Today by default
|
||||
property bool groupByDate: true
|
||||
onCurrentRangeChanged: resetFocus()
|
||||
|
||||
// Keyboard navigation state
|
||||
property int focusIndex: -1
|
||||
property int actionIndex: -1 // For actions within a notification
|
||||
|
||||
function resetFocus() {
|
||||
focusIndex = -1;
|
||||
actionIndex = -1;
|
||||
}
|
||||
|
||||
function checkKey(event, settingName) {
|
||||
return Keybinds.checkKey(event, settingName, Settings);
|
||||
}
|
||||
|
||||
// Helper functions (lazy-loaded with panelContent)
|
||||
function dateOnly(d) {
|
||||
@@ -277,7 +501,7 @@ SmartPanel {
|
||||
id: emptyState
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginM
|
||||
spacing: Style.marginL
|
||||
spacing: Style.marginM
|
||||
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
@@ -333,6 +557,7 @@ SmartPanel {
|
||||
visible: panelContent.isInCurrentRange(model.timestamp)
|
||||
height: visible && !isRemoving ? contentColumn.height + Style.marginXL : 0
|
||||
|
||||
property int listIndex: index
|
||||
property string notificationId: model.id
|
||||
property bool isExpanded: scrollView.expandedId === notificationId
|
||||
property bool canExpand: summaryText.truncated || bodyText.truncated
|
||||
@@ -415,12 +640,20 @@ SmartPanel {
|
||||
}
|
||||
}
|
||||
|
||||
property bool isFocused: index === panelContent.focusIndex
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: Style.radiusM
|
||||
color: Color.mSurfaceVariant
|
||||
border.color: Settings.data.ui.boxBorderEnabled ? Qt.alpha(Color.mOutline, Style.opacityHeavy) : "transparent"
|
||||
border.width: Style.borderS
|
||||
border.color: {
|
||||
if (notificationDelegate.isFocused)
|
||||
return Color.mPrimary;
|
||||
if (Settings.data.ui.boxBorderEnabled)
|
||||
return Qt.alpha(Color.mOutline, Style.opacityHeavy);
|
||||
return "transparent";
|
||||
}
|
||||
border.width: notificationDelegate.isFocused ? Style.borderM : Style.borderS
|
||||
|
||||
Behavior on color {
|
||||
enabled: !Settings.data.general.animationDisabled
|
||||
@@ -438,6 +671,9 @@ SmartPanel {
|
||||
enabled: !notificationDelegate.isRemoving
|
||||
cursorShape: notificationDelegate.canExpand ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onPressed: mouse => {
|
||||
panelContent.focusIndex = index;
|
||||
panelContent.actionIndex = -1;
|
||||
|
||||
if (mouse.button !== Qt.LeftButton)
|
||||
return;
|
||||
const globalPoint = historyInteractionArea.mapToGlobal(mouse.x, mouse.y);
|
||||
@@ -641,14 +877,26 @@ SmartPanel {
|
||||
|
||||
Repeater {
|
||||
model: notificationDelegate.actionsList
|
||||
|
||||
delegate: NButton {
|
||||
text: modelData.text
|
||||
fontSize: Style.fontSizeS
|
||||
backgroundColor: Color.mPrimary
|
||||
textColor: Color.mOnPrimary
|
||||
|
||||
readonly property bool actionNavActive: notificationDelegate.isFocused && panelContent.actionIndex !== -1
|
||||
readonly property bool isSelected: actionNavActive && panelContent.actionIndex === index
|
||||
|
||||
backgroundColor: isSelected ? Color.mSecondary : Color.mPrimary
|
||||
textColor: isSelected ? Color.mOnSecondary : Color.mOnPrimary
|
||||
|
||||
outlined: false
|
||||
implicitHeight: 24
|
||||
|
||||
onHoveredChanged: {
|
||||
if (hovered) {
|
||||
panelContent.focusIndex = notificationDelegate.listIndex;
|
||||
}
|
||||
}
|
||||
|
||||
// Capture modelData in a property to avoid reference errors
|
||||
property var actionData: modelData
|
||||
onClicked: {
|
||||
@@ -664,7 +912,7 @@ SmartPanel {
|
||||
icon: "trash"
|
||||
tooltipText: I18n.tr("tooltips.delete-notification")
|
||||
baseSize: Style.baseWidgetSize * 0.7
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.top: parent.top
|
||||
|
||||
onClicked: {
|
||||
NotificationService.removeFromHistory(notificationId);
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
// Public properties
|
||||
property string icon: ""
|
||||
property string tooltipText: ""
|
||||
property bool checked: false
|
||||
property int tabIndex: 0
|
||||
|
||||
// Internal state
|
||||
property bool isHovered: false
|
||||
|
||||
signal clicked
|
||||
|
||||
// Sizing
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
// Styling
|
||||
radius: Style.iRadiusXS
|
||||
color: root.checked ? Color.mPrimary : (root.isHovered ? Color.mHover : Color.mSurface)
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
NIcon {
|
||||
id: tabIcon
|
||||
anchors.centerIn: parent
|
||||
icon: root.icon
|
||||
pointSize: Style.fontSizeL
|
||||
color: root.checked ? Color.mOnPrimary : root.isHovered ? Color.mOnHover : Color.mOnSurface
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onEntered: {
|
||||
root.isHovered = true;
|
||||
if (root.tooltipText) {
|
||||
TooltipService.show(parent, root.tooltipText);
|
||||
}
|
||||
}
|
||||
onExited: {
|
||||
root.isHovered = false;
|
||||
if (root.tooltipText) {
|
||||
TooltipService.hide();
|
||||
}
|
||||
}
|
||||
onClicked: {
|
||||
if (root.tooltipText) {
|
||||
TooltipService.hide();
|
||||
}
|
||||
root.clicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
+67
-23
@@ -7,8 +7,11 @@ import qs.Widgets
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
// Public properties
|
||||
// Public properties
|
||||
property string text: ""
|
||||
property string icon: ""
|
||||
property string tooltipText: ""
|
||||
property bool checked: false
|
||||
property int tabIndex: 0
|
||||
property real pointSize: Style.fontSizeM
|
||||
@@ -22,7 +25,7 @@ Rectangle {
|
||||
|
||||
// Sizing
|
||||
Layout.fillHeight: true
|
||||
implicitWidth: tabText.implicitWidth + Style.marginXL
|
||||
implicitWidth: contentLayout.implicitWidth + Style.marginXL
|
||||
|
||||
topLeftRadius: isFirst ? Style.iRadiusM : Style.iRadiusXXXS
|
||||
bottomLeftRadius: isFirst ? Style.iRadiusM : Style.iRadiusXXXS
|
||||
@@ -41,27 +44,57 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
NText {
|
||||
id: tabText
|
||||
y: Style.pixelAlignCenter(parent.height, height)
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
leftMargin: Style.marginS
|
||||
rightMargin: Style.marginS
|
||||
}
|
||||
text: root.text
|
||||
pointSize: root.pointSize
|
||||
font.weight: Style.fontWeightSemiBold
|
||||
color: root.isHovered ? Color.mOnHover : (root.checked ? Color.mOnPrimary : Color.mOnSurface)
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
// Content
|
||||
RowLayout {
|
||||
id: contentLayout
|
||||
anchors.centerIn: parent
|
||||
width: Math.min(implicitWidth, parent.width - (Style.marginS * 2))
|
||||
spacing: (root.icon !== "" && root.text !== "") ? Style.marginXS : 0
|
||||
|
||||
Behavior on color {
|
||||
enabled: !Color.isTransitioning
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.OutCubic
|
||||
NIcon {
|
||||
visible: root.icon !== ""
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
icon: root.icon
|
||||
pointSize: root.pointSize * 1.2
|
||||
color: root.isHovered ? Color.mOnHover : (root.checked ? Color.mOnPrimary : Color.mOnSurface)
|
||||
|
||||
Behavior on color {
|
||||
enabled: !Color.isTransitioning
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NText {
|
||||
id: tabText
|
||||
visible: root.text !== ""
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
text: root.text
|
||||
pointSize: root.pointSize
|
||||
font.weight: Style.fontWeightSemiBold
|
||||
color: root.isHovered ? Color.mOnHover : (root.checked ? Color.mOnPrimary : Color.mOnSurface)
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
|
||||
Behavior on color {
|
||||
enabled: !Color.isTransitioning
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tooltip
|
||||
Timer {
|
||||
id: tooltipTimer
|
||||
interval: 500
|
||||
onTriggered: {
|
||||
if (root.isHovered && root.tooltipText !== "") {
|
||||
TooltipService.show(root, root.tooltipText);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,8 +103,19 @@ Rectangle {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onEntered: root.isHovered = true
|
||||
onExited: root.isHovered = false
|
||||
onEntered: {
|
||||
root.isHovered = true;
|
||||
if (root.tooltipText !== "") {
|
||||
tooltipTimer.start();
|
||||
}
|
||||
}
|
||||
onExited: {
|
||||
root.isHovered = false;
|
||||
tooltipTimer.stop();
|
||||
if (root.tooltipText !== "") {
|
||||
TooltipService.hide();
|
||||
}
|
||||
}
|
||||
onClicked: {
|
||||
root.clicked();
|
||||
// Update parent NTabBar's currentIndex
|
||||
|
||||
Reference in New Issue
Block a user