mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
NGridView + NScrollView + NListView: everywhere, with auto top/bottom gradients.
This commit is contained in:
@@ -1300,14 +1300,12 @@ SmartPanel {
|
||||
}
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
NScrollView {
|
||||
Layout.alignment: Qt.AlignTop | Qt.AlignLeft
|
||||
Layout.topMargin: Style.fontSizeL + Style.marginXL
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
clip: true
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
contentWidth: availableWidth
|
||||
horizontalPolicy: ScrollBar.AlwaysOff
|
||||
|
||||
NText {
|
||||
width: parent.width
|
||||
|
||||
@@ -541,39 +541,6 @@ SmartPanel {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Overlay gradient to smooth the hard cut due to scrolling at the bottom (only visible when scrollable)
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "transparent"
|
||||
visible: scrollView.ScrollBar.vertical && scrollView.ScrollBar.vertical.size < 1.0
|
||||
opacity: {
|
||||
const scrollBar = scrollView.ScrollBar.vertical;
|
||||
return (scrollBar.position + scrollBar.size >= 0.99) ? 0 : 1;
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
|
||||
gradient: Gradient {
|
||||
GradientStop {
|
||||
position: 0.0
|
||||
color: "transparent"
|
||||
}
|
||||
GradientStop {
|
||||
position: 0.85
|
||||
color: "transparent"
|
||||
}
|
||||
GradientStop {
|
||||
position: 1.0
|
||||
color: Qt.alpha(Color.mSurface, 0.95)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,11 +96,12 @@ ColumnLayout {
|
||||
}
|
||||
|
||||
// List of current blacklist items
|
||||
ListView {
|
||||
NListView {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 150
|
||||
Layout.topMargin: Style.marginL // Increased top margin
|
||||
clip: true
|
||||
gradientColor: Color.mSurface
|
||||
|
||||
model: blacklistModel
|
||||
delegate: Item {
|
||||
width: ListView.width
|
||||
|
||||
@@ -803,6 +803,8 @@ Item {
|
||||
spacing: Style.marginXS
|
||||
visible: root.searchText.trim() !== ""
|
||||
verticalPolicy: ScrollBar.AsNeeded
|
||||
gradientColor: Color.mSurface
|
||||
reserveScrollbarSpace: false
|
||||
|
||||
HoverHandler {
|
||||
onPointChanged: {
|
||||
@@ -901,6 +903,8 @@ Item {
|
||||
spacing: Style.marginXS
|
||||
currentIndex: root.currentTabIndex
|
||||
verticalPolicy: ScrollBar.AsNeeded
|
||||
gradientColor: Color.mSurface
|
||||
reserveScrollbarSpace: false
|
||||
|
||||
delegate: Rectangle {
|
||||
id: tabItem
|
||||
@@ -1014,38 +1018,6 @@ Item {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Overlay gradient for sidebar scrolling
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.borderS
|
||||
radius: Style.radiusM
|
||||
color: "transparent"
|
||||
visible: sidebarList.verticalScrollBarActive
|
||||
opacity: (sidebarList.contentY + sidebarList.height >= sidebarList.contentHeight - 10) ? 0 : 1
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
|
||||
gradient: Gradient {
|
||||
GradientStop {
|
||||
position: 0.0
|
||||
color: "transparent"
|
||||
}
|
||||
GradientStop {
|
||||
position: 0.95
|
||||
color: "transparent"
|
||||
}
|
||||
GradientStop {
|
||||
position: 1.0
|
||||
color: Color.mSurfaceVariant
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Content pane
|
||||
@@ -1128,39 +1100,38 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
sourceComponent: Flickable {
|
||||
id: flickable
|
||||
sourceComponent: NScrollView {
|
||||
id: scrollView
|
||||
anchors.fill: parent
|
||||
pressDelay: 200
|
||||
horizontalPolicy: ScrollBar.AlwaysOff
|
||||
verticalPolicy: ScrollBar.AsNeeded
|
||||
leftPadding: Style.marginL
|
||||
topPadding: Style.marginL
|
||||
bottomPadding: Style.marginL
|
||||
userRightPadding: Style.marginL
|
||||
reserveScrollbarSpace: false
|
||||
|
||||
NScrollView {
|
||||
id: scrollView
|
||||
anchors.fill: parent
|
||||
horizontalPolicy: ScrollBar.AlwaysOff
|
||||
verticalPolicy: ScrollBar.AsNeeded
|
||||
padding: Style.marginL
|
||||
Component.onCompleted: {
|
||||
root.activeScrollView = scrollView;
|
||||
}
|
||||
Component.onCompleted: {
|
||||
root.activeScrollView = scrollView;
|
||||
}
|
||||
|
||||
Loader {
|
||||
active: true
|
||||
sourceComponent: root.tabsModel[index]?.source
|
||||
width: scrollView.availableWidth
|
||||
onLoaded: {
|
||||
if (item && item.hasOwnProperty("screen")) {
|
||||
item.screen = root.screen;
|
||||
}
|
||||
root.activeTabContent = item;
|
||||
// Handle pending subtab + highlight from search navigation
|
||||
if (root.highlightLabelKey) {
|
||||
if (root._pendingSubTab >= 0) {
|
||||
root.setSubTabIndex(root._pendingSubTab);
|
||||
root._pendingSubTab = -1;
|
||||
}
|
||||
highlightScrollTimer.targetKey = root.highlightLabelKey;
|
||||
highlightScrollTimer.restart();
|
||||
Loader {
|
||||
active: true
|
||||
sourceComponent: root.tabsModel[index]?.source
|
||||
width: scrollView.availableWidth
|
||||
onLoaded: {
|
||||
if (item && item.hasOwnProperty("screen")) {
|
||||
item.screen = root.screen;
|
||||
}
|
||||
root.activeTabContent = item;
|
||||
// Handle pending subtab + highlight from search navigation
|
||||
if (root.highlightLabelKey) {
|
||||
if (root._pendingSubTab >= 0) {
|
||||
root.setSubTabIndex(root._pendingSubTab);
|
||||
root._pendingSubTab = -1;
|
||||
}
|
||||
highlightScrollTimer.targetKey = root.highlightLabelKey;
|
||||
highlightScrollTimer.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1168,41 +1139,6 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
// Overlay gradient for content scrolling
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "transparent"
|
||||
visible: root.activeScrollView && root.activeScrollView.ScrollBar.vertical && root.activeScrollView.ScrollBar.vertical.size < 1.0
|
||||
opacity: {
|
||||
if (!root.activeScrollView)
|
||||
return 1;
|
||||
const scrollBar = root.activeScrollView.ScrollBar.vertical;
|
||||
return (scrollBar.position + scrollBar.size >= 0.99) ? 0 : 1;
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
|
||||
gradient: Gradient {
|
||||
GradientStop {
|
||||
position: 0.0
|
||||
color: "transparent"
|
||||
}
|
||||
GradientStop {
|
||||
position: 0.95
|
||||
color: "transparent"
|
||||
}
|
||||
GradientStop {
|
||||
position: 1.0
|
||||
color: Qt.alpha(Color.mSurfaceVariant, 0.95)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Highlight overlay for search results
|
||||
Rectangle {
|
||||
id: highlightOverlay
|
||||
|
||||
@@ -19,12 +19,11 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: listView.contentHeight
|
||||
|
||||
ListView {
|
||||
NListView {
|
||||
id: listView
|
||||
anchors.fill: parent
|
||||
spacing: Style.marginS
|
||||
interactive: false
|
||||
clip: true
|
||||
model: root.entriesModel
|
||||
|
||||
delegate: Item {
|
||||
|
||||
@@ -87,13 +87,11 @@ ColumnLayout {
|
||||
}
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
NScrollView {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
clip: true
|
||||
contentWidth: availableWidth
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||
horizontalPolicy: ScrollBar.AlwaysOff
|
||||
verticalPolicy: ScrollBar.AsNeeded
|
||||
|
||||
ColumnLayout {
|
||||
width: parent.width
|
||||
|
||||
@@ -56,13 +56,11 @@ ColumnLayout {
|
||||
}
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
NScrollView {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
clip: true
|
||||
contentWidth: availableWidth
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||
horizontalPolicy: ScrollBar.AlwaysOff
|
||||
verticalPolicy: ScrollBar.AsNeeded
|
||||
|
||||
ColumnLayout {
|
||||
width: parent.width
|
||||
|
||||
@@ -135,15 +135,16 @@ ColumnLayout {
|
||||
// Wallpaper gallery strip
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 88
|
||||
Layout.preferredHeight: 92
|
||||
visible: filteredWallpapers.length > 0
|
||||
|
||||
ScrollView {
|
||||
NScrollView {
|
||||
id: galleryScroll
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
ScrollBar.horizontal.policy: ScrollBar.AsNeeded
|
||||
ScrollBar.vertical.policy: ScrollBar.AlwaysOff
|
||||
horizontalPolicy: ScrollBar.AsNeeded
|
||||
verticalPolicy: ScrollBar.AlwaysOff
|
||||
showGradientMasks: false
|
||||
reserveScrollbarSpace: false
|
||||
|
||||
// Enable vertical mouse wheel to scroll the horizontal strip by moving contentX
|
||||
WheelHandler {
|
||||
|
||||
@@ -46,7 +46,7 @@ SmartPanel {
|
||||
return;
|
||||
let view = contentItem.screenRepeater.itemAt(contentItem.currentScreenIndex);
|
||||
if (view?.gridView) {
|
||||
if (!view.gridView.activeFocus) {
|
||||
if (!view.gridView.hasActiveFocus) {
|
||||
view.gridView.forceActiveFocus();
|
||||
if (view.gridView.currentIndex < 0 && view.gridView.model.length > 0) {
|
||||
view.gridView.currentIndex = 0;
|
||||
@@ -65,7 +65,7 @@ SmartPanel {
|
||||
if (!contentItem)
|
||||
return;
|
||||
let view = contentItem.screenRepeater.itemAt(contentItem.currentScreenIndex);
|
||||
if (view?.gridView?.activeFocus) {
|
||||
if (view?.gridView?.hasActiveFocus) {
|
||||
if (view.gridView.currentIndex < 0 && view.gridView.model.length > 0) {
|
||||
view.gridView.currentIndex = 0;
|
||||
} else {
|
||||
@@ -78,7 +78,7 @@ SmartPanel {
|
||||
if (!contentItem)
|
||||
return;
|
||||
let view = contentItem.screenRepeater.itemAt(contentItem.currentScreenIndex);
|
||||
if (view?.gridView?.activeFocus) {
|
||||
if (view?.gridView?.hasActiveFocus) {
|
||||
if (view.gridView.currentIndex < 0 && view.gridView.model.length > 0) {
|
||||
view.gridView.currentIndex = 0;
|
||||
} else {
|
||||
@@ -91,7 +91,7 @@ SmartPanel {
|
||||
if (!contentItem)
|
||||
return;
|
||||
let view = contentItem.screenRepeater.itemAt(contentItem.currentScreenIndex);
|
||||
if (view?.gridView?.activeFocus) {
|
||||
if (view?.gridView?.hasActiveFocus) {
|
||||
if (view.gridView.currentIndex < 0 && view.gridView.model.length > 0) {
|
||||
view.gridView.currentIndex = 0;
|
||||
} else {
|
||||
@@ -104,7 +104,7 @@ SmartPanel {
|
||||
if (!contentItem)
|
||||
return;
|
||||
let view = contentItem.screenRepeater.itemAt(contentItem.currentScreenIndex);
|
||||
if (view?.gridView?.activeFocus) {
|
||||
if (view?.gridView?.hasActiveFocus) {
|
||||
let gridView = view.gridView;
|
||||
if (gridView.currentIndex >= 0 && gridView.currentIndex < gridView.model.length) {
|
||||
view.selectItem(gridView.model[gridView.currentIndex]);
|
||||
@@ -534,51 +534,6 @@ SmartPanel {
|
||||
id: wallhavenView
|
||||
}
|
||||
}
|
||||
|
||||
// Overlay gradient to smooth the hard cut due to scrolling
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.borderS
|
||||
radius: Style.radiusM
|
||||
|
||||
// Get active grid view for scroll position
|
||||
readonly property var activeGridView: {
|
||||
if (Settings.data.wallpaper.useWallhaven) {
|
||||
return wallhavenView.gridView;
|
||||
} else {
|
||||
const view = screenRepeater.itemAt(currentScreenIndex);
|
||||
return view?.gridView ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
opacity: {
|
||||
if (!activeGridView)
|
||||
return 1;
|
||||
return (activeGridView.contentY + activeGridView.height >= activeGridView.contentHeight - 10) ? 0 : 1;
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
|
||||
gradient: Gradient {
|
||||
GradientStop {
|
||||
position: 0.0
|
||||
color: "transparent"
|
||||
}
|
||||
GradientStop {
|
||||
position: 0.9
|
||||
color: "transparent"
|
||||
}
|
||||
GradientStop {
|
||||
position: 1.0
|
||||
color: Color.mSurfaceVariant
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -832,7 +787,7 @@ SmartPanel {
|
||||
}
|
||||
}
|
||||
|
||||
GridView {
|
||||
NGridView {
|
||||
id: wallpaperGridView
|
||||
|
||||
Layout.fillWidth: true
|
||||
@@ -840,10 +795,9 @@ SmartPanel {
|
||||
|
||||
visible: !WallpaperService.scanning
|
||||
interactive: true
|
||||
clip: true
|
||||
focus: true
|
||||
keyNavigationEnabled: true
|
||||
keyNavigationWraps: false
|
||||
highlightFollowsCurrentItem: false
|
||||
currentIndex: -1
|
||||
|
||||
model: filteredItems
|
||||
@@ -858,19 +812,10 @@ SmartPanel {
|
||||
positionViewAtBeginning();
|
||||
}
|
||||
|
||||
// Capture clicks on empty areas to give focus to GridView
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
z: -1
|
||||
onClicked: {
|
||||
wallpaperGridView.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
property int columns: (screen.width > 1920) ? 5 : 4
|
||||
property int itemSize: cellWidth
|
||||
|
||||
cellWidth: Math.floor((width - leftMargin - rightMargin) / columns)
|
||||
cellWidth: Math.floor((availableWidth - leftMargin - rightMargin) / columns)
|
||||
cellHeight: Math.floor(itemSize * 0.7) + Style.marginXS + Style.fontSizeXS + Style.marginM
|
||||
|
||||
leftMargin: Style.marginS
|
||||
@@ -895,62 +840,14 @@ SmartPanel {
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onPressed: event => {
|
||||
if (event.key === Qt.Key_Return || event.key === Qt.Key_Space) {
|
||||
if (currentIndex >= 0 && currentIndex < filteredItems.length) {
|
||||
selectItem(filteredItems[currentIndex]);
|
||||
}
|
||||
event.accepted = true;
|
||||
onKeyPressed: event => {
|
||||
if (event.key === Qt.Key_Return || event.key === Qt.Key_Space) {
|
||||
if (currentIndex >= 0 && currentIndex < filteredItems.length) {
|
||||
selectItem(filteredItems[currentIndex]);
|
||||
}
|
||||
event.accepted = true;
|
||||
}
|
||||
|
||||
ScrollBar.vertical: ScrollBar {
|
||||
policy: ScrollBar.AsNeeded
|
||||
parent: wallpaperGridView
|
||||
x: wallpaperGridView.mirrored ? 0 : wallpaperGridView.width - width
|
||||
y: 0
|
||||
height: wallpaperGridView.height
|
||||
|
||||
property color handleColor: Qt.alpha(Color.mHover, 0.8)
|
||||
property color handleHoverColor: handleColor
|
||||
property color handlePressedColor: handleColor
|
||||
property real handleWidth: 6
|
||||
property real handleRadius: Style.radiusM
|
||||
|
||||
contentItem: Rectangle {
|
||||
implicitWidth: parent.handleWidth
|
||||
implicitHeight: 100
|
||||
radius: parent.handleRadius
|
||||
color: parent.pressed ? parent.handlePressedColor : parent.hovered ? parent.handleHoverColor : parent.handleColor
|
||||
opacity: parent.policy === ScrollBar.AlwaysOn || parent.active ? 1.0 : 0.0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
implicitWidth: parent.handleWidth
|
||||
implicitHeight: 100
|
||||
color: "transparent"
|
||||
opacity: parent.policy === ScrollBar.AlwaysOn || parent.active ? 0.3 : 0.0
|
||||
radius: parent.handleRadius / 2
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delegate: Item {
|
||||
id: wallpaperItemWrapper
|
||||
@@ -1253,17 +1150,16 @@ SmartPanel {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
GridView {
|
||||
NGridView {
|
||||
id: wallhavenGridView
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
visible: !loading && errorMessage === "" && (wallpapers && wallpapers.length > 0)
|
||||
interactive: true
|
||||
clip: true
|
||||
focus: true
|
||||
keyNavigationEnabled: true
|
||||
keyNavigationWraps: false
|
||||
highlightFollowsCurrentItem: false
|
||||
currentIndex: -1
|
||||
|
||||
model: wallpapers || []
|
||||
@@ -1281,7 +1177,7 @@ SmartPanel {
|
||||
property int columns: (screen.width > 1920) ? 5 : 4
|
||||
property int itemSize: cellWidth
|
||||
|
||||
cellWidth: Math.floor((width - leftMargin - rightMargin) / columns)
|
||||
cellWidth: Math.floor((availableWidth - leftMargin - rightMargin) / columns)
|
||||
cellHeight: Math.floor(itemSize * 0.7) + Style.marginXS + Style.fontSizeXS + Style.marginM
|
||||
|
||||
leftMargin: Style.marginS
|
||||
@@ -1304,63 +1200,15 @@ SmartPanel {
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onPressed: event => {
|
||||
if (event.key === Qt.Key_Return || event.key === Qt.Key_Space) {
|
||||
if (currentIndex >= 0 && currentIndex < wallpapers.length) {
|
||||
let wallpaper = wallpapers[currentIndex];
|
||||
wallhavenDownloadAndApply(wallpaper);
|
||||
}
|
||||
event.accepted = true;
|
||||
onKeyPressed: event => {
|
||||
if (event.key === Qt.Key_Return || event.key === Qt.Key_Space) {
|
||||
if (currentIndex >= 0 && currentIndex < wallpapers.length) {
|
||||
let wallpaper = wallpapers[currentIndex];
|
||||
wallhavenDownloadAndApply(wallpaper);
|
||||
}
|
||||
event.accepted = true;
|
||||
}
|
||||
|
||||
ScrollBar.vertical: ScrollBar {
|
||||
policy: ScrollBar.AsNeeded
|
||||
parent: wallhavenGridView
|
||||
x: wallhavenGridView.mirrored ? 0 : wallhavenGridView.width - width
|
||||
y: 0
|
||||
height: wallhavenGridView.height
|
||||
|
||||
property color handleColor: Qt.alpha(Color.mHover, 0.8)
|
||||
property color handleHoverColor: handleColor
|
||||
property color handlePressedColor: handleColor
|
||||
property real handleWidth: 6
|
||||
property real handleRadius: Style.radiusM
|
||||
|
||||
contentItem: Rectangle {
|
||||
implicitWidth: parent.handleWidth
|
||||
implicitHeight: 100
|
||||
radius: parent.handleRadius
|
||||
color: parent.pressed ? parent.handlePressedColor : parent.hovered ? parent.handleHoverColor : parent.handleColor
|
||||
opacity: parent.policy === ScrollBar.AlwaysOn || parent.active ? 1.0 : 0.0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
implicitWidth: parent.handleWidth
|
||||
implicitHeight: 100
|
||||
color: "transparent"
|
||||
opacity: parent.policy === ScrollBar.AlwaysOn || parent.active ? 0.3 : 0.0
|
||||
radius: parent.handleRadius / 2
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delegate: Item {
|
||||
id: wallhavenItemWrapper
|
||||
|
||||
+6
-49
@@ -402,57 +402,20 @@ Popup {
|
||||
id: filteredModel
|
||||
}
|
||||
|
||||
// Common scroll bar component
|
||||
Component {
|
||||
id: scrollBarComponent
|
||||
ScrollBar {
|
||||
policy: ScrollBar.AsNeeded
|
||||
contentItem: Rectangle {
|
||||
implicitWidth: 6
|
||||
implicitHeight: 100
|
||||
radius: Style.iRadiusM
|
||||
color: Qt.alpha(Color.mHover, 0.8)
|
||||
opacity: parent.policy === ScrollBar.AlwaysOn || parent.active ? 1.0 : 0.0
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
background: Rectangle {
|
||||
implicitWidth: 6
|
||||
implicitHeight: 100
|
||||
color: "transparent"
|
||||
opacity: parent.policy === ScrollBar.AlwaysOn || parent.active ? 0.3 : 0.0
|
||||
radius: (Style.iRadiusM) / 2
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Grid view
|
||||
GridView {
|
||||
NGridView {
|
||||
id: gridView
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginM
|
||||
model: filteredModel
|
||||
visible: filePickerPanel.viewMode
|
||||
clip: true
|
||||
reuseItems: true
|
||||
gradientColor: Color.mSurface
|
||||
|
||||
property int columns: Math.max(1, Math.floor(width / (120)))
|
||||
property int itemSize: Math.floor((width - leftMargin - rightMargin - (columns * Style.marginS)) / columns)
|
||||
property int columns: Math.max(1, Math.floor(availableWidth / 120))
|
||||
property int itemSize: Math.floor((availableWidth - leftMargin - rightMargin - (columns * Style.marginS)) / columns)
|
||||
|
||||
cellWidth: Math.floor((width - leftMargin - rightMargin) / columns)
|
||||
cellWidth: Math.floor((availableWidth - leftMargin - rightMargin) / columns)
|
||||
cellHeight: Math.floor(itemSize * 0.8) + Style.marginXS + Style.fontSizeS + Style.marginM
|
||||
|
||||
leftMargin: Style.marginS
|
||||
@@ -460,13 +423,6 @@ Popup {
|
||||
topMargin: Style.marginS
|
||||
bottomMargin: Style.marginS
|
||||
|
||||
ScrollBar.vertical: scrollBarComponent.createObject(gridView, {
|
||||
"parent": gridView,
|
||||
"x": gridView.mirrored ? 0 : gridView.width - width,
|
||||
"y": 0,
|
||||
"height": gridView.height
|
||||
})
|
||||
|
||||
delegate: Rectangle {
|
||||
id: gridItem
|
||||
width: gridView.itemSize
|
||||
@@ -660,6 +616,7 @@ Popup {
|
||||
anchors.margins: Style.marginS
|
||||
model: filteredModel
|
||||
visible: !filePickerPanel.viewMode
|
||||
gradientColor: Color.mSurface
|
||||
|
||||
delegate: Rectangle {
|
||||
id: listItem
|
||||
|
||||
+100
-24
@@ -6,15 +6,8 @@ import qs.Commons
|
||||
Item {
|
||||
id: root
|
||||
|
||||
// Intercept all key events at the root level to prevent GridView from handling them
|
||||
Keys.onPressed: event => {
|
||||
// Don't let this event reach the GridView
|
||||
event.accepted = false;
|
||||
}
|
||||
|
||||
Keys.onReleased: event => {
|
||||
event.accepted = false;
|
||||
}
|
||||
// Signal for key press events when keyNavigationEnabled is true
|
||||
signal keyPressed(var event)
|
||||
|
||||
property color handleColor: Qt.alpha(Color.mHover, 0.8)
|
||||
property color handleHoverColor: handleColor
|
||||
@@ -30,6 +23,19 @@ Item {
|
||||
return gridView.contentHeight > gridView.height;
|
||||
}
|
||||
|
||||
// Gradient properties
|
||||
property bool showGradientMasks: true
|
||||
property color gradientColor: Color.mSurfaceVariant
|
||||
property int gradientHeight: 16
|
||||
property bool reserveScrollbarSpace: true
|
||||
|
||||
// Available width for content (excludes scrollbar space when reserveScrollbarSpace is true)
|
||||
// Note: Always reserves space when enabled to avoid binding loops with cellWidth calculations
|
||||
readonly property real availableWidth: width - (reserveScrollbarSpace ? handleWidth + Style.marginXS : 0)
|
||||
|
||||
// Expose activeFocus from internal gridView
|
||||
readonly property bool hasActiveFocus: gridView.activeFocus
|
||||
|
||||
// Forward GridView properties
|
||||
property alias model: gridView.model
|
||||
property alias delegate: gridView.delegate
|
||||
@@ -68,6 +74,7 @@ Item {
|
||||
property alias dragging: gridView.dragging
|
||||
property alias horizontalVelocity: gridView.horizontalVelocity
|
||||
property alias verticalVelocity: gridView.verticalVelocity
|
||||
property alias reuseItems: gridView.reuseItems
|
||||
|
||||
// Forward GridView methods
|
||||
function positionViewAtIndex(index, mode) {
|
||||
@@ -86,6 +93,10 @@ Item {
|
||||
gridView.forceLayout();
|
||||
}
|
||||
|
||||
function forceActiveFocus() {
|
||||
gridView.forceActiveFocus();
|
||||
}
|
||||
|
||||
function cancelFlick() {
|
||||
gridView.cancelFlick();
|
||||
}
|
||||
@@ -114,13 +125,83 @@ Item {
|
||||
return gridView.itemAtIndex(index);
|
||||
}
|
||||
|
||||
function moveCurrentIndexUp() {
|
||||
gridView.moveCurrentIndexUp();
|
||||
}
|
||||
|
||||
function moveCurrentIndexDown() {
|
||||
gridView.moveCurrentIndexDown();
|
||||
}
|
||||
|
||||
function moveCurrentIndexLeft() {
|
||||
gridView.moveCurrentIndexLeft();
|
||||
}
|
||||
|
||||
function moveCurrentIndexRight() {
|
||||
gridView.moveCurrentIndexRight();
|
||||
}
|
||||
|
||||
// Set reasonable implicit sizes for Layout usage
|
||||
implicitWidth: 200
|
||||
implicitHeight: 200
|
||||
|
||||
Component.onCompleted: {
|
||||
createGradients();
|
||||
}
|
||||
|
||||
// Dynamically create gradient overlays
|
||||
function createGradients() {
|
||||
if (!showGradientMasks)
|
||||
return;
|
||||
|
||||
Qt.createQmlObject(`
|
||||
import QtQuick
|
||||
import qs.Commons
|
||||
Rectangle {
|
||||
x: 0
|
||||
y: 0
|
||||
width: root.availableWidth
|
||||
height: root.gradientHeight
|
||||
z: 1
|
||||
visible: root.showGradientMasks && root.verticalScrollBarActive
|
||||
opacity: gridView.contentY <= 1 ? 0 : 1
|
||||
Behavior on opacity {
|
||||
NumberAnimation { duration: Style.animationFast; easing.type: Easing.InOutQuad }
|
||||
}
|
||||
gradient: Gradient {
|
||||
GradientStop { position: 0.0; color: root.gradientColor }
|
||||
GradientStop { position: 1.0; color: "transparent" }
|
||||
}
|
||||
}
|
||||
`, root, "topGradient");
|
||||
|
||||
Qt.createQmlObject(`
|
||||
import QtQuick
|
||||
import qs.Commons
|
||||
Rectangle {
|
||||
x: 0
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: -1
|
||||
width: root.availableWidth
|
||||
height: root.gradientHeight + 1
|
||||
z: 1
|
||||
visible: root.showGradientMasks && root.verticalScrollBarActive
|
||||
opacity: (gridView.contentY + gridView.height >= gridView.contentHeight - 1) ? 0 : 1
|
||||
Behavior on opacity {
|
||||
NumberAnimation { duration: Style.animationFast; easing.type: Easing.InOutQuad }
|
||||
}
|
||||
gradient: Gradient {
|
||||
GradientStop { position: 0.0; color: "transparent" }
|
||||
GradientStop { position: 1.0; color: root.gradientColor }
|
||||
}
|
||||
}
|
||||
`, root, "bottomGradient");
|
||||
}
|
||||
|
||||
GridView {
|
||||
id: gridView
|
||||
anchors.fill: parent
|
||||
anchors.rightMargin: root.reserveScrollbarSpace ? root.handleWidth + Style.marginXS : 0
|
||||
|
||||
// Enable clipping to keep content within bounds
|
||||
clip: true
|
||||
@@ -128,27 +209,22 @@ Item {
|
||||
// Enable flickable for smooth scrolling
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
|
||||
// Completely disable focus to prevent any keyboard interaction
|
||||
focus: false
|
||||
activeFocusOnTab: false
|
||||
enabled: true // Still enabled for mouse interaction
|
||||
// Focus handling depends on keyNavigationEnabled
|
||||
focus: keyNavigationEnabled
|
||||
activeFocusOnTab: keyNavigationEnabled
|
||||
|
||||
// Override key navigation - do nothing
|
||||
// Emit keyPressed signal for custom key handling
|
||||
Keys.onPressed: event => {
|
||||
// Consume the event here so GridView doesn't process it
|
||||
// but don't actually do anything
|
||||
event.accepted = true;
|
||||
if (keyNavigationEnabled) {
|
||||
root.keyPressed(event);
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onReleased: event => {
|
||||
event.accepted = true;
|
||||
}
|
||||
|
||||
ScrollBar.vertical: ScrollBar {
|
||||
parent: gridView
|
||||
x: gridView.mirrored ? 0 : gridView.width - width
|
||||
parent: root
|
||||
x: root.mirrored ? 0 : root.width - width
|
||||
y: 0
|
||||
height: gridView.height
|
||||
height: root.height
|
||||
policy: root.verticalPolicy
|
||||
|
||||
contentItem: Rectangle {
|
||||
|
||||
+65
-3
@@ -20,6 +20,14 @@ Item {
|
||||
return listView.contentHeight > listView.height;
|
||||
}
|
||||
|
||||
property bool showGradientMasks: true
|
||||
property color gradientColor: Color.mSurfaceVariant
|
||||
property int gradientHeight: 16
|
||||
property bool reserveScrollbarSpace: true
|
||||
|
||||
// Available width for content (excludes scrollbar space when reserveScrollbarSpace is true)
|
||||
readonly property real availableWidth: width - (reserveScrollbarSpace ? handleWidth + Style.marginXS : 0)
|
||||
|
||||
// Forward ListView properties
|
||||
property alias model: listView.model
|
||||
property alias delegate: listView.delegate
|
||||
@@ -108,18 +116,72 @@ Item {
|
||||
implicitWidth: 200
|
||||
implicitHeight: 200
|
||||
|
||||
Component.onCompleted: {
|
||||
createGradients();
|
||||
}
|
||||
|
||||
// Dynamically create gradient overlays
|
||||
function createGradients() {
|
||||
if (!showGradientMasks)
|
||||
return;
|
||||
|
||||
Qt.createQmlObject(`
|
||||
import QtQuick
|
||||
import qs.Commons
|
||||
Rectangle {
|
||||
x: 0
|
||||
y: 0
|
||||
width: root.availableWidth
|
||||
height: root.gradientHeight
|
||||
z: 1
|
||||
visible: root.showGradientMasks && root.verticalScrollBarActive
|
||||
opacity: listView.contentY <= 1 ? 0 : 1
|
||||
Behavior on opacity {
|
||||
NumberAnimation { duration: Style.animationFast; easing.type: Easing.InOutQuad }
|
||||
}
|
||||
gradient: Gradient {
|
||||
GradientStop { position: 0.0; color: root.gradientColor }
|
||||
GradientStop { position: 1.0; color: "transparent" }
|
||||
}
|
||||
}
|
||||
`, root, "topGradient");
|
||||
|
||||
Qt.createQmlObject(`
|
||||
import QtQuick
|
||||
import qs.Commons
|
||||
Rectangle {
|
||||
x: 0
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: -1
|
||||
width: root.availableWidth
|
||||
height: root.gradientHeight + 1
|
||||
z: 1
|
||||
visible: root.showGradientMasks && root.verticalScrollBarActive
|
||||
opacity: (listView.contentY + listView.height >= listView.contentHeight - 1) ? 0 : 1
|
||||
Behavior on opacity {
|
||||
NumberAnimation { duration: Style.animationFast; easing.type: Easing.InOutQuad }
|
||||
}
|
||||
gradient: Gradient {
|
||||
GradientStop { position: 0.0; color: "transparent" }
|
||||
GradientStop { position: 1.0; color: root.gradientColor }
|
||||
}
|
||||
}
|
||||
`, root, "bottomGradient");
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: listView
|
||||
anchors.fill: parent
|
||||
anchors.rightMargin: root.reserveScrollbarSpace ? root.handleWidth + Style.marginXS : 0
|
||||
|
||||
clip: true
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
|
||||
ScrollBar.vertical: ScrollBar {
|
||||
parent: listView
|
||||
x: listView.mirrored ? 0 : listView.width - width
|
||||
parent: root
|
||||
x: root.mirrored ? 0 : root.width - width
|
||||
y: 0
|
||||
height: listView.height
|
||||
height: root.height
|
||||
policy: root.verticalPolicy
|
||||
|
||||
contentItem: Rectangle {
|
||||
|
||||
@@ -18,6 +18,13 @@ T.ScrollView {
|
||||
property int boundsBehavior: Flickable.StopAtBounds
|
||||
readonly property bool verticalScrollable: contentItem.contentHeight > contentItem.height
|
||||
readonly property bool horizontalScrollable: contentItem.contentWidth > contentItem.width
|
||||
property bool showGradientMasks: true
|
||||
property color gradientColor: Color.mSurfaceVariant
|
||||
property int gradientHeight: 16
|
||||
property bool reserveScrollbarSpace: true
|
||||
property real userRightPadding: 0
|
||||
|
||||
rightPadding: userRightPadding + (reserveScrollbarSpace && verticalScrollable ? handleWidth + Style.marginXS : 0)
|
||||
|
||||
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, contentWidth + leftPadding + rightPadding)
|
||||
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, contentHeight + topPadding + bottomPadding)
|
||||
@@ -25,6 +32,55 @@ T.ScrollView {
|
||||
// Configure the internal flickable when it becomes available
|
||||
Component.onCompleted: {
|
||||
configureFlickable();
|
||||
createGradients();
|
||||
}
|
||||
|
||||
// Dynamically create gradient overlays to avoid interfering with ScrollView content management
|
||||
function createGradients() {
|
||||
if (!showGradientMasks)
|
||||
return;
|
||||
|
||||
Qt.createQmlObject(`
|
||||
import QtQuick
|
||||
import qs.Commons
|
||||
Rectangle {
|
||||
x: root.leftPadding
|
||||
y: root.topPadding
|
||||
width: root.availableWidth
|
||||
height: root.gradientHeight
|
||||
z: 1
|
||||
visible: root.showGradientMasks && root.verticalScrollable
|
||||
opacity: root.contentItem.contentY <= 1 ? 0 : 1
|
||||
Behavior on opacity {
|
||||
NumberAnimation { duration: Style.animationFast; easing.type: Easing.InOutQuad }
|
||||
}
|
||||
gradient: Gradient {
|
||||
GradientStop { position: 0.0; color: root.gradientColor }
|
||||
GradientStop { position: 1.0; color: "transparent" }
|
||||
}
|
||||
}
|
||||
`, root, "topGradient");
|
||||
|
||||
Qt.createQmlObject(`
|
||||
import QtQuick
|
||||
import qs.Commons
|
||||
Rectangle {
|
||||
x: root.leftPadding
|
||||
y: root.height - root.bottomPadding - height + 1
|
||||
width: root.availableWidth
|
||||
height: root.gradientHeight + 1
|
||||
z: 1
|
||||
visible: root.showGradientMasks && root.verticalScrollable
|
||||
opacity: (root.contentItem.contentY + root.contentItem.height >= root.contentItem.contentHeight - 1) ? 0 : 1
|
||||
Behavior on opacity {
|
||||
NumberAnimation { duration: Style.animationFast; easing.type: Easing.InOutQuad }
|
||||
}
|
||||
gradient: Gradient {
|
||||
GradientStop { position: 0.0; color: "transparent" }
|
||||
GradientStop { position: 1.0; color: root.gradientColor }
|
||||
}
|
||||
}
|
||||
`, root, "bottomGradient");
|
||||
}
|
||||
|
||||
// Function to configure the underlying Flickable
|
||||
|
||||
Reference in New Issue
Block a user