From bfbb729a8cd248b2157c3e26f3809b6a20c0c803 Mon Sep 17 00:00:00 2001 From: Arkadiusz Fal Date: Thu, 24 Sep 2026 22:01:01 +0200 Subject: [PATCH] Align side column page content and tabs with the grabber inset The corner layout side column puts a 12pt resize grabber strip before the panel, and every page then added the standard 16pt horizontal padding, so content sat ~28pt from the left edge but 16pt from the right. Add a PanelHorizontalInsets environment value with a panelHorizontalPadding() modifier that the info, comments, related and chapters pages use instead of .padding(.horizontal). The side column trims the leading inset by the grabber width so content is 16pt from the column's outer edge on both sides; other hosts keep the defaults. Queue and tracks are List-backed and get the same leading delta as a scroll content margin. The page bar follows the same insets so the first pill lines up with the content. --- Yattee/Views/Player/CornerPlayerLayout.swift | 13 +++- .../Player/Panels/ChaptersPanelPage.swift | 2 +- .../Player/Panels/CommentsPanelPage.swift | 2 +- .../Views/Player/Panels/InfoPanelPage.swift | 20 +++--- Yattee/Views/Player/Panels/PanelPageBar.swift | 10 ++- .../Player/Panels/PanelPageContext.swift | 61 +++++++++++++++++++ .../Views/Player/Panels/QueuePanelPage.swift | 1 + .../Player/Panels/RelatedPanelPage.swift | 2 +- .../Views/Player/Panels/TracksPanelPage.swift | 1 + 9 files changed, 97 insertions(+), 15 deletions(-) diff --git a/Yattee/Views/Player/CornerPlayerLayout.swift b/Yattee/Views/Player/CornerPlayerLayout.swift index 809395f9..76668fe0 100644 --- a/Yattee/Views/Player/CornerPlayerLayout.swift +++ b/Yattee/Views/Player/CornerPlayerLayout.swift @@ -60,6 +60,10 @@ struct CornerPlayerLayout: View { /// Side column width bounds. static var minColumnWidth: CGFloat { 280 } + + /// Resize grabber strip on the side column's leading edge — narrow, + /// matching the panel drag handle look. + static var grabberWidth: CGFloat { 12 } static var maxColumnWidthCap: CGFloat { 560 } /// Minimum width kept for the video column. static var minVideoColumnWidth: CGFloat { 480 } @@ -167,7 +171,7 @@ struct CornerPlayerLayout: View { let effectiveColumnWidth = clampColumnWidth(columnWidth, availableWidth: width) // Grabber sits on the column's leading edge, inside the column // background — narrow strip matching the panel drag handle look. - let grabberWidth: CGFloat = 12 + let grabberWidth = Self.grabberWidth let totalColumnWidth = effectiveColumnWidth + grabberWidth let videoWidth = showsSideColumn ? max(1, width - totalColumnWidth) : width @@ -540,6 +544,13 @@ struct CornerPlayerLayout: View { pageBarTopPadding: 8, excludedPages: belowVideoPageTypes ) + // The grabber strip already separates the column from the video; + // trim the leading inset so content sits 16pt from the column's + // outer edge on both sides instead of grabber + 16 on the left. + .environment(\.panelHorizontalInsets, PanelHorizontalInsets( + leading: max(0, PanelHorizontalInsets.standard - Self.grabberWidth), + trailing: PanelHorizontalInsets.standard + )) .background(panelBackgroundColor.ignoresSafeArea(edges: .bottom)) } diff --git a/Yattee/Views/Player/Panels/ChaptersPanelPage.swift b/Yattee/Views/Player/Panels/ChaptersPanelPage.swift index ee54910f..138d258b 100644 --- a/Yattee/Views/Player/Panels/ChaptersPanelPage.swift +++ b/Yattee/Views/Player/Panels/ChaptersPanelPage.swift @@ -38,7 +38,7 @@ struct ChaptersPanelPage: View { ) } } - .padding(.horizontal) + .panelHorizontalPadding() .padding(.top, 8) } .task(id: context.video.id) { diff --git a/Yattee/Views/Player/Panels/CommentsPanelPage.swift b/Yattee/Views/Player/Panels/CommentsPanelPage.swift index d5486c6d..217aa220 100644 --- a/Yattee/Views/Player/Panels/CommentsPanelPage.swift +++ b/Yattee/Views/Player/Panels/CommentsPanelPage.swift @@ -23,7 +23,7 @@ struct CommentsPanelPage: View { videoID: context.video.id.videoID, showAvatars: settings.effectiveShowAvatars ) - .padding(.horizontal) + .panelHorizontalPadding() } .task(id: context.video.id) { // Opening the page is an explicit request; load even when the diff --git a/Yattee/Views/Player/Panels/InfoPanelPage.swift b/Yattee/Views/Player/Panels/InfoPanelPage.swift index 9c24c468..08c97a46 100644 --- a/Yattee/Views/Player/Panels/InfoPanelPage.swift +++ b/Yattee/Views/Player/Panels/InfoPanelPage.swift @@ -64,7 +64,7 @@ struct InfoPanelPage: View { if settings.effectiveShowDescription { Divider() - .padding(.horizontal) + .panelHorizontalPadding() descriptionContent .padding(.vertical) @@ -126,7 +126,7 @@ struct InfoPanelPage: View { ) } } - .padding(.horizontal) + .panelHorizontalPadding() .padding(.vertical, 8) } @@ -144,7 +144,7 @@ struct InfoPanelPage: View { .font(.subheadline) .foregroundStyle(.secondary) .tint(context.accentColor) - .padding(.horizontal) + .panelHorizontalPadding() .handleTimestampLinks(using: playerService) } } else if videoDetailsState == .loading { @@ -158,7 +158,7 @@ struct InfoPanelPage: View { Text(String(localized: "player.noDescription")) .font(.subheadline) .foregroundStyle(.secondary) - .padding(.horizontal) + .panelHorizontalPadding() } } } @@ -221,7 +221,7 @@ struct InfoPanelPage: View { .buttonStyle(.plain) } } - .padding(.horizontal) + .panelHorizontalPadding() .contentShape(Rectangle()) .onTapGesture { if !isDescriptionExpanded { @@ -257,7 +257,7 @@ struct InfoPanelPage: View { Spacer(minLength: 0) } - .padding(.horizontal) + .panelHorizontalPadding() .padding(.top, 4) .padding(.bottom, 8) .contentShape(Rectangle()) @@ -269,7 +269,7 @@ struct InfoPanelPage: View { private var chaptersSection: some View { if !chapters.isEmpty { Divider() - .padding(.horizontal) + .panelHorizontalPadding() .padding(.bottom, 8) sectionHeader(String(localized: "player.chapters"), isCollapsed: $isChaptersCollapsed) @@ -290,7 +290,7 @@ struct InfoPanelPage: View { ) } } - .padding(.horizontal) + .panelHorizontalPadding() } } } @@ -299,7 +299,7 @@ struct InfoPanelPage: View { private var relatedSection: some View { if !relatedVideos.isEmpty { Divider() - .padding(.horizontal) + .panelHorizontalPadding() .padding(.vertical, 8) sectionHeader(String(localized: "videoInfo.section.relatedVideos"), isCollapsed: $isRelatedCollapsed) @@ -327,7 +327,7 @@ struct InfoPanelPage: View { } } } - .padding(.horizontal) + .panelHorizontalPadding() } } } diff --git a/Yattee/Views/Player/Panels/PanelPageBar.swift b/Yattee/Views/Player/Panels/PanelPageBar.swift index 055fe49c..b3792772 100644 --- a/Yattee/Views/Player/Panels/PanelPageBar.swift +++ b/Yattee/Views/Player/Panels/PanelPageBar.swift @@ -15,6 +15,13 @@ struct PanelPageBar: View { let accentColor: Color var showLabels: Bool = true + @Environment(\.panelHorizontalInsets) private var insets + + /// Pills sit 4pt closer to the edge than page content (12 vs 16 with the + /// standard inset); a host that trims the content inset (corner side + /// column) trims the bar the same way so the first pill lines up. + private static let pillEdgeOffset: CGFloat = 4 + var body: some View { ScrollViewReader { proxy in ScrollView(.horizontal, showsIndicators: false) { @@ -24,7 +31,8 @@ struct PanelPageBar: View { .id(page.pageType) } } - .padding(.horizontal, 12) + .padding(.leading, max(0, insets.leading - Self.pillEdgeOffset)) + .padding(.trailing, max(0, insets.trailing - Self.pillEdgeOffset)) .padding(.vertical, 6) } .onChange(of: selectedPage) { _, newValue in diff --git a/Yattee/Views/Player/Panels/PanelPageContext.swift b/Yattee/Views/Player/Panels/PanelPageContext.swift index a4ae5dd0..7d883ae1 100644 --- a/Yattee/Views/Player/Panels/PanelPageContext.swift +++ b/Yattee/Views/Player/Panels/PanelPageContext.swift @@ -46,6 +46,67 @@ struct PanelScrollConfig { var onOverscrollDragEnded: ((CGFloat, CGFloat) -> Void)? } +// MARK: - Horizontal Insets + +/// Horizontal content insets a host wants on the panel pages. Hosts whose +/// panel already carries leading chrome (the corner side column's resize +/// grabber strip) narrow the leading inset so content sits the same distance +/// from both edges. +struct PanelHorizontalInsets: Equatable { + static let standard: CGFloat = 16 + + var leading: CGFloat = PanelHorizontalInsets.standard + var trailing: CGFloat = PanelHorizontalInsets.standard + + /// Leading delta relative to the standard inset; List-backed pages + /// (queue, tracks) apply it as a scroll content margin since their + /// section insets can't be restyled per host. + var leadingListAdjustment: CGFloat { leading - Self.standard } +} + +private struct PanelHorizontalInsetsKey: EnvironmentKey { + static let defaultValue = PanelHorizontalInsets() +} + +extension EnvironmentValues { + var panelHorizontalInsets: PanelHorizontalInsets { + get { self[PanelHorizontalInsetsKey.self] } + set { self[PanelHorizontalInsetsKey.self] = newValue } + } +} + +private struct PanelHorizontalPadding: ViewModifier { + @Environment(\.panelHorizontalInsets) private var insets + + func body(content: Content) -> some View { + content + .padding(.leading, insets.leading) + .padding(.trailing, insets.trailing) + } +} + +private struct PanelListLeadingMargin: ViewModifier { + @Environment(\.panelHorizontalInsets) private var insets + + func body(content: Content) -> some View { + content + .contentMargins(.leading, insets.leadingListAdjustment, for: .scrollContent) + } +} + +extension View { + /// Host-configurable replacement for `.padding(.horizontal)` on panel + /// page content. + func panelHorizontalPadding() -> some View { + modifier(PanelHorizontalPadding()) + } + + /// Shifts List-backed page content by the host's leading inset delta. + func panelListLeadingMargin() -> some View { + modifier(PanelListLeadingMargin()) + } +} + // MARK: - Page Scroll View /// Shared scroll container used by the scrollable panel pages. Applies the diff --git a/Yattee/Views/Player/Panels/QueuePanelPage.swift b/Yattee/Views/Player/Panels/QueuePanelPage.swift index 8bbfda00..345654da 100644 --- a/Yattee/Views/Player/Panels/QueuePanelPage.swift +++ b/Yattee/Views/Player/Panels/QueuePanelPage.swift @@ -17,6 +17,7 @@ struct QueuePanelPage: View { var body: some View { QueueListView(showHistory: settings.effectiveShowHistory) .safeAreaPadding(.bottom, bottomInset) + .panelListLeadingMargin() } } diff --git a/Yattee/Views/Player/Panels/RelatedPanelPage.swift b/Yattee/Views/Player/Panels/RelatedPanelPage.swift index ea5dd55e..0f3ce1e9 100644 --- a/Yattee/Views/Player/Panels/RelatedPanelPage.swift +++ b/Yattee/Views/Player/Panels/RelatedPanelPage.swift @@ -54,7 +54,7 @@ struct RelatedPanelPage: View { } } } - .padding(.horizontal) + .panelHorizontalPadding() .padding(.top, 8) } } diff --git a/Yattee/Views/Player/Panels/TracksPanelPage.swift b/Yattee/Views/Player/Panels/TracksPanelPage.swift index b4f26df5..07f68204 100644 --- a/Yattee/Views/Player/Panels/TracksPanelPage.swift +++ b/Yattee/Views/Player/Panels/TracksPanelPage.swift @@ -89,6 +89,7 @@ struct TracksPanelPage: View { visibleSections: settings ) .safeAreaPadding(.bottom, bottomInset) + .panelListLeadingMargin() } } }