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:
@@ -1426,3 +1426,54 @@ body.fullscreen .bp-main {
|
||||
font-size: 0.7rem;
|
||||
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
@@ -56,6 +56,9 @@ const state = {
|
||||
cursorSpeed: 15,
|
||||
cursorElement: null,
|
||||
|
||||
// Sidebar visibility (for fullscreen webview)
|
||||
sidebarHidden: false,
|
||||
|
||||
// OSK (On-Screen Keyboard)
|
||||
oskVisible: false,
|
||||
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) {
|
||||
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)
|
||||
const webviewContainer = document.getElementById('webview-container');
|
||||
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');
|
||||
if (!activeSection) return;
|
||||
|
||||
@@ -472,11 +520,15 @@ function handleGamepadInput(gamepad) {
|
||||
const stickLeft = leftX < -CONFIG.STICK_DEADZONE;
|
||||
const stickRight = leftX > CONFIG.STICK_DEADZONE;
|
||||
|
||||
// Combine inputs
|
||||
const up = dpadUp || stickUp;
|
||||
const down = dpadDown || stickDown;
|
||||
const left = dpadLeft || stickLeft;
|
||||
const right = dpadRight || stickRight;
|
||||
// When cursor is enabled (viewing a webpage), only D-Pad navigates sidebar
|
||||
// Left stick is ignored for UI navigation in webview mode
|
||||
const inWebviewMode = state.cursorEnabled && state.currentWebview;
|
||||
|
||||
// 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
|
||||
const now = Date.now();
|
||||
@@ -509,7 +561,7 @@ function handleGamepadInput(gamepad) {
|
||||
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) {
|
||||
activateFocused();
|
||||
state.lastInput.a = true;
|
||||
@@ -567,10 +619,22 @@ function handleGamepadInput(gamepad) {
|
||||
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) {
|
||||
// Toggle to settings
|
||||
if (state.currentSection !== 'settings') {
|
||||
// If viewing a webpage, toggle sidebar instead of going to settings
|
||||
if (state.currentSection === 'browse' && state.currentWebview) {
|
||||
toggleSidebar();
|
||||
} else if (state.currentSection !== 'settings') {
|
||||
switchSection('settings');
|
||||
} else {
|
||||
switchSection('home');
|
||||
@@ -595,6 +659,15 @@ function handleGamepadInput(gamepad) {
|
||||
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
|
||||
if (gamepad.buttons[7]?.pressed && !state.lastInput.rt) {
|
||||
virtualClick();
|
||||
@@ -619,15 +692,6 @@ function handleGamepadInput(gamepad) {
|
||||
} else if (!gamepad.buttons[11]?.pressed) {
|
||||
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();
|
||||
state.cursorElement.classList.add('active');
|
||||
|
||||
// Update focusable elements to only include sidebar when in webview mode
|
||||
updateFocusableElements();
|
||||
|
||||
// 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() {
|
||||
@@ -1231,6 +1298,9 @@ function disableCursor() {
|
||||
if (state.cursorElement) {
|
||||
state.cursorElement.classList.remove('active');
|
||||
}
|
||||
|
||||
// Restore full focusable elements
|
||||
updateFocusableElements();
|
||||
}
|
||||
|
||||
function moveCursor(dx, dy) {
|
||||
@@ -1328,11 +1398,11 @@ function virtualClick(rightClick = false) {
|
||||
}
|
||||
}
|
||||
|
||||
function scrollWebview(amount) {
|
||||
function scrollWebview(amountY, amountX = 0) {
|
||||
if (!state.currentWebview) return;
|
||||
|
||||
try {
|
||||
state.currentWebview.executeJavaScript(`window.scrollBy(0, ${amount})`);
|
||||
state.currentWebview.executeJavaScript(`window.scrollBy(${amountX}, ${amountY})`);
|
||||
} catch (err) {
|
||||
console.log('[BigPicture] Scroll error:', err);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user