Add Big Picture mode and multi-target build
Introduce a Big Picture mode and support building two app targets. CMakeLists was refactored to add a helper (add_nebula_app_target) and now registers NebulaBrowser and NebulaBigPicture executables, moving UI/assets post-build copying into the helper. A new app/main_bigpicture.cpp entry was added and RunNebula now accepts LaunchOptions (AppMode) with a new LaunchOptions struct. NebulaController was extended heavily to manage a BigPicture browser role: creation, enter/exit mode, layout logic, cursor injection/removal, remote input handlers (mouse move/click/wheel/text), and state syncing. Cef/browser_client updated for the new BigPicture role and message filtering. Platform API gained MoveCursorToBrowserPoint with platform stubs/Win implementation. Big-picture UI files (CSS/JS/HTML) were also updated to support the new mode.
This commit is contained in:
+237
-1
@@ -132,6 +132,83 @@ body.mouse-active {
|
||||
left: -100px;
|
||||
}
|
||||
|
||||
.browser-stage-frame {
|
||||
position: fixed;
|
||||
left: var(--browser-stage-x, 20%);
|
||||
top: var(--browser-stage-y, 10%);
|
||||
width: var(--browser-stage-width, 60%);
|
||||
height: var(--browser-stage-height, 80%);
|
||||
z-index: 3;
|
||||
pointer-events: none;
|
||||
border-radius: var(--bp-radius-xl);
|
||||
box-shadow:
|
||||
0 0 0 2px rgba(255, 255, 255, 0.08),
|
||||
0 0 0 6px rgba(123, 46, 255, 0.18),
|
||||
0 24px 80px rgba(0, 0, 0, 0.55),
|
||||
0 0 80px var(--bp-primary-glow);
|
||||
opacity: 1;
|
||||
transition: opacity var(--bp-transition-normal), box-shadow var(--bp-transition-normal);
|
||||
}
|
||||
|
||||
.browser-stage-frame.hidden {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.virtual-cursor {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 250;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
pointer-events: none;
|
||||
transform: translate3d(var(--virtual-cursor-x, 50vw), var(--virtual-cursor-y, 50vh), 0);
|
||||
transition: opacity var(--bp-transition-fast);
|
||||
filter: drop-shadow(0 8px 18px rgba(0, 0, 0, 0.55));
|
||||
}
|
||||
|
||||
.virtual-cursor.hidden {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.virtual-cursor::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 4px;
|
||||
top: 3px;
|
||||
width: 18px;
|
||||
height: 24px;
|
||||
background: linear-gradient(135deg, var(--bp-text), var(--bp-accent));
|
||||
clip-path: polygon(0 0, 0 100%, 38% 76%, 60% 100%, 84% 86%, 62% 62%, 100% 62%);
|
||||
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.45), 0 0 20px var(--bp-accent-glow);
|
||||
}
|
||||
|
||||
.virtual-cursor-dot {
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
top: 10px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 999px;
|
||||
background: var(--bp-primary);
|
||||
opacity: 0;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.virtual-cursor.clicking .virtual-cursor-dot {
|
||||
animation: virtual-cursor-click 180ms ease-out;
|
||||
}
|
||||
|
||||
.virtual-cursor.right-clicking .virtual-cursor-dot {
|
||||
background: var(--bp-warning);
|
||||
animation: virtual-cursor-click 180ms ease-out;
|
||||
}
|
||||
|
||||
@keyframes virtual-cursor-click {
|
||||
0% { opacity: 0.95; transform: scale(0.8); }
|
||||
100% { opacity: 0; transform: scale(4.4); }
|
||||
}
|
||||
|
||||
@keyframes glow-pulse {
|
||||
0% { transform: scale(1); opacity: 0.3; }
|
||||
100% { transform: scale(1.2); opacity: 0.5; }
|
||||
@@ -220,6 +297,15 @@ body.mouse-active {
|
||||
color: var(--bp-text-muted);
|
||||
}
|
||||
|
||||
.status-icon.controller-status.connected {
|
||||
color: var(--bp-success);
|
||||
box-shadow: 0 0 20px rgba(74, 222, 128, 0.25);
|
||||
}
|
||||
|
||||
.status-icon.controller-status.disconnected {
|
||||
color: var(--bp-text-dim);
|
||||
}
|
||||
|
||||
.status-icon .material-symbols-outlined {
|
||||
font-size: 20px;
|
||||
}
|
||||
@@ -338,8 +424,9 @@ body.mouse-active {
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: var(--bp-bg);
|
||||
background: transparent;
|
||||
z-index: 2;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.webview-container.hidden {
|
||||
@@ -353,6 +440,155 @@ body.mouse-active {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.native-browser-panel {
|
||||
position: absolute;
|
||||
top: var(--bp-spacing-md);
|
||||
right: var(--bp-spacing-md);
|
||||
bottom: var(--bp-spacing-md);
|
||||
width: clamp(168px, 18vw, 260px);
|
||||
overflow-y: auto;
|
||||
padding: var(--bp-spacing-sm);
|
||||
background: linear-gradient(180deg, rgba(10,10,15,0.88) 0%, rgba(20,20,31,0.82) 100%);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: var(--bp-radius-lg);
|
||||
box-shadow: 0 18px 60px rgba(0, 0, 0, 0.35);
|
||||
backdrop-filter: blur(20px);
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.native-page-card {
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(0, 1fr);
|
||||
align-items: center;
|
||||
gap: var(--bp-spacing-sm);
|
||||
padding: var(--bp-spacing-sm);
|
||||
margin-bottom: var(--bp-spacing-md);
|
||||
background: rgba(20, 20, 31, 0.78);
|
||||
border: 2px solid var(--bp-border);
|
||||
border-radius: var(--bp-radius-lg);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.native-page-card:focus,
|
||||
.native-page-card.focused {
|
||||
outline: none;
|
||||
border-color: var(--bp-accent);
|
||||
box-shadow: var(--bp-focus-ring-accent);
|
||||
}
|
||||
|
||||
.native-page-card .material-symbols-outlined {
|
||||
font-size: 28px;
|
||||
color: var(--bp-accent);
|
||||
}
|
||||
|
||||
.native-page-card h2 {
|
||||
font-size: 0.95rem;
|
||||
color: var(--bp-text);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.native-page-card p {
|
||||
font-size: 0.72rem;
|
||||
color: var(--bp-text-muted);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.native-page-card .controller-note {
|
||||
grid-column: 1 / -1;
|
||||
margin-top: 4px;
|
||||
color: var(--bp-accent);
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.browser-actions {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: var(--bp-spacing-xs);
|
||||
margin-bottom: var(--bp-spacing-lg);
|
||||
}
|
||||
|
||||
.action-button:disabled {
|
||||
opacity: 0.45;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.tab-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--bp-spacing-xs);
|
||||
}
|
||||
|
||||
.bp-tab {
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(0, 1fr) auto;
|
||||
grid-template-areas:
|
||||
"dot title close"
|
||||
"dot url close";
|
||||
align-items: center;
|
||||
column-gap: var(--bp-spacing-sm);
|
||||
padding: var(--bp-spacing-sm);
|
||||
background: var(--bp-surface);
|
||||
border: 2px solid var(--bp-border);
|
||||
border-radius: var(--bp-radius-md);
|
||||
color: var(--bp-text);
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.bp-tab.active {
|
||||
border-color: var(--bp-primary);
|
||||
background: var(--bp-surface-active);
|
||||
}
|
||||
|
||||
.bp-tab:focus,
|
||||
.bp-tab.focused {
|
||||
outline: none;
|
||||
border-color: var(--bp-accent);
|
||||
box-shadow: var(--bp-focus-ring-accent);
|
||||
}
|
||||
|
||||
.tab-dot {
|
||||
grid-area: dot;
|
||||
color: var(--bp-accent);
|
||||
}
|
||||
|
||||
.tab-text {
|
||||
grid-area: title;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.tab-url {
|
||||
grid-area: url;
|
||||
color: var(--bp-text-muted);
|
||||
font-size: 0.8rem;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.tab-close-inline {
|
||||
grid-area: close;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: var(--bp-radius-sm);
|
||||
color: var(--bp-text-muted);
|
||||
}
|
||||
|
||||
.tab-close-inline:hover {
|
||||
background: var(--bp-danger);
|
||||
color: var(--bp-text);
|
||||
}
|
||||
|
||||
/* Content area */
|
||||
.bp-content {
|
||||
flex: 1;
|
||||
|
||||
+817
-2797
File diff suppressed because it is too large
Load Diff
@@ -26,6 +26,10 @@
|
||||
<div class="bg-particles"></div>
|
||||
<div class="bg-glow"></div>
|
||||
</div>
|
||||
<div id="browser-stage-frame" class="browser-stage-frame hidden" aria-hidden="true"></div>
|
||||
<div id="virtual-cursor" class="virtual-cursor hidden" aria-hidden="true">
|
||||
<div class="virtual-cursor-dot"></div>
|
||||
</div>
|
||||
|
||||
<!-- Top header bar -->
|
||||
<header class="bp-header">
|
||||
@@ -41,6 +45,9 @@
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="status-icons">
|
||||
<span id="bp-controller-status" class="status-icon controller-status disconnected" title="Controller disconnected">
|
||||
<span class="material-symbols-outlined">sports_esports</span>
|
||||
</span>
|
||||
<span id="bp-wifi" class="status-icon" title="Connected">
|
||||
<span class="material-symbols-outlined">wifi</span>
|
||||
</span>
|
||||
@@ -72,6 +79,10 @@
|
||||
<span class="material-symbols-outlined">bookmarks</span>
|
||||
<span class="nav-label">Bookmarks</span>
|
||||
</button>
|
||||
<button class="nav-item" data-section="history" data-focusable tabindex="0">
|
||||
<span class="material-symbols-outlined">history</span>
|
||||
<span class="nav-label">History</span>
|
||||
</button>
|
||||
<button class="nav-item" data-section="downloads" data-focusable tabindex="0">
|
||||
<span class="material-symbols-outlined">download</span>
|
||||
<span class="nav-label">Downloads</span>
|
||||
@@ -147,6 +158,26 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- History section -->
|
||||
<section id="section-history" class="bp-section">
|
||||
<div class="section-header">
|
||||
<h1 class="section-title">History</h1>
|
||||
<p class="section-subtitle">Recently visited sites from this profile</p>
|
||||
</div>
|
||||
<div class="section-actions">
|
||||
<button class="action-btn danger" id="bp-clear-history" data-focusable tabindex="0">
|
||||
<span class="material-symbols-outlined">delete</span>
|
||||
<span>Clear History</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="list-container" id="historyList">
|
||||
<div class="empty-state">
|
||||
<span class="material-symbols-outlined">history</span>
|
||||
<p>No browsing history</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Downloads section -->
|
||||
<section id="section-downloads" class="bp-section">
|
||||
<div class="section-header">
|
||||
@@ -332,7 +363,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="option-control">
|
||||
<button class="action-button" id="bp-clear-history" data-focusable tabindex="0">
|
||||
<button class="action-button" id="bp-clear-history-settings" data-focusable tabindex="0">
|
||||
<span class="material-symbols-outlined">delete</span>
|
||||
<span>Clear</span>
|
||||
</button>
|
||||
@@ -403,28 +434,28 @@
|
||||
|
||||
<!-- Bottom controller hints -->
|
||||
<footer class="bp-footer">
|
||||
<div class="controller-hints">
|
||||
<div class="controller-hints" id="controller-hints">
|
||||
<div class="hint">
|
||||
<span class="controller-btn dpad">
|
||||
<span class="material-symbols-outlined">gamepad</span>
|
||||
</span>
|
||||
<span>Navigate</span>
|
||||
<span id="hint-navigate">Navigate</span>
|
||||
</div>
|
||||
<div class="hint">
|
||||
<span class="controller-btn a-btn">A</span>
|
||||
<span>Select</span>
|
||||
<span id="hint-a">Select</span>
|
||||
</div>
|
||||
<div class="hint">
|
||||
<span class="controller-btn b-btn">B</span>
|
||||
<span>Back</span>
|
||||
<span id="hint-b">Back</span>
|
||||
</div>
|
||||
<div class="hint">
|
||||
<span class="controller-btn y-btn">Y</span>
|
||||
<span>Search</span>
|
||||
<span id="hint-y">Search</span>
|
||||
</div>
|
||||
<div class="hint">
|
||||
<span class="controller-btn menu-btn">☰</span>
|
||||
<span>Menu</span>
|
||||
<span id="hint-menu">Menu</span>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
Reference in New Issue
Block a user