diff --git a/resources/images/logo-crt.png b/resources/images/logo-crt.png new file mode 100644 index 0000000..c27006c Binary files /dev/null and b/resources/images/logo-crt.png differ diff --git a/rust/launcher/.qmlls.ini b/rust/launcher/.qmlls.ini new file mode 100644 index 0000000..49a0cf3 --- /dev/null +++ b/rust/launcher/.qmlls.ini @@ -0,0 +1,3 @@ +[General] +buildDir="/tmp/zaparoo-target/cxxqt/qml_modules" +no-cmake-calls=true diff --git a/scripts/run-macos-mister-core.sh b/scripts/run-macos-mister-core.sh index c227472..01d38be 100755 --- a/scripts/run-macos-mister-core.sh +++ b/scripts/run-macos-mister-core.sh @@ -17,5 +17,5 @@ fi export ZAPAROO_CORE_ENDPOINT="ws://192.168.1.176:7497/api/v0.1" export ZAPAROO_CRT_PREVIEW_SCALE=3 -exec "${FRONTEND}" +exec "${FRONTEND}" --crt # exec "${FRONTEND}" --crt diff --git a/src/ui/app/MainLayout.qml b/src/ui/app/MainLayout.qml index 8c10079..0c45d4a 100644 --- a/src/ui/app/MainLayout.qml +++ b/src/ui/app/MainLayout.qml @@ -287,7 +287,7 @@ ApplicationWindow { readonly property string recentsScreenState: Browse.RecentsModel.loading ? "loading" : ((Browse.RecentsModel.error_message ?? "") !== "" ? "error" : (Browse.RecentsModel.count === 0 ? "empty" : "ready")) readonly property bool _crtGridBrowseLayout: root.crtNativePath && Browse.Settings.current_browse_layout !== "list" - readonly property var _browseTileLayout: root._crtGridBrowseLayout ? BrowseLayouts.crtTile : BrowseLayouts.defaultTile + readonly property var _browseTileLayout: root.crtNativePath ? BrowseLayouts.crtTile : BrowseLayouts.defaultTile readonly property var _contextMenuLayout: root.crtNativePath ? BrowseLayouts.crtTile : BrowseLayouts.defaultTile readonly property string _crtGamesHeaderTitle: { const sid = Browse.GamesModel.current_system_id; @@ -297,12 +297,21 @@ ApplicationWindow { return idx >= 0 ? Browse.SystemsModel.system_name_at(idx) : sid; } readonly property string browseHeaderTitle: { - if (!root._crtGridBrowseLayout) + if (!root.crtNativePath) + return ""; + if (Browse.Settings.current_browse_layout === "list") return ""; if (root.activeScreen === root.screenSystems) return Browse.SystemsModel.current_category; if (root.activeScreen === root.screenGames) return root._crtGamesHeaderTitle; + if (root.activeScreen === root.screenFavorites) + return qsTr("Favorites"); + if (root.activeScreen === root.screenRecents) + return qsTr("Recently Played"); + return ""; + } + readonly property string browseHeaderProgressText: { return ""; } @@ -427,6 +436,7 @@ ApplicationWindow { anchors.topMargin: Sizing.headerTopMargin layoutProfile: root._browseTileLayout browseTitle: root.browseHeaderTitle + browseProgressText: root.browseHeaderProgressText z: 200 } diff --git a/src/ui/components/BrowseDetailPane.qml b/src/ui/components/BrowseDetailPane.qml index b71c347..82f4ef9 100644 --- a/src/ui/components/BrowseDetailPane.qml +++ b/src/ui/components/BrowseDetailPane.qml @@ -21,17 +21,20 @@ Item { property bool detailSuppressed: false property bool showChrome: true property string loadingText: qsTr("Loading…") + property var layoutProfile: null - readonly property int _cardPaddingX: Sizing.pctW(2) - readonly property int _cardPaddingY: Sizing.pctH(2) + readonly property int _cardPaddingLeft: root.layoutProfile && root.layoutProfile.detailPanePaddingLeft !== undefined ? root.layoutProfile.detailPanePaddingLeft : Sizing.pctW(2) + readonly property int _cardPaddingRight: root.layoutProfile && root.layoutProfile.detailPanePaddingRight !== undefined ? root.layoutProfile.detailPanePaddingRight : Sizing.pctW(2) + readonly property int _cardPaddingTop: root.layoutProfile && root.layoutProfile.detailPanePaddingTop !== undefined ? root.layoutProfile.detailPanePaddingTop : Sizing.pctH(2) + readonly property int _cardPaddingBottom: root.layoutProfile && root.layoutProfile.detailPanePaddingBottom !== undefined ? root.layoutProfile.detailPanePaddingBottom : Sizing.pctH(2) readonly property int _carouselGutter: (canPreviousImage || canNextImage) ? Sizing.pctW(4) : 0 property int _labelColumnWidth: 0 readonly property int _tagTextSize: Sizing.fontSize(2.2) readonly property int _tagLabelGap: Sizing.pctW(1.4) readonly property var _detailRows: _parseDetailTags(detailTags) readonly property int _tagRowCount: _detailRows.length - readonly property int _tagRowHeight: Sizing.pctH(3) - readonly property int _tagRowSpacing: Sizing.pctH(0.55) + readonly property int _tagRowHeight: root.layoutProfile && root.layoutProfile.detailTagRowHeight !== undefined ? root.layoutProfile.detailTagRowHeight : Sizing.pctH(3) + readonly property int _tagRowSpacing: root.layoutProfile && root.layoutProfile.detailTagRowSpacing !== undefined ? root.layoutProfile.detailTagRowSpacing : Sizing.pctH(0.55) readonly property int _metadataNaturalHeight: _tagRowCount <= 0 ? 0 : (_tagRowCount * _tagRowHeight) + ((_tagRowCount - 1) * _tagRowSpacing) readonly property int _compactDetailHeight: Math.min(Sizing.px(content.height * 0.38), _metadataNaturalHeight) readonly property bool _coverPending: coverKey === "icons/Loading" @@ -39,6 +42,16 @@ Item { readonly property bool _paneLoading: root.loading readonly property bool _detailVisible: !root._paneLoading && !root.detailSuppressed readonly property bool _suppressedPlaceholderCover: root.detailSuppressed && coverKey.startsWith("icons/") && root._coverSource !== "" + readonly property int _metadataYOffset: root.layoutProfile && root.layoutProfile.detailMetadataYOffset !== undefined ? root.layoutProfile.detailMetadataYOffset : 0 + readonly property int _metadataExtraHeight: root.layoutProfile && root.layoutProfile.detailMetadataExtraHeight !== undefined ? root.layoutProfile.detailMetadataExtraHeight : 0 + readonly property int _metadataLeftInset: root.layoutProfile && root.layoutProfile.detailMetadataLeftInset !== undefined ? root.layoutProfile.detailMetadataLeftInset : 0 + readonly property int _metadataRightInset: root.layoutProfile && root.layoutProfile.detailMetadataRightInset !== undefined ? root.layoutProfile.detailMetadataRightInset : 0 + readonly property int _imageXOffset: root.layoutProfile && root.layoutProfile.detailImageXOffset !== undefined ? root.layoutProfile.detailImageXOffset : 0 + readonly property int _imageLeftInset: root.layoutProfile && root.layoutProfile.detailImageLeftInset !== undefined ? root.layoutProfile.detailImageLeftInset : 0 + readonly property int _imageRightInset: root.layoutProfile && root.layoutProfile.detailImageRightInset !== undefined ? root.layoutProfile.detailImageRightInset : 0 + readonly property int _imageExtraWidth: root.layoutProfile && root.layoutProfile.detailImageExtraWidth !== undefined ? root.layoutProfile.detailImageExtraWidth : 0 + readonly property int _imageExtraHeight: root.layoutProfile && root.layoutProfile.detailImageExtraHeight !== undefined ? root.layoutProfile.detailImageExtraHeight : 0 + readonly property int _imageBottomGap: root.layoutProfile && root.layoutProfile.detailImageBottomGap !== undefined ? root.layoutProfile.detailImageBottomGap : 0 onDetailTagsChanged: root._labelColumnWidth = 0 @@ -91,23 +104,24 @@ Item { id: content anchors.fill: parent - anchors.leftMargin: root._cardPaddingX - anchors.rightMargin: root._cardPaddingX - anchors.topMargin: root._cardPaddingY - anchors.bottomMargin: root._cardPaddingY + anchors.leftMargin: root._cardPaddingLeft + anchors.rightMargin: root._cardPaddingRight + anchors.topMargin: root._cardPaddingTop + anchors.bottomMargin: root._cardPaddingBottom clip: true Item { id: imageSlot - readonly property int availableWidth: Math.max(0, parent.width - (2 * root._carouselGutter)) - readonly property int availableHeight: Math.max(0, root.showTitle ? Sizing.px(parent.height * 0.48) : detailBody.y - Sizing.pctH(1)) - readonly property int slotSize: Math.min(availableWidth, availableHeight) + readonly property int availableWidth: Math.max(0, parent.width - root._imageLeftInset - root._imageRightInset - (2 * root._carouselGutter)) + readonly property int availableHeight: Math.max(0, root.showTitle ? Sizing.px(parent.height * 0.48) : detailBody.y - root._imageBottomGap) + readonly property int slotWidth: Math.min(parent.width - root._imageLeftInset - root._imageRightInset, availableWidth + root._imageExtraWidth) + readonly property int slotHeight: Math.min(parent.height, Math.min(availableWidth, availableHeight) + root._imageExtraHeight) - x: root._carouselGutter + Sizing.center(availableWidth, width) + x: root._imageLeftInset + root._carouselGutter + root._imageXOffset anchors.top: parent.top - width: slotSize - height: slotSize + width: Math.max(0, slotWidth) + height: slotHeight Image { id: cover @@ -181,10 +195,10 @@ Item { readonly property int _bodyY: Math.round(root.showTitle ? (titleText.visible ? titleText.y + titleText.height : imageSlot.y + imageSlot.height) + Sizing.pctH(2) : parent.height - height) - x: 0 - y: _bodyY - width: parent.width - height: root.showTitle ? Math.round(Math.max(0, parent.height - _bodyY)) : root._compactDetailHeight + x: root._metadataLeftInset + y: _bodyY + root._metadataYOffset + width: Math.max(0, parent.width - root._metadataLeftInset - root._metadataRightInset) + height: root.showTitle ? Math.round(Math.max(0, parent.height - y + root._metadataExtraHeight)) : root._compactDetailHeight clip: true Column { diff --git a/src/ui/components/BrowseList.qml b/src/ui/components/BrowseList.qml index 430b89a..26f7177 100644 --- a/src/ui/components/BrowseList.qml +++ b/src/ui/components/BrowseList.qml @@ -17,22 +17,34 @@ Item { property int targetVisibleRowCount: 0 property bool showFileStem: false property bool showChrome: true + property var layoutProfile: null readonly property int itemCount: listView.count readonly property int totalItems: totalItemsOverride >= 0 ? totalItemsOverride : itemCount - readonly property int cardPaddingX: Sizing.pctW(2) - readonly property int cardPaddingY: Sizing.pctH(2) - readonly property int rowSpacing: Sizing.pctH(0.7) - readonly property int contentHeight: Math.max(0, height - (2 * cardPaddingY)) - readonly property int rowHeight: targetVisibleRowCount > 0 ? Math.max(Sizing.pctH(3), Math.floor((contentHeight - (rowSpacing * (targetVisibleRowCount - 1))) / targetVisibleRowCount)) : Sizing.pctH(6) + readonly property int _selectionRadius: root.layoutProfile ? root.layoutProfile.tileCornerRadius : Sizing.cornerRadius + readonly property int cardPaddingLeft: root.layoutProfile ? root.layoutProfile.listCardPaddingLeft : Sizing.pctW(2) + readonly property int cardPaddingRight: root.layoutProfile ? root.layoutProfile.listCardPaddingRight : Sizing.pctW(2) + readonly property int cardPaddingTop: root.layoutProfile ? root.layoutProfile.listCardPaddingTop : Sizing.pctH(2) + readonly property int cardPaddingBottom: root.layoutProfile ? root.layoutProfile.listCardPaddingBottom : Sizing.pctH(2) + readonly property int rowSpacing: root.layoutProfile ? root.layoutProfile.listRowSpacing : Sizing.pctH(0.7) + readonly property int contentHeight: Math.max(0, height - cardPaddingTop - cardPaddingBottom) + readonly property int rowHeight: root.layoutProfile && root.layoutProfile.listRowHeight > 0 ? root.layoutProfile.listRowHeight : (targetVisibleRowCount > 0 ? Math.max(Sizing.pctH(3), Math.floor((contentHeight - (rowSpacing * (targetVisibleRowCount - 1))) / targetVisibleRowCount)) : Sizing.pctH(6)) readonly property int rowStride: rowHeight + rowSpacing readonly property int visibleRowCount: targetVisibleRowCount > 0 ? targetVisibleRowCount : Math.max(1, Math.floor((contentHeight + rowSpacing) / rowStride)) - readonly property int _centerSlot: Math.max(0, Math.floor((visibleRowCount - 1) / 2)) + readonly property int _centerSlot: root.layoutProfile && root.layoutProfile.listCenterSlot >= 0 ? Math.max(0, Math.min(visibleRowCount - 1, root.layoutProfile.listCenterSlot)) : Math.max(0, Math.floor((visibleRowCount - 1) / 2)) readonly property int _maxViewTopIndex: Math.max(0, itemCount - visibleRowCount) readonly property int _viewTopIndex: Math.max(0, Math.min(_maxViewTopIndex, currentIndex - _centerSlot)) readonly property int _targetContentY: _viewTopIndex * rowStride readonly property int _maxScrollTopIndex: Math.max(0, totalItems - visibleRowCount) - readonly property int _gutterWidth: Sizing.pctW(3) - readonly property int _gutterGap: Sizing.pctW(1.5) + readonly property int _gutterWidth: root.layoutProfile ? root.layoutProfile.gridGutterWidth : Sizing.pctW(3) + readonly property int _gutterGap: root.layoutProfile && root.layoutProfile.listScrollbarGap !== undefined ? root.layoutProfile.listScrollbarGap : (root.layoutProfile ? root.layoutProfile.gridGutterGap : Sizing.pctW(1.5)) + readonly property int _scrollThumbWidth: root.layoutProfile ? root.layoutProfile.scrollThumbWidth : Sizing.pctW(1.2) + readonly property int _scrollThumbRightInset: root.layoutProfile ? root.layoutProfile.scrollThumbRightInset : 0 + readonly property bool _scrollThumbRightAligned: root.layoutProfile && root.layoutProfile.scrollThumbRightAligned !== undefined ? root.layoutProfile.scrollThumbRightAligned : false + readonly property int _scrollArrowSize: root.layoutProfile ? root.layoutProfile.scrollArrowSize : Math.min(root._gutterWidth, Sizing.pctH(4)) + readonly property int _selectionAccentWidth: root.layoutProfile && root.layoutProfile.listSelectionAccentWidth !== undefined ? root.layoutProfile.listSelectionAccentWidth : Sizing.pctW(0.45) + readonly property int _rowTextLeftPadding: root.layoutProfile ? root.layoutProfile.listRowTextLeftPadding : Sizing.pctW(1.6) + readonly property int _rowTextRightPadding: root.layoutProfile ? root.layoutProfile.listRowTextRightPadding : Sizing.pctW(1.6) + readonly property int _favoriteRightPadding: root.layoutProfile ? root.layoutProfile.listFavoriteRightPadding : Sizing.pctW(1.6) signal itemHovered(int index) signal itemClicked(int index) @@ -87,13 +99,13 @@ Item { id: listView anchors.left: parent.left - anchors.leftMargin: root.cardPaddingX + anchors.leftMargin: root.cardPaddingLeft anchors.top: parent.top - anchors.topMargin: root.cardPaddingY + anchors.topMargin: root.cardPaddingTop anchors.bottom: parent.bottom - anchors.bottomMargin: root.cardPaddingY + anchors.bottomMargin: root.cardPaddingBottom anchors.right: parent.right - anchors.rightMargin: root.totalItems > root.visibleRowCount ? root._gutterWidth + root._gutterGap + root.cardPaddingX : root.cardPaddingX + anchors.rightMargin: root.totalItems > root.visibleRowCount ? root._gutterWidth + root._gutterGap + root.cardPaddingRight : root.cardPaddingRight model: root.model currentIndex: root.currentIndex contentY: Math.min(root._targetContentY, Math.max(0, contentHeight - height)) @@ -139,14 +151,14 @@ Item { Rectangle { anchors.fill: parent color: Theme.selectionSurface - radius: Sizing.cornerRadius + radius: root._selectionRadius } Rectangle { anchors.left: parent.left anchors.top: parent.top anchors.bottom: parent.bottom - width: Sizing.cornerRadius + width: root._selectionRadius color: Theme.selectionSurface } } @@ -155,7 +167,7 @@ Item { anchors.left: parent.left anchors.top: parent.top anchors.bottom: parent.bottom - width: Sizing.pctW(0.45) + width: root._selectionAccentWidth color: Theme.accent visible: row.selected radius: Math.max(0, Sizing.px(width / 3)) @@ -163,9 +175,9 @@ Item { Text { anchors.left: parent.left - anchors.leftMargin: Sizing.pctW(1.6) + anchors.leftMargin: root._rowTextLeftPadding anchors.right: parent.right - anchors.rightMargin: row.favorite !== 0 ? Sizing.pctW(5.2) : Sizing.pctW(1.6) + anchors.rightMargin: row.favorite !== 0 ? root._favoriteRightPadding + Sizing.pctH(3.2) + root._rowTextRightPadding : root._rowTextRightPadding anchors.verticalCenter: parent.verticalCenter text: root.showFileStem && row.fileStem !== "" ? row.fileStem : row.name color: row.selected ? Theme.textPrimary : Theme.textLabel @@ -178,7 +190,7 @@ Item { Image { anchors.right: parent.right - anchors.rightMargin: Sizing.pctW(1.6) + anchors.rightMargin: root._favoriteRightPadding anchors.verticalCenter: parent.verticalCenter width: Sizing.pctH(3.2) height: width @@ -214,21 +226,19 @@ Item { id: scrollGutter anchors.right: parent.right - anchors.rightMargin: root.cardPaddingX + anchors.rightMargin: root.cardPaddingRight anchors.top: parent.top - anchors.topMargin: root.cardPaddingY + anchors.topMargin: root.cardPaddingTop anchors.bottom: parent.bottom - anchors.bottomMargin: root.cardPaddingY + anchors.bottomMargin: root.cardPaddingBottom width: root._gutterWidth visible: root.totalItems > root.visibleRowCount - readonly property int arrowSize: Math.min(width, Sizing.pctH(4)) - Image { id: upArrow source: Resources.iconUrl("ScrollUp") - width: scrollGutter.arrowSize - height: scrollGutter.arrowSize + width: root._scrollArrowSize + height: root._scrollArrowSize anchors.top: parent.top anchors.horizontalCenter: parent.horizontalCenter fillMode: Image.PreserveAspectFit @@ -247,8 +257,8 @@ Item { Image { id: downArrow source: Resources.iconUrl("ScrollDown") - width: scrollGutter.arrowSize - height: scrollGutter.arrowSize + width: root._scrollArrowSize + height: root._scrollArrowSize anchors.bottom: parent.bottom anchors.horizontalCenter: parent.horizontalCenter fillMode: Image.PreserveAspectFit @@ -267,10 +277,13 @@ Item { Item { id: scrollRegion anchors.top: parent.top - anchors.topMargin: scrollGutter.arrowSize + Sizing.pctH(1) + anchors.topMargin: root._scrollArrowSize + Sizing.pctH(1) anchors.bottom: parent.bottom - anchors.bottomMargin: scrollGutter.arrowSize + Sizing.pctH(1) - anchors.horizontalCenter: parent.horizontalCenter + anchors.bottomMargin: root._scrollArrowSize + Sizing.pctH(1) + anchors.right: root._scrollThumbRightAligned ? parent.right : undefined + anchors.rightMargin: root._scrollThumbRightAligned ? root._scrollThumbRightInset : 0 + anchors.horizontalCenter: root._scrollThumbRightAligned ? undefined : parent.horizontalCenter + width: root._scrollThumbWidth readonly property int _minThumbHeight: Sizing.pctH(4) readonly property int _thumbHeight: root.totalItems <= 0 ? 0 : Math.min(scrollRegion.height, Math.max(_minThumbHeight, Math.round(scrollRegion.height * root.visibleRowCount / root.totalItems))) @@ -278,9 +291,10 @@ Item { Rectangle { id: scrollThumb - width: Sizing.pctW(1.2) + width: root._scrollThumbWidth height: scrollRegion._thumbHeight - anchors.horizontalCenter: parent.horizontalCenter + anchors.right: root._scrollThumbRightAligned ? parent.right : undefined + anchors.horizontalCenter: root._scrollThumbRightAligned ? undefined : parent.horizontalCenter y: scrollRegion._thumbY color: Theme.textPrimary radius: Sizing.half(width) diff --git a/src/ui/components/BrowseListDetailView.qml b/src/ui/components/BrowseListDetailView.qml index 1a38c87..0dbe1f6 100644 --- a/src/ui/components/BrowseListDetailView.qml +++ b/src/ui/components/BrowseListDetailView.qml @@ -17,6 +17,7 @@ Item { property alias currentCoverKey: browseList.currentCoverKey property alias itemCount: browseList.itemCount property alias visibleRowCount: browseList.visibleRowCount + property var layoutProfile: null property alias detailTitle: detailPane.title property alias detailCoverKey: detailPane.coverKey @@ -29,6 +30,8 @@ Item { property alias detailSuppressed: detailPane.detailSuppressed property alias detailCanPreviousImage: detailPane.canPreviousImage property alias detailCanNextImage: detailPane.canNextImage + readonly property int _cardRadius: root.layoutProfile ? root.layoutProfile.tileCornerRadius : Sizing.cornerRadius + readonly property int _dividerOffsetX: root.layoutProfile && root.layoutProfile.listDividerOffsetX !== undefined ? root.layoutProfile.listDividerOffsetX : 0 signal itemHovered(int index) signal itemClicked(int index) @@ -45,13 +48,13 @@ Item { color: Theme.surfaceCard border.width: Sizing.stroke(1) border.color: Theme.borderMid - radius: Sizing.cornerRadius + radius: root._cardRadius } CardDivider { id: listDivider - x: Sizing.px(parent.width * 2 / 3) + x: Sizing.px(parent.width * 2 / 3) + root._dividerOffsetX anchors.top: parent.top anchors.bottom: parent.bottom } @@ -63,6 +66,7 @@ Item { anchors.right: listDivider.left anchors.top: parent.top anchors.bottom: parent.bottom + layoutProfile: root.layoutProfile showChrome: false onItemHovered: index => root.itemHovered(index) onItemClicked: index => root.itemClicked(index) @@ -78,6 +82,7 @@ Item { anchors.right: parent.right anchors.top: parent.top anchors.bottom: parent.bottom + layoutProfile: root.layoutProfile showChrome: false } } diff --git a/src/ui/components/HeaderBar.qml b/src/ui/components/HeaderBar.qml index 781fe10..60db43b 100644 --- a/src/ui/components/HeaderBar.qml +++ b/src/ui/components/HeaderBar.qml @@ -26,6 +26,7 @@ Item { property alias logoItem: logo property var layoutProfile: null property string browseTitle: "" + property string browseProgressText: "" height: Sizing.headerHeight @@ -44,6 +45,25 @@ Item { source: "qrc:/qt/qml/Zaparoo/App/resources/images/logo.png" } + Text { + id: browseProgressLabel + + visible: header.browseProgressText !== "" + anchors.left: logo.right + anchors.leftMargin: Sizing.pctW(1) + anchors.verticalCenter: logo.verticalCenter + width: Math.max(0, Math.floor(parent.width / 4)) + height: Sizing.headerRowHeight + elide: Text.ElideRight + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + text: header.browseProgressText + font.family: Theme.fontUi + font.pixelSize: Sizing.fontSize(2.9) + color: Theme.textPrimary + renderType: Text.NativeRendering + } + TextMetrics { id: clockMetrics diff --git a/src/ui/components/PagedGrid.qml b/src/ui/components/PagedGrid.qml index 61eced9..96ec69f 100644 --- a/src/ui/components/PagedGrid.qml +++ b/src/ui/components/PagedGrid.qml @@ -170,6 +170,7 @@ Item { readonly property int gutterGap: root.layoutProfile ? root.layoutProfile.gridGutterGap : Sizing.pctW(1.5) readonly property int scrollThumbWidth: root.layoutProfile ? root.layoutProfile.scrollThumbWidth : Sizing.pctW(1.2) readonly property int scrollThumbRightInset: root.layoutProfile ? root.layoutProfile.scrollThumbRightInset : 0 + readonly property bool scrollThumbRightAligned: root.layoutProfile && root.layoutProfile.scrollThumbRightAligned !== undefined ? root.layoutProfile.scrollThumbRightAligned : false readonly property int scrollArrowSize: root.layoutProfile ? root.layoutProfile.scrollArrowSize : Math.min(gutterWidth, Sizing.pctH(4)) readonly property int topInset: root.layoutProfile ? root.layoutProfile.gridTopInset : Sizing.pctH(2) readonly property int bottomInset: root.layoutProfile ? root.layoutProfile.gridBottomInset : Sizing.pctH(2) @@ -717,8 +718,9 @@ Item { anchors.topMargin: root.scrollArrowSize + Sizing.pctH(1) anchors.bottom: parent.bottom anchors.bottomMargin: root.scrollArrowSize + Sizing.pctH(1) - anchors.right: parent.right - anchors.rightMargin: root.scrollThumbRightInset + anchors.right: root.scrollThumbRightAligned ? parent.right : undefined + anchors.rightMargin: root.scrollThumbRightAligned ? root.scrollThumbRightInset : 0 + anchors.horizontalCenter: root.scrollThumbRightAligned ? undefined : parent.horizontalCenter width: root.scrollThumbWidth // Standard paginated-scrollbar formulas (cf. Qt @@ -735,7 +737,8 @@ Item { id: scrollThumb width: root.scrollThumbWidth height: scrollRegion._thumbHeight - anchors.right: parent.right + anchors.right: root.scrollThumbRightAligned ? parent.right : undefined + anchors.horizontalCenter: root.scrollThumbRightAligned ? undefined : parent.horizontalCenter y: scrollRegion._thumbY color: Theme.textPrimary radius: Sizing.half(width) diff --git a/src/ui/components/TopStatusStrip.qml b/src/ui/components/TopStatusStrip.qml index 652a183..ec19150 100644 --- a/src/ui/components/TopStatusStrip.qml +++ b/src/ui/components/TopStatusStrip.qml @@ -24,8 +24,8 @@ Item { property int totalPages: 1 property string totalText: "" // formatted; empty hides the slot property string rightTextOverride: "" // formatted; non-empty replaces Page N / M + property int slotMargin: Sizing.pctW(5) readonly property int _slotWidth: Sizing.px(status.width / 3) - readonly property int _slotMargin: Sizing.pctW(5) readonly property int _textMeasureSlack: Theme.crtNativePath ? 0 : 2 readonly property int _titleMeasuredWidth: Math.ceil(Math.max(titleMetrics.advanceWidth, titleMetrics.boundingRect.width) + status._textMeasureSlack) readonly property int _titleTextWidth: Math.min(status._slotWidth, status._titleMeasuredWidth) @@ -40,9 +40,9 @@ Item { visible: status.totalText !== "" anchors.left: parent.left - anchors.leftMargin: status._slotMargin + anchors.leftMargin: status.slotMargin anchors.bottom: titleText.bottom - width: status._slotWidth - status._slotMargin + width: status._slotWidth - status.slotMargin elide: Text.ElideRight horizontalAlignment: Text.AlignLeft text: status.totalText @@ -83,9 +83,9 @@ Item { visible: status.rightTextOverride !== "" || status.totalPages > 1 anchors.right: parent.right - anchors.rightMargin: status._slotMargin + anchors.rightMargin: status.slotMargin anchors.bottom: titleText.bottom - width: status._slotWidth - status._slotMargin + width: status._slotWidth - status.slotMargin elide: Text.ElideRight horizontalAlignment: Text.AlignRight text: status.rightTextOverride !== "" ? status.rightTextOverride : qsTr("Page %1 / %2").arg(status.currentPage + 1).arg(status.totalPages) diff --git a/src/ui/screens/FavoritesScreen.qml b/src/ui/screens/FavoritesScreen.qml index d2e2316..9d5033c 100644 --- a/src/ui/screens/FavoritesScreen.qml +++ b/src/ui/screens/FavoritesScreen.qml @@ -24,4 +24,5 @@ MediaListScreen { screenTitle: qsTr("Favorites") emptyText: qsTr("No favorites yet") loadingText: qsTr("Loading favorites…") + detailShowTitle: false } diff --git a/src/ui/screens/GamesScreen.qml b/src/ui/screens/GamesScreen.qml index d0d6016..88b6274 100644 --- a/src/ui/screens/GamesScreen.qml +++ b/src/ui/screens/GamesScreen.qml @@ -118,7 +118,7 @@ MediaListScreen { return qsTr("%1 / %2").arg(games.gamesGrid.currentIndex + 1).arg(total); } gridLayoutProfile: games._tileLayout - gridBottomMargin: games._tileLayout.activeLabelBottomMargin + games._tileLayout.activeLabelHeight + gridBottomMargin: games._tileLayout.showBottomStatusRow ? games._tileLayout.activeLabelBottomMargin + games._tileLayout.activeLabelHeight : Sizing.pctH(6) + games._tileLayout.activeLabelBottomMargin + games._tileLayout.activeLabelHeight gridTotalItemsOverride: Browse.GamesModel.dir_count + Browse.GamesModel.total_files gridHasMorePages: Browse.GamesModel.has_next_page gridLoadMoreAction: () => Browse.GamesModel.fetch_more() diff --git a/src/ui/screens/MediaListScreen.qml b/src/ui/screens/MediaListScreen.qml index 93625c4..4144855 100644 --- a/src/ui/screens/MediaListScreen.qml +++ b/src/ui/screens/MediaListScreen.qml @@ -84,6 +84,8 @@ Item { property int gridTotalItemsOverride: -1 property bool gridHasMorePages: false readonly property bool _listLayout: root.forceListLayout || Browse.Settings.current_browse_layout === "list" + readonly property bool _crtListStrip: Theme.crtNativePath && root._listLayout + readonly property var _listLayoutProfile: Theme.crtNativePath ? BrowseLayouts.crtTile : BrowseLayouts.defaultTile readonly property int _listOverlayBottomMargin: Sizing.pctH(15) readonly property bool _gateHide: root.transitioning || root._loading() @@ -283,12 +285,13 @@ Item { TopStatusStrip { id: topStrip - visible: !root._gateHide && root.showTopStrip + visible: !root._gateHide && (root.showTopStrip || root._crtListStrip) anchors.left: parent.left anchors.right: parent.right anchors.top: parent.top anchors.topMargin: Sizing.headerBottom + Sizing.pctH(1) - height: root.showTopStrip ? Sizing.pctH(7) : 0 + height: root._crtListStrip ? root._listLayoutProfile.listStripHeight : (root.showTopStrip ? Sizing.pctH(7) : 0) + slotMargin: root._crtListStrip ? root._listLayoutProfile.listStripSlotMargin : Sizing.pctW(5) title: typeof root.topStripTitleProvider === "function" ? root.topStripTitleProvider() : root.screenTitle currentPage: typeof root.topStripCurrentPageProvider === "function" ? root.topStripCurrentPageProvider() : mediaGrid.currentPage totalPages: typeof root.topStripTotalPagesProvider === "function" ? root.topStripTotalPagesProvider() : Math.max(1, Math.ceil(root._count() / mediaGrid.pageSize)) @@ -301,13 +304,14 @@ Item { visible: !root._gateHide && root._listLayout anchors.left: parent.left - anchors.leftMargin: Sizing.pctW(5) + anchors.leftMargin: root._listLayoutProfile.listCardSideMargin anchors.right: parent.right - anchors.rightMargin: Sizing.pctW(5) + anchors.rightMargin: root._listLayoutProfile.listCardSideMargin anchors.top: topStrip.bottom anchors.topMargin: Sizing.pctH(2) anchors.bottom: parent.bottom anchors.bottomMargin: Sizing.pctH(8) + layoutProfile: root._listLayoutProfile model: root.mediaModel totalItemsOverride: root.totalItemsOverride targetVisibleRowCount: root.targetVisibleRowCount diff --git a/src/ui/screens/RecentsScreen.qml b/src/ui/screens/RecentsScreen.qml index 8301ac3..db1fe13 100644 --- a/src/ui/screens/RecentsScreen.qml +++ b/src/ui/screens/RecentsScreen.qml @@ -24,4 +24,5 @@ MediaListScreen { screenTitle: qsTr("Recently Played") emptyText: qsTr("Nothing played yet") loadingText: qsTr("Loading recently played…") + detailShowTitle: false } diff --git a/src/ui/screens/SystemsScreen.qml b/src/ui/screens/SystemsScreen.qml index e95ff3e..97c45bf 100644 --- a/src/ui/screens/SystemsScreen.qml +++ b/src/ui/screens/SystemsScreen.qml @@ -38,7 +38,8 @@ Item { property bool gridFocused: true readonly property bool _listLayout: Browse.Settings.current_browse_layout === "list" readonly property bool _crtGridLayout: Theme.crtNativePath && !systems._listLayout - readonly property var _tileLayout: systems._crtGridLayout ? BrowseLayouts.crtTile : BrowseLayouts.defaultTile + readonly property bool _crtListStrip: Theme.crtNativePath && systems._listLayout + readonly property var _tileLayout: Theme.crtNativePath ? BrowseLayouts.crtTile : BrowseLayouts.defaultTile readonly property int _listOverlayBottomMargin: Sizing.pctH(6) + systems._tileLayout.activeLabelBottomMargin + systems._tileLayout.activeLabelHeight signal requestAccept(systemId: string) @@ -178,17 +179,14 @@ Item { anchors.right: parent.right anchors.top: parent.top anchors.topMargin: Sizing.headerBottom + Sizing.pctH(1) - height: systems._tileLayout.showTopStrip ? Sizing.pctH(7) : 0 + height: systems._crtListStrip ? systems._tileLayout.listStripHeight : (systems._tileLayout.showTopStrip ? Sizing.pctH(7) : 0) + slotMargin: systems._crtListStrip ? systems._tileLayout.listStripSlotMargin : Sizing.pctW(5) title: systems._tileLayout.showHeaderTitleInHeader ? "" : Browse.SystemsModel.current_category currentPage: systemsGrid.currentPage totalPages: systems._tileLayout.showBottomStatusRow ? 1 : Math.max(1, Math.ceil(Browse.SystemsModel.count / systemsGrid.pageSize)) - totalText: systems._listLayout || systems._tileLayout.showBottomStatusRow ? "" : (Browse.SystemsModel.count > 0 ? qsTr("%1 systems").arg(Browse.SystemsModel.count) : "") - rightTextOverride: { - if (!systems._listLayout || systemsGrid.itemCount <= 0) - return ""; - return qsTr("%1 / %2").arg(systemsGrid.currentIndex + 1).arg(Math.max(1, Browse.SystemsModel.count)); - } - visible: !systems.transitioning && systems._tileLayout.showTopStrip + totalText: Theme.crtNativePath ? "" : (Browse.SystemsModel.count > 0 ? qsTr("%1 systems").arg(Browse.SystemsModel.count) : "") + rightTextOverride: !systems._listLayout || systemsGrid.itemCount <= 0 ? "" : qsTr("%1 / %2").arg(systemsGrid.currentIndex + 1).arg(Math.max(1, Browse.SystemsModel.count)) + visible: !systems.transitioning && (systems._tileLayout.showTopStrip || systems._crtListStrip) } BrowseListDetailView { @@ -196,9 +194,9 @@ Item { visible: !systems.transitioning && systems._listLayout anchors.left: parent.left - anchors.leftMargin: Sizing.pctW(5) + anchors.leftMargin: systems._tileLayout.listCardSideMargin anchors.right: parent.right - anchors.rightMargin: Sizing.pctW(5) + anchors.rightMargin: systems._tileLayout.listCardSideMargin anchors.top: topStrip.bottom anchors.topMargin: Sizing.pctH(2) anchors.bottom: parent.bottom @@ -232,7 +230,7 @@ Item { anchors.right: parent.right anchors.top: topStrip.bottom anchors.bottom: parent.bottom - anchors.bottomMargin: Sizing.pctH(6) + systems._tileLayout.activeLabelBottomMargin + systems._tileLayout.activeLabelHeight + anchors.bottomMargin: systems._tileLayout.showBottomStatusRow ? systems._tileLayout.activeLabelBottomMargin + systems._tileLayout.activeLabelHeight : Sizing.pctH(6) + systems._tileLayout.activeLabelBottomMargin + systems._tileLayout.activeLabelHeight focused: systems.gridFocused model: Browse.SystemsModel layoutProfile: systems._tileLayout @@ -273,7 +271,7 @@ Item { } Text { - visible: systems._tileLayout.showBottomStatusRow && !systems.transitioning && Browse.SystemsModel.count > 0 + visible: systems._tileLayout.showBottomStatusRow && !systems.transitioning && !systems._listLayout && Browse.SystemsModel.count > 0 anchors.left: parent.left anchors.leftMargin: systems._tileLayout.bottomStatusLeftMargin anchors.verticalCenter: activeLabel.verticalCenter @@ -290,7 +288,7 @@ Item { } Text { - visible: systems._tileLayout.showBottomStatusRow && !systems.transitioning && Math.ceil(Browse.SystemsModel.count / systemsGrid.pageSize) > 1 + visible: systems._tileLayout.showBottomStatusRow && !systems.transitioning && !systems._listLayout && Math.ceil(Browse.SystemsModel.count / systemsGrid.pageSize) > 1 anchors.right: parent.right anchors.rightMargin: systems._tileLayout.bottomStatusRightMargin anchors.verticalCenter: activeLabel.verticalCenter diff --git a/src/ui/theme/BrowseLayouts.qml b/src/ui/theme/BrowseLayouts.qml index 3b13943..4b96161 100644 --- a/src/ui/theme/BrowseLayouts.qml +++ b/src/ui/theme/BrowseLayouts.qml @@ -24,6 +24,7 @@ QtObject { readonly property int gridRowGap: Sizing.pctH(4) readonly property int scrollThumbWidth: Sizing.pctW(1.2) readonly property int scrollThumbRightInset: 0 + readonly property bool scrollThumbRightAligned: false readonly property int scrollArrowSize: Math.min(gridGutterWidth, Sizing.pctH(4)) readonly property bool packHorizontalRemainderAfterGutter: false readonly property int activeLabelHeight: Sizing.pctH(7) @@ -32,6 +33,38 @@ QtObject { readonly property int bottomStatusRightMargin: Sizing.pctW(5) readonly property int bottomUnsafeHeight: Sizing.pctH(6) + Sizing.pctH(2) readonly property int tileCornerRadius: Sizing.cornerRadius + readonly property int listCardSideMargin: Sizing.pctW(5) + readonly property int listDividerOffsetX: 0 + readonly property int listStripHeight: Sizing.pctH(7) + readonly property int listStripSlotMargin: Sizing.pctW(5) + readonly property int listCardPaddingLeft: Sizing.pctW(2) + readonly property int listCardPaddingRight: Sizing.pctW(2) + readonly property int listCardPaddingTop: Sizing.pctH(2) + readonly property int listCardPaddingBottom: Sizing.pctH(2) + readonly property int listRowHeight: 0 + readonly property int listRowSpacing: Sizing.pctH(0.7) + readonly property int listCenterSlot: -1 + readonly property int listScrollbarGap: Sizing.pctW(1.5) + readonly property int listSelectionAccentWidth: Sizing.pctW(0.45) + readonly property int detailMetadataYOffset: 0 + readonly property int detailMetadataExtraHeight: 0 + readonly property int detailMetadataLeftInset: 0 + readonly property int detailMetadataRightInset: 0 + readonly property int detailPanePaddingLeft: Sizing.pctW(2) + readonly property int detailPanePaddingRight: Sizing.pctW(2) + readonly property int detailPanePaddingTop: Sizing.pctH(2) + readonly property int detailPanePaddingBottom: Sizing.pctH(2) + readonly property int detailImageXOffset: 0 + readonly property int detailImageLeftInset: 0 + readonly property int detailImageRightInset: 0 + readonly property int detailImageExtraWidth: 0 + readonly property int detailImageExtraHeight: 0 + readonly property int detailImageBottomGap: 0 + readonly property int detailTagRowHeight: Sizing.pctH(3) + readonly property int detailTagRowSpacing: Sizing.pctH(0.55) + readonly property int listRowTextLeftPadding: Sizing.pctW(1.6) + readonly property int listRowTextRightPadding: Sizing.pctW(1.6) + readonly property int listFavoriteRightPadding: Sizing.pctW(1.6) } readonly property QtObject crtTile: QtObject { @@ -50,13 +83,46 @@ QtObject { readonly property int gridRowGap: 4 readonly property int scrollThumbWidth: 4 readonly property int scrollThumbRightInset: 2 + readonly property bool scrollThumbRightAligned: false readonly property int scrollArrowSize: 8 readonly property bool packHorizontalRemainderAfterGutter: true readonly property int activeLabelHeight: 8 - readonly property int activeLabelBottomMargin: Sizing.pctH(6) + 4 + readonly property int activeLabelBottomMargin: Sizing.pctH(6) readonly property int bottomStatusLeftMargin: 4 readonly property int bottomStatusRightMargin: Sizing.pctW(5) readonly property int bottomUnsafeHeight: 16 readonly property int tileCornerRadius: 4 + readonly property int listCardSideMargin: 4 + readonly property int listDividerOffsetX: -16 + readonly property int listStripHeight: 8 + readonly property int listStripSlotMargin: Sizing.headerSideMargin + readonly property int listCardPaddingLeft: 3 + readonly property int listCardPaddingRight: 2 + readonly property int listCardPaddingTop: 3 + readonly property int listCardPaddingBottom: 2 + readonly property int listRowHeight: 12 + readonly property int listRowSpacing: 0 + readonly property int listCenterSlot: 7 + readonly property int listScrollbarGap: 2 + readonly property int listSelectionAccentWidth: 2 + readonly property int detailMetadataYOffset: -14 + readonly property int detailMetadataExtraHeight: 2 + readonly property int detailMetadataLeftInset: 2 + readonly property int detailMetadataRightInset: 1 + readonly property int detailPanePaddingLeft: 1 + readonly property int detailPanePaddingRight: 1 + readonly property int detailPanePaddingTop: 3 + readonly property int detailPanePaddingBottom: 2 + readonly property int detailImageXOffset: 0 + readonly property int detailImageLeftInset: 2 + readonly property int detailImageRightInset: 2 + readonly property int detailImageExtraWidth: 16 + readonly property int detailImageExtraHeight: 0 + readonly property int detailImageBottomGap: 2 + readonly property int detailTagRowHeight: 9 + readonly property int detailTagRowSpacing: 0 + readonly property int listRowTextLeftPadding: 4 + readonly property int listRowTextRightPadding: 2 + readonly property int listFavoriteRightPadding: 2 } } diff --git a/tests/ui/tst_browse_layout_profiles.qml b/tests/ui/tst_browse_layout_profiles.qml index d010460..96be6e5 100644 --- a/tests/ui/tst_browse_layout_profiles.qml +++ b/tests/ui/tst_browse_layout_profiles.qml @@ -52,13 +52,13 @@ TestCase { compare(main.systemsScreen.systemsGrid.scrollArrowSize, 8); } - function test_list_mode_uses_default_tile_profile(): void { + function test_crt_list_uses_crt_header_and_profile(): void { main.crtNativePath = true; Browse.Settings.current_browse_layout = "list"; - compare(main.headerBar.layoutProfile.showHeaderTitleInHeader, false); - compare(main.systemsScreen.systemsGrid.layoutProfile.tileCornerRadius, Sizing.cornerRadius); - compare(main.systemsScreen.systemsGrid.leftInset, Sizing.pctW(5)); - compare(main.systemsScreen.systemsGrid.gutterWidth, Sizing.pctW(3)); + compare(main.headerBar.layoutProfile.showHeaderTitleInHeader, true); + compare(main.systemsScreen.systemsGrid.layoutProfile.tileCornerRadius, 4); + compare(main.systemsScreen.systemsGrid.leftInset, 4); + compare(main.systemsScreen.systemsGrid.gutterWidth, 8); } }