Add sidebar toggle and fullscreen webview support

Implements a sidebar hidden state and toggle functionality for fullscreen webview mode, including new CSS for sidebar transitions and a menu hint. Updates gamepad input handling to allow toggling the sidebar and scrolling in webview mode, and restricts focusable elements to the sidebar and header when browsing. Improves user experience when navigating and interacting with web content in fullscreen.
This commit is contained in:
2025-12-27 23:27:03 +13:00
parent 5c837aecd8
commit 3d538a09f9
2 changed files with 142 additions and 21 deletions
+51
View File
@@ -1426,3 +1426,54 @@ body.fullscreen .bp-main {
font-size: 0.7rem; font-size: 0.7rem;
color: var(--bp-text); color: var(--bp-text);
} }
/* =============================================================================
SIDEBAR HIDDEN STATE (Fullscreen webview mode)
============================================================================= */
.bp-sidebar.sidebar-hidden {
transform: translateX(-100%);
opacity: 0;
pointer-events: none;
width: 0;
min-width: 0;
padding: 0;
border: none;
transition: all 0.3s ease-out;
}
.bp-sidebar {
transition: all 0.3s ease-out;
}
.bp-content.fullscreen {
margin-left: 0;
width: 100%;
}
.bp-header.sidebar-hidden .header-left {
opacity: 0.5;
transform: scale(0.9);
transition: all 0.3s ease;
}
/* Show sidebar toggle hint when in fullscreen */
.bp-content.fullscreen::before {
content: '☰ Menu';
position: fixed;
bottom: 20px;
left: 20px;
background: rgba(20, 20, 31, 0.8);
border: 1px solid var(--bp-border);
border-radius: var(--bp-radius-sm);
padding: 8px 12px;
font-size: 0.75rem;
color: var(--bp-text-muted);
z-index: 100;
opacity: 0.6;
transition: opacity 0.2s ease;
}
.bp-content.fullscreen:hover::before {
opacity: 1;
}
+91 -21
View File
@@ -56,6 +56,9 @@ const state = {
cursorSpeed: 15, cursorSpeed: 15,
cursorElement: null, cursorElement: null,
// Sidebar visibility (for fullscreen webview)
sidebarHidden: false,
// OSK (On-Screen Keyboard) // OSK (On-Screen Keyboard)
oskVisible: false, oskVisible: false,
oskCallback: null, oskCallback: null,
@@ -190,9 +193,44 @@ function initNavigation() {
}); });
} }
// =============================================================================
// SIDEBAR TOGGLE (for fullscreen webview)
// =============================================================================
function toggleSidebar() {
state.sidebarHidden = !state.sidebarHidden;
const sidebar = document.querySelector('.bp-sidebar');
const content = document.querySelector('.bp-content');
const header = document.querySelector('.bp-header');
if (state.sidebarHidden) {
sidebar?.classList.add('sidebar-hidden');
content?.classList.add('fullscreen');
header?.classList.add('sidebar-hidden');
showToast('📺 Fullscreen mode | Press ☰ to show sidebar');
} else {
sidebar?.classList.remove('sidebar-hidden');
content?.classList.remove('fullscreen');
header?.classList.remove('sidebar-hidden');
showToast('Sidebar restored');
}
}
function showSidebar() {
if (state.sidebarHidden) {
toggleSidebar();
}
}
function switchSection(sectionId) { function switchSection(sectionId) {
console.log('[BigPicture] Switching to section:', sectionId); console.log('[BigPicture] Switching to section:', sectionId);
// Restore sidebar when leaving browse section
if (sectionId !== 'browse' && state.sidebarHidden) {
showSidebar();
}
// Handle webview container visibility (preserve state instead of destroying) // Handle webview container visibility (preserve state instead of destroying)
const webviewContainer = document.getElementById('webview-container'); const webviewContainer = document.getElementById('webview-container');
if (webviewContainer) { if (webviewContainer) {
@@ -241,6 +279,16 @@ function updateFocusableElements() {
} }
} }
// When in webview mode, only sidebar navigation is available
if (state.cursorEnabled && state.currentWebview) {
state.focusableElements = [
...document.querySelectorAll('.bp-sidebar [data-focusable]'),
...document.querySelectorAll('.bp-header [data-focusable]')
];
console.log('[BigPicture] Webview mode - sidebar focusable elements:', state.focusableElements.length);
return;
}
const activeSection = document.querySelector('.bp-section.active'); const activeSection = document.querySelector('.bp-section.active');
if (!activeSection) return; if (!activeSection) return;
@@ -472,11 +520,15 @@ function handleGamepadInput(gamepad) {
const stickLeft = leftX < -CONFIG.STICK_DEADZONE; const stickLeft = leftX < -CONFIG.STICK_DEADZONE;
const stickRight = leftX > CONFIG.STICK_DEADZONE; const stickRight = leftX > CONFIG.STICK_DEADZONE;
// Combine inputs // When cursor is enabled (viewing a webpage), only D-Pad navigates sidebar
const up = dpadUp || stickUp; // Left stick is ignored for UI navigation in webview mode
const down = dpadDown || stickDown; const inWebviewMode = state.cursorEnabled && state.currentWebview;
const left = dpadLeft || stickLeft;
const right = dpadRight || stickRight; // Combine inputs - but only use D-Pad when in webview mode
const up = inWebviewMode ? dpadUp : (dpadUp || stickUp);
const down = inWebviewMode ? dpadDown : (dpadDown || stickDown);
const left = inWebviewMode ? dpadLeft : (dpadLeft || stickLeft);
const right = inWebviewMode ? dpadRight : (dpadRight || stickRight);
// Navigation with repeat prevention // Navigation with repeat prevention
const now = Date.now(); const now = Date.now();
@@ -509,7 +561,7 @@ function handleGamepadInput(gamepad) {
state.lastInput.right = 0; state.lastInput.right = 0;
} }
// A button (usually index 0) - Select/Type letter // A button (usually index 0) - Always select/activate focused menu item
if (gamepad.buttons[0]?.pressed && !state.lastInput.a) { if (gamepad.buttons[0]?.pressed && !state.lastInput.a) {
activateFocused(); activateFocused();
state.lastInput.a = true; state.lastInput.a = true;
@@ -567,10 +619,22 @@ function handleGamepadInput(gamepad) {
state.lastInput.rb = false; state.lastInput.rb = false;
} }
// Start button (usually index 9) - Menu // Back/Select button (usually index 8) - Toggle sidebar when in webview
if (gamepad.buttons[8]?.pressed && !state.lastInput.select) {
if (state.currentSection === 'browse' && state.currentWebview) {
toggleSidebar();
}
state.lastInput.select = true;
} else if (!gamepad.buttons[8]?.pressed) {
state.lastInput.select = false;
}
// Start button (usually index 9) - Menu / Toggle sidebar when viewing webpage
if (gamepad.buttons[9]?.pressed && !state.lastInput.start) { if (gamepad.buttons[9]?.pressed && !state.lastInput.start) {
// Toggle to settings // If viewing a webpage, toggle sidebar instead of going to settings
if (state.currentSection !== 'settings') { if (state.currentSection === 'browse' && state.currentWebview) {
toggleSidebar();
} else if (state.currentSection !== 'settings') {
switchSection('settings'); switchSection('settings');
} else { } else {
switchSection('home'); switchSection('home');
@@ -595,6 +659,15 @@ function handleGamepadInput(gamepad) {
moveCursor(moveX * state.cursorSpeed, moveY * state.cursorSpeed); moveCursor(moveX * state.cursorSpeed, moveY * state.cursorSpeed);
} }
// Left stick for scrolling in webview mode
const scrollDeadzone = 0.25;
const scrollX = Math.abs(leftX) > scrollDeadzone ? leftX : 0;
const scrollY = Math.abs(leftY) > scrollDeadzone ? leftY : 0;
if (scrollX !== 0 || scrollY !== 0) {
scrollWebview(scrollY * 20, scrollX * 20);
}
// Right trigger (index 7) - Left click // Right trigger (index 7) - Left click
if (gamepad.buttons[7]?.pressed && !state.lastInput.rt) { if (gamepad.buttons[7]?.pressed && !state.lastInput.rt) {
virtualClick(); virtualClick();
@@ -619,15 +692,6 @@ function handleGamepadInput(gamepad) {
} else if (!gamepad.buttons[11]?.pressed) { } else if (!gamepad.buttons[11]?.pressed) {
state.lastInput.rs = false; state.lastInput.rs = false;
} }
// Left stick click (index 10) - Scroll mode toggle could go here
if (gamepad.buttons[10]?.pressed && !state.lastInput.ls) {
// Scroll the page
scrollWebview(leftY * 100);
state.lastInput.ls = true;
} else if (!gamepad.buttons[10]?.pressed) {
state.lastInput.ls = false;
}
} }
} }
@@ -1222,8 +1286,11 @@ function enableCursor() {
updateCursorPosition(); updateCursorPosition();
state.cursorElement.classList.add('active'); state.cursorElement.classList.add('active');
// Update focusable elements to only include sidebar when in webview mode
updateFocusableElements();
// Show cursor hint // Show cursor hint
showToast('🎮 Right stick: Move cursor | RT: Click | LT: Right-click | B: Back'); showToast('🎮 Right stick: Move cursor | RT: Click | Left stick: Scroll | B: Back');
} }
function disableCursor() { function disableCursor() {
@@ -1231,6 +1298,9 @@ function disableCursor() {
if (state.cursorElement) { if (state.cursorElement) {
state.cursorElement.classList.remove('active'); state.cursorElement.classList.remove('active');
} }
// Restore full focusable elements
updateFocusableElements();
} }
function moveCursor(dx, dy) { function moveCursor(dx, dy) {
@@ -1328,11 +1398,11 @@ function virtualClick(rightClick = false) {
} }
} }
function scrollWebview(amount) { function scrollWebview(amountY, amountX = 0) {
if (!state.currentWebview) return; if (!state.currentWebview) return;
try { try {
state.currentWebview.executeJavaScript(`window.scrollBy(0, ${amount})`); state.currentWebview.executeJavaScript(`window.scrollBy(${amountX}, ${amountY})`);
} catch (err) { } catch (err) {
console.log('[BigPicture] Scroll error:', err); console.log('[BigPicture] Scroll error:', err);
} }