Add keyboard shortcuts to toggle corner layout panels

Playback menu items toggle the info panel below the video (⌥⌘I), the
side column (⌥⌘S) and both at once (⌥⌘P) while the corner layout is
active. Showing one region from the hidden state brings back only that
region; hiding the last visible one hides the whole panel.
This commit is contained in:
Arkadiusz Fal
2026-10-21 22:06:27 +02:00
parent 1fe7caa6bc
commit 582aa56644
4 changed files with 141 additions and 0 deletions

View File

@@ -79,6 +79,13 @@ struct PlaybackCommands: Commands {
// Display modes
pipButton
Divider()
// Corner layout panels
toggleInfoPanelButton
toggleSidebarButton
toggleAllPanelsButton
Divider()
closeVideoButton
@@ -309,6 +316,42 @@ struct PlaybackCommands: Commands {
.disabled(!hasActiveVideo || !state.isPiPPossible || state.currentStream?.isAudioOnly == true)
}
// MARK: - Corner Layout Panels
private var isCornerLayoutActive: Bool {
isPlayerExpanded && navigationCoordinator.isCornerLayoutActive
}
private var toggleInfoPanelButton: some View {
Button {
navigationCoordinator.cornerPanelToggleRequest = CornerPanelToggleRequest(target: .belowVideo)
} label: {
Text(String(localized: "menu.playback.toggleInfoPanel"))
}
.keyboardShortcut("i", modifiers: [.command, .option])
.disabled(!isCornerLayoutActive)
}
private var toggleSidebarButton: some View {
Button {
navigationCoordinator.cornerPanelToggleRequest = CornerPanelToggleRequest(target: .sideColumn)
} label: {
Text(String(localized: "menu.playback.toggleSidebar"))
}
.keyboardShortcut("s", modifiers: [.command, .option])
.disabled(!isCornerLayoutActive)
}
private var toggleAllPanelsButton: some View {
Button {
navigationCoordinator.cornerPanelToggleRequest = CornerPanelToggleRequest(target: .all)
} label: {
Text(String(localized: "menu.playback.toggleAllPanels"))
}
.keyboardShortcut("p", modifiers: [.command, .option])
.disabled(!isCornerLayoutActive)
}
// MARK: - Close video button
private var closeVideoButton: some View {

View File

@@ -6907,6 +6907,36 @@
}
}
},
"menu.playback.toggleAllPanels" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Toggle All Panels"
}
}
}
},
"menu.playback.toggleInfoPanel" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Toggle Info Panel"
}
}
}
},
"menu.playback.toggleSidebar" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Toggle Sidebar"
}
}
}
},
"menu.playback.volumeDown" : {
"localizations" : {
"en" : {

View File

@@ -165,6 +165,11 @@ final class NavigationCoordinator {
/// not collapsed) — the seek pan must not begin from inside it.
var cornerSideColumnEdgeRevealInset: CGFloat = 0
/// Corner layout panel toggle requested from outside the layout (menu
/// bar keyboard shortcuts). Each request carries its own id so repeating
/// the same shortcut fires again.
var cornerPanelToggleRequest: CornerPanelToggleRequest?
/// Progress bar frame in screen coordinates (for gesture conflict resolution).
var progressBarFrame: CGRect = .zero
@@ -389,3 +394,18 @@ final class NavigationCoordinator {
}
}
}
/// Corner layout regions a panel toggle request targets.
enum CornerPanelToggle: Equatable {
/// The info panel below the video (with the comments pill on iOS).
case belowVideo
/// The tabbed side column on the trailing edge.
case sideColumn
/// Every region at once — same as the in-player panel toggle button.
case all
}
struct CornerPanelToggleRequest: Equatable {
let target: CornerPanelToggle
let id = UUID()
}

View File

@@ -652,6 +652,10 @@ struct CornerPlayerLayout<PlayerContent: View>: View {
panelToggleGeneration += 1
}
}
.onChange(of: appEnvironment?.navigationCoordinator.cornerPanelToggleRequest) { _, request in
guard let request else { return }
handlePanelToggleRequest(request.target)
}
}
// MARK: - Panels
@@ -851,6 +855,50 @@ struct CornerPlayerLayout<PlayerContent: View>: View {
}
}
/// Keyboard shortcut toggles from the menu bar. `.all` is the toggle
/// button; the single-region toggles flip one region and keep the other
/// as it is on screen.
private func handlePanelToggleRequest(_ target: CornerPanelToggle) {
let showsBelow = isPanelVisible && isBelowPanelVisible && hasBelowVideoInfo
let showsSide = isPanelVisible && isSideColumnVisible && hasSideColumn
switch target {
case .all:
togglePanel()
case .belowVideo:
guard hasBelowVideoInfo else { return }
setRegionsVisible(below: !showsBelow, side: showsSide)
case .sideColumn:
guard hasSideColumn else { return }
setRegionsVisible(below: showsBelow, side: !showsSide)
}
}
/// Shows exactly the given regions, animated like the toggle button.
/// Hiding the last visible region hides the whole panel instead (video
/// fullscreen state), leaving both regions armed for the next show.
private func setRegionsVisible(below: Bool, side: Bool) {
let showsAny = (below && hasBelowVideoInfo) || (side && hasSideColumn)
withAnimation(.easeInOut(duration: 0.25)) {
// Drop any in-flight drag geometry — the flags alone define
// the layout from here
isRevealDragActive = false
isSideColumnRevealDragActive = false
sideColumnDragOffset = 0
belowPanelDragOffset = 0
if !below || !showsAny { collapseBelowPanelUnderVideo() }
isBelowPanelVisible = showsAny ? below : true
isSideColumnVisible = showsAny ? side : true
isPanelVisible = showsAny
panelToggleGeneration += 1
// Also gates the macOS window aspect lock
// (`isCornerPanelLayoutActive`)
if settingsManager?.landscapeDetailsPanelVisible != showsAny {
settingsManager?.landscapeDetailsPanelVisible = showsAny
settingsManager?.landscapeDetailsPanelLastVisible = showsAny
}
}
}
/// Hiding everything grows the video over the below panel, so an
/// expanded panel (raised above the video) must drop back to its home
/// geometry under the video first.