mirror of
https://github.com/Pixelplanet/Pixel-VR-Player.git
synced 2026-10-26 01:01:33 +01:00
Implement Ambilight screen glow, Card hover Z-lift, Scrubber timestamp bubble, and Quick Format selector dialog
This commit is contained in:
1 parent
05f13ddfc2
commit
a6be5eb60d
9 files changed
+342
-67
No files matched your search
+3
-1
@@ -109,7 +109,9 @@ set(PIXELVR_SHADERS
|
||||
shaders/ui.frag
|
||||
shaders/text.vert
|
||||
shaders/text.frag
|
||||
shaders/thumb.frag)
|
||||
shaders/thumb.frag
|
||||
shaders/ambilight.frag
|
||||
shaders/ambilight_nv12.frag)
|
||||
pixelvr_compile_shaders(pixelvr_shaders
|
||||
OUTPUT_DIR "${CMAKE_BINARY_DIR}/shaders"
|
||||
SOURCES ${PIXELVR_SHADERS})
|
||||
|
||||
+13
-13
@@ -69,9 +69,9 @@ In `shaders/thumb.frag`, thumbnails are drawn without scrims, causing white dura
|
||||
```
|
||||
- **Folder Stack Visual Cue (`src/gfx/vulkan_renderer.cpp`):**
|
||||
- When `item.isDir` is `true` in `BigCards` or `MiniCards`, draw a secondary rounded box `0.008m` above `lay.thumbCy + lay.thumbHalfH` (`width = lay.thumbHalfW * 0.88f, height = 0.012f`), mimicking a physical folder tab so folders with video previews inside are instantly recognizable.
|
||||
- **Smooth Hover Z-Lift Animation:**
|
||||
- Add `float cardHoverAnim_[12]{};` to `VulkanRenderer`. Each frame, lerp `cardHoverAnim_[i]` toward `1.0f` if `entryIdx == browserHovered_` else `0.0f` (`speed ~ 14.0f * dt`).
|
||||
- Offset the card's local Z by `-0.012f * cardHoverAnim_[i]` (towards user) so hovered cards float forward.
|
||||
- **Smooth Hover Z-Lift Animation [COMPLETED]:**
|
||||
- Added `float cardHoverAnim_[12]{};` to `VulkanRenderer`. Each frame, lerps `cardHoverAnim_[i]` toward `1.0f` if `entryIdx == browserHovered_` else `0.0f` (`speed ~ 14.0f * dt`).
|
||||
- Translates the card, its thumbnail, tab, format badges, and text along panel-local Z by `+0.012f * cardHoverAnim_[i]` (towards user) with stereoscopic eye parallax so hovered cards float forward in real 3D VR space.
|
||||
|
||||
### 3.3. Header Navigation & Breadcrumbs (`src/gfx/vulkan_renderer.cpp`)
|
||||
- **Dedicated `[ ↑ Up ]` Button in Header:**
|
||||
@@ -87,8 +87,8 @@ In `shaders/thumb.frag`, thumbnails are drawn without scrims, causing white dura
|
||||
- **Color Swatches:** In `Settings > UI & Appearance`, render circular color dots for `Text Color` and `Accent Theme` with a white halo ring on the active selection.
|
||||
- **Stepped Bar Sliders:** For numeric settings (`Text Size`, `UI Distance`, `Screen Curvature`), draw a thin progress fill inside the card.
|
||||
|
||||
### 3.5. Cinema Transport Dock Scrubber Timestamp Bubble (`src/gfx/vulkan_renderer.cpp`)
|
||||
- When hovering over the playback scrubber track (`pointerOnTimeline_` is true), calculate `hoverTime = format_time(pointerScrub_ * transportDuration_)` and render a floating glass bubble above the pointer tip showing the timestamp before clicking.
|
||||
### 3.5. Cinema Transport Dock Scrubber Timestamp Bubble [COMPLETED]
|
||||
- When hovering or dragging along the playback scrubber track (`pointerOnTimeline_` is true), renders a rounded floating speech-bubble capsule with an accent rim and downward pointer notch directly over the laser/scrub position displaying `M:SS` or `H:MM:SS` before and during seeks.
|
||||
|
||||
---
|
||||
|
||||
@@ -123,18 +123,18 @@ In `shaders/thumb.frag`, thumbnails are drawn without scrims, causing white dura
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3. Dynamic Context Menu Height & Format Selector Dialog
|
||||
- **Dynamic Box Sizing:** In `src/gfx/vulkan_renderer.cpp`, compute context menu container `halfH` dynamically as `numOptions * (btnHalfH + gap) + padding` so buttons never overflow outside the background box.
|
||||
- **3D & VR Projection Quick Selector:** Add a "Change 3D / VR Format" action to the `Y` context menu that opens a 5-pill popup (`2D Flat`, `3D SBS`, `3D Top-Bottom`, `180° VR`, `360° VR`) to instantly override incorrect automatic media tags.
|
||||
### 4.3. Dynamic Context Menu Height & Format Selector Dialog [COMPLETED]
|
||||
- **Dynamic Box Sizing:** In `src/gfx/vulkan_renderer.cpp`, computes context menu container `mHalfH` dynamically as `std::max(0.20f, 0.5f * menuSpanY + 0.130f)` so buttons never overflow outside the background box.
|
||||
- **3D & VR Projection Quick Selector:** Added "Change 3D / VR Format" action to the `Y` context menu and clicking `PROJ: ...` on the Transport dock that opens a 5-pill popup (`2D Flat`, `3D SBS`, `3D Top-Bottom`, `180° VR`, `360° VR`) to instantly override projection format.
|
||||
|
||||
### 4.4. Spatial Haptic Feedback Integration
|
||||
### 4.4. Spatial Haptic Feedback Integration [COMPLETED]
|
||||
- OpenXR haptic pulses make laser interactions feel responsive and physical:
|
||||
- **Button Hover Enter:** Trigger short tick: `xr_.triggerHaptic(hand, 0.008f, 0.0f, 0.12f)`.
|
||||
- **Button Hover Enter:** Trigger short tick: `xr_.triggerHaptic(hand, 0.008f, 0.0f, settings_.hoverHapticsStrength())`.
|
||||
- **Button Click / Toggle:** Trigger crisp confirmation click: `xr_.triggerHaptic(hand, 0.025f, 0.0f, 0.45f)`.
|
||||
- **Scrubber Seek Drag:** Trigger micro-ticks as the scrubber thumb crosses minute intervals: `xr_.triggerHaptic(hand, 0.005f, 0.0f, 0.08f)`.
|
||||
- **Controls Menu Settings:** Toggle switch + 4 strength presets in `Settings > Controls & Bindings`.
|
||||
|
||||
### 4.5. Cinema Screen Ambilight Glow
|
||||
- When playing flat or curved video, sample the top mip of the video frame texture and render a soft, blurred glow quad behind the screen with radial alpha falloff, filling the peripheral vision with matching environmental colors.
|
||||
### 4.5. Cinema Screen Ambilight Glow [COMPLETED]
|
||||
- When playing flat or curved video, samples the top mip levels of the video frame texture and renders a soft, blurred glow quad behind the screen (`shaders/ambilight.frag` and `shaders/ambilight_nv12.frag`) with radial alpha falloff, filling the peripheral vision with matching environmental colors. Configurable under `Settings > Display > Ambient Screen Glow` (`Off`, `Subtle`, `Medium`, `Strong`).
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
#version 450
|
||||
|
||||
// Ambient screen glow ("ambilight") for the flat cinema screen (resolved RGBA
|
||||
// dma-buf path). Drawn on an enlarged copy of the screen mesh *behind* the
|
||||
// video: each fragment outside the screen rectangle samples a heavily blurred
|
||||
// (high mip) colour from the nearest screen edge and fades it out radially, so
|
||||
// the dark void around the screen picks up the colours of the picture.
|
||||
|
||||
layout(binding = 0) uniform sampler2D video;
|
||||
|
||||
layout(push_constant) uniform Push {
|
||||
mat4 mvp;
|
||||
vec4 conversion; // xy: crop UV scale
|
||||
vec4 range; // z: sRGB decode flag, w: glow intensity
|
||||
vec4 stereo; // x: mode (0 mono/1 SBS/2 TB), y: eye, z: swap, w: curvature
|
||||
} pc;
|
||||
|
||||
layout(location = 0) in vec2 vUV;
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
// Must match kAmbilightScaleX/Y in vulkan_renderer.cpp.
|
||||
const vec2 kScale = vec2(1.55, 1.90);
|
||||
const float kAspect = 1.7778; // screen width / height (glow shape only)
|
||||
|
||||
vec3 sampleEye(vec2 uv, float lod) {
|
||||
float eye = pc.stereo.y;
|
||||
if (pc.stereo.z > 0.5) eye = 1.0 - eye;
|
||||
if (pc.stereo.x > 1.5) uv.y = uv.y * 0.5 + eye * 0.5;
|
||||
else if (pc.stereo.x > 0.5) uv.x = uv.x * 0.5 + eye * 0.5;
|
||||
return textureLod(video, uv * pc.conversion.xy, lod).rgb;
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 p = (vUV - 0.5) * kScale; // the screen covers |p| <= 0.5
|
||||
vec2 q = abs(p) - 0.5;
|
||||
if (q.x < 0.0 && q.y < 0.0) {
|
||||
discard; // hidden behind the screen anyway
|
||||
}
|
||||
float d = length(max(q * vec2(kAspect, 1.0), 0.0)); // metres-ish, in screen heights
|
||||
float a = 1.0 - smoothstep(0.0, 0.46, d);
|
||||
a *= a;
|
||||
if (a <= 0.003) {
|
||||
discard;
|
||||
}
|
||||
|
||||
// Nearest edge point, pulled slightly inwards so we gather the edge band.
|
||||
vec2 uvS = clamp(p + 0.5, 0.0, 1.0);
|
||||
uvS = 0.5 + (uvS - 0.5) * 0.90;
|
||||
float lod = max(float(textureQueryLevels(video)) - 4.0, 0.0);
|
||||
vec3 rgb = sampleEye(uvS, lod) * 0.40;
|
||||
rgb += sampleEye(clamp(uvS + vec2(0.07, 0.0), 0.0, 1.0), lod) * 0.15;
|
||||
rgb += sampleEye(clamp(uvS - vec2(0.07, 0.0), 0.0, 1.0), lod) * 0.15;
|
||||
rgb += sampleEye(clamp(uvS + vec2(0.0, 0.07), 0.0, 1.0), lod) * 0.15;
|
||||
rgb += sampleEye(clamp(uvS - vec2(0.0, 0.07), 0.0, 1.0), lod) * 0.15;
|
||||
|
||||
if (pc.range.z > 0.5) {
|
||||
rgb = mix(rgb / 12.92, pow((rgb + 0.055) / 1.055, vec3(2.4)),
|
||||
step(vec3(0.04045), rgb));
|
||||
}
|
||||
// Gentle saturation lift so the glow reads as colour, not grey haze.
|
||||
float l = dot(rgb, vec3(0.2126, 0.7152, 0.0722));
|
||||
rgb = max(mix(vec3(l), rgb, 1.25), vec3(0.0));
|
||||
|
||||
outColor = vec4(rgb, a * clamp(pc.range.w, 0.0, 1.0));
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
#version 450
|
||||
|
||||
// Ambient screen glow for the CPU NV12 upload path. Same geometry and falloff
|
||||
// as ambilight.frag, but converts YUV -> RGB like quad.frag.
|
||||
|
||||
layout(binding = 0) uniform sampler2D luma;
|
||||
layout(binding = 1) uniform sampler2D chroma;
|
||||
|
||||
layout(push_constant) uniform Push {
|
||||
mat4 mvp;
|
||||
vec4 conversion;
|
||||
vec4 range; // x: luma offset, y: luma scale, z: sRGB decode flag, w: glow intensity
|
||||
vec4 stereo; // x: mode (0 mono/1 SBS/2 TB), y: eye, z: swap, w: curvature
|
||||
} pc;
|
||||
|
||||
layout(location = 0) in vec2 vUV;
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
const vec2 kScale = vec2(1.55, 1.90);
|
||||
const float kAspect = 1.7778;
|
||||
|
||||
vec3 sampleEye(vec2 uv) {
|
||||
float eye = pc.stereo.y;
|
||||
if (pc.stereo.z > 0.5) eye = 1.0 - eye;
|
||||
if (pc.stereo.x > 1.5) uv.y = uv.y * 0.5 + eye * 0.5;
|
||||
else if (pc.stereo.x > 0.5) uv.x = uv.x * 0.5 + eye * 0.5;
|
||||
float lodY = max(float(textureQueryLevels(luma)) - 4.0, 0.0);
|
||||
float lodC = max(float(textureQueryLevels(chroma)) - 3.0, 0.0);
|
||||
float y = (textureLod(luma, uv, lodY).r - pc.range.x) * pc.range.y;
|
||||
vec2 c = textureLod(chroma, uv, lodC).rg - vec2(128.0 / 255.0);
|
||||
return clamp(vec3(y + pc.conversion.x * c.y,
|
||||
y + pc.conversion.y * c.x + pc.conversion.z * c.y,
|
||||
y + pc.conversion.w * c.x), 0.0, 1.0);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 p = (vUV - 0.5) * kScale;
|
||||
vec2 q = abs(p) - 0.5;
|
||||
if (q.x < 0.0 && q.y < 0.0) {
|
||||
discard;
|
||||
}
|
||||
float d = length(max(q * vec2(kAspect, 1.0), 0.0));
|
||||
float a = 1.0 - smoothstep(0.0, 0.46, d);
|
||||
a *= a;
|
||||
if (a <= 0.003) {
|
||||
discard;
|
||||
}
|
||||
|
||||
vec2 uvS = clamp(p + 0.5, 0.0, 1.0);
|
||||
uvS = 0.5 + (uvS - 0.5) * 0.90;
|
||||
vec3 rgb = sampleEye(uvS) * 0.40;
|
||||
rgb += sampleEye(clamp(uvS + vec2(0.07, 0.0), 0.0, 1.0)) * 0.15;
|
||||
rgb += sampleEye(clamp(uvS - vec2(0.07, 0.0), 0.0, 1.0)) * 0.15;
|
||||
rgb += sampleEye(clamp(uvS + vec2(0.0, 0.07), 0.0, 1.0)) * 0.15;
|
||||
rgb += sampleEye(clamp(uvS - vec2(0.0, 0.07), 0.0, 1.0)) * 0.15;
|
||||
|
||||
if (pc.range.z > 0.5) {
|
||||
rgb = mix(rgb / 12.92, pow((rgb + 0.055) / 1.055, vec3(2.4)),
|
||||
step(vec3(0.04045), rgb));
|
||||
}
|
||||
float l = dot(rgb, vec3(0.2126, 0.7152, 0.0722));
|
||||
rgb = max(mix(vec3(l), rgb, 1.25), vec3(0.0));
|
||||
|
||||
outColor = vec4(rgb, a * clamp(pc.range.w, 0.0, 1.0));
|
||||
}
|
||||
+62
-26
@@ -792,6 +792,9 @@ void App::openActionMenu() {
|
||||
if (VaultManager::instance().isUnlocked() && !browser_.inVault()) {
|
||||
actionMenu_.options.push_back("Move to Vault");
|
||||
}
|
||||
if (fe && !fe->isDir && fe->isVideo) {
|
||||
actionMenu_.options.push_back("Change 3D / VR Format");
|
||||
}
|
||||
actionMenu_.options.push_back("Move To...");
|
||||
actionMenu_.options.push_back("Rename");
|
||||
actionMenu_.options.push_back("Delete");
|
||||
@@ -1072,6 +1075,7 @@ int App::run() {
|
||||
settings_.uiTextColorIdx(),
|
||||
settings_.uiAccentColorIdx(),
|
||||
settings_.uiDistance());
|
||||
renderer_.setAmbilightIntensity(settings_.ambilightIntensity());
|
||||
renderer_.setUiFollowMode(settings_.uiFollowMode());
|
||||
renderer_.setBrowserViewMode(static_cast<VulkanRenderer::BrowserViewMode>(settings_.browserViewMode()));
|
||||
renderer_.setBrowserQuickPath(browser_.isCurrentBookmarked());
|
||||
@@ -1837,7 +1841,7 @@ int App::run() {
|
||||
|
||||
// File Action Submenu or Files view interaction inside Unified Spatial Hub
|
||||
if (actionMenu_.visible) {
|
||||
if (!browser_.isOpen() || downloadsWindow_ || settings_.isOpen() || !pendingDl_.empty()) {
|
||||
if ((!browser_.isOpen() && actionMenu_.page != 5) || downloadsWindow_ || settings_.isOpen() || !pendingDl_.empty()) {
|
||||
actionMenu_.visible = false;
|
||||
} else {
|
||||
const Browser::Entry* fe = browser_.focused();
|
||||
@@ -1845,7 +1849,7 @@ int App::run() {
|
||||
// Button Y (or configured move button) toggles/cancels
|
||||
if (actionBtnPressed) {
|
||||
actionMenu_.visible = false;
|
||||
} else if (actionMenu_.page == 0 || actionMenu_.page == 1) {
|
||||
} else if (actionMenu_.page == 0 || actionMenu_.page == 1 || actionMenu_.page == 5) {
|
||||
const int dy = navStep(in.thumbstickY, nowT);
|
||||
if (dy != 0) {
|
||||
actionMenu_.selectedOption += dy;
|
||||
@@ -1909,6 +1913,18 @@ int App::run() {
|
||||
xr_.triggerHaptic(1, 0.050f, 0.0f, 0.7f);
|
||||
}
|
||||
actionMenu_.visible = false;
|
||||
} else if (opt == "Change 3D / VR Format") {
|
||||
actionMenu_.page = 5;
|
||||
actionMenu_.title = "3D & VR Format";
|
||||
actionMenu_.subtitle = fe ? fe->label : "";
|
||||
actionMenu_.options.clear();
|
||||
actionMenu_.options.push_back("2D Flat (Standard)");
|
||||
actionMenu_.options.push_back("3D Side-by-Side (SBS)");
|
||||
actionMenu_.options.push_back("3D Top-Bottom (Over-Under)");
|
||||
actionMenu_.options.push_back("VR 180° (Side-by-Side)");
|
||||
actionMenu_.options.push_back("VR 360° (Equirectangular)");
|
||||
actionMenu_.options.push_back("Cancel");
|
||||
actionMenu_.selectedOption = 0;
|
||||
} else if (opt == "Move To...") {
|
||||
actionMenu_.page = 1;
|
||||
actionMenu_.title = "Move Destination";
|
||||
@@ -1982,6 +1998,36 @@ int App::run() {
|
||||
}
|
||||
actionMenu_.visible = false;
|
||||
}
|
||||
} else if (actionMenu_.page == 5) {
|
||||
if (opt == "2D Flat (Standard)") {
|
||||
settings_.setProjectionMode(0);
|
||||
settings_.setStereoMode(0);
|
||||
renderer_.setProjectionMode(VulkanRenderer::ProjectionMode::Flat);
|
||||
renderer_.setStereoMode(VulkanRenderer::StereoMode::Mono);
|
||||
} else if (opt == "3D Side-by-Side (SBS)") {
|
||||
settings_.setProjectionMode(0);
|
||||
settings_.setStereoMode(1);
|
||||
renderer_.setProjectionMode(VulkanRenderer::ProjectionMode::Flat);
|
||||
renderer_.setStereoMode(VulkanRenderer::StereoMode::SideBySide);
|
||||
} else if (opt == "3D Top-Bottom (Over-Under)") {
|
||||
settings_.setProjectionMode(0);
|
||||
settings_.setStereoMode(2);
|
||||
renderer_.setProjectionMode(VulkanRenderer::ProjectionMode::Flat);
|
||||
renderer_.setStereoMode(VulkanRenderer::StereoMode::TopBottom);
|
||||
} else if (opt == "VR 180° (Side-by-Side)") {
|
||||
settings_.setProjectionMode(2);
|
||||
settings_.setStereoMode(1);
|
||||
renderer_.setProjectionMode(VulkanRenderer::ProjectionMode::Equirect180);
|
||||
renderer_.setStereoMode(VulkanRenderer::StereoMode::SideBySide);
|
||||
} else if (opt == "VR 360° (Equirectangular)") {
|
||||
settings_.setProjectionMode(1);
|
||||
settings_.setStereoMode(0);
|
||||
renderer_.setProjectionMode(VulkanRenderer::ProjectionMode::Equirect360);
|
||||
renderer_.setStereoMode(VulkanRenderer::StereoMode::Mono);
|
||||
}
|
||||
xr_.triggerHaptic(0, 0.040f, 0.0f, 0.6f);
|
||||
xr_.triggerHaptic(1, 0.040f, 0.0f, 0.6f);
|
||||
actionMenu_.visible = false;
|
||||
}
|
||||
}
|
||||
interacted = true;
|
||||
@@ -2347,30 +2393,20 @@ int App::run() {
|
||||
cb == VulkanRenderer::CtrlButton::SeekFwd) {
|
||||
media_.seekRelative(10.0);
|
||||
} else if (cb == VulkanRenderer::CtrlButton::FormatToggle) {
|
||||
int pm = settings_.projectionMode();
|
||||
int sm = settings_.stereoMode();
|
||||
if (pm == 0 && sm == 0) {
|
||||
settings_.setStereoMode(1);
|
||||
renderer_.setStereoMode(VulkanRenderer::StereoMode::SideBySide);
|
||||
} else if (pm == 0) {
|
||||
settings_.setProjectionMode(2);
|
||||
settings_.setStereoMode(1);
|
||||
renderer_.setProjectionMode(VulkanRenderer::ProjectionMode::Equirect180);
|
||||
renderer_.setStereoMode(VulkanRenderer::StereoMode::SideBySide);
|
||||
} else if (pm == 2 && sm == 1) {
|
||||
settings_.setStereoMode(2);
|
||||
renderer_.setStereoMode(VulkanRenderer::StereoMode::TopBottom);
|
||||
} else if (pm == 2) {
|
||||
settings_.setProjectionMode(1);
|
||||
settings_.setStereoMode(1);
|
||||
renderer_.setProjectionMode(VulkanRenderer::ProjectionMode::Equirect360);
|
||||
renderer_.setStereoMode(VulkanRenderer::StereoMode::SideBySide);
|
||||
} else {
|
||||
settings_.setProjectionMode(0);
|
||||
settings_.setStereoMode(0);
|
||||
renderer_.setProjectionMode(VulkanRenderer::ProjectionMode::Flat);
|
||||
renderer_.setStereoMode(VulkanRenderer::StereoMode::Mono);
|
||||
}
|
||||
actionMenu_.visible = true;
|
||||
actionMenu_.page = 5;
|
||||
actionMenu_.title = "Playback Format";
|
||||
actionMenu_.subtitle = "Select 3D / VR Projection";
|
||||
actionMenu_.options.clear();
|
||||
actionMenu_.options.push_back("2D Flat (Standard)");
|
||||
actionMenu_.options.push_back("3D Side-by-Side (SBS)");
|
||||
actionMenu_.options.push_back("3D Top-Bottom (Over-Under)");
|
||||
actionMenu_.options.push_back("VR 180° (Side-by-Side)");
|
||||
actionMenu_.options.push_back("VR 360° (Equirectangular)");
|
||||
actionMenu_.options.push_back("Cancel");
|
||||
actionMenu_.selectedOption = 0;
|
||||
xr_.triggerHaptic(0, 0.020f, 0.0f, 0.4f);
|
||||
xr_.triggerHaptic(1, 0.020f, 0.0f, 0.4f);
|
||||
} else if (cb == VulkanRenderer::CtrlButton::AudioToggle) {
|
||||
if (mediaOpen_ && media_.audioStreams().size() > 1) {
|
||||
const auto& streams = media_.audioStreams();
|
||||
|
||||
+102
-27
@@ -192,6 +192,7 @@ VulkanRenderer::~VulkanRenderer() {
|
||||
if (descriptorSetLayout_ != VK_NULL_HANDLE)
|
||||
vkDestroyDescriptorSetLayout(device_, descriptorSetLayout_, nullptr);
|
||||
if (pipeline_ != VK_NULL_HANDLE) vkDestroyPipeline(device_, pipeline_, nullptr);
|
||||
if (ambilightPipeline_ != VK_NULL_HANDLE) vkDestroyPipeline(device_, ambilightPipeline_, nullptr);
|
||||
if (pipelineLayout_ != VK_NULL_HANDLE)
|
||||
vkDestroyPipelineLayout(device_, pipelineLayout_, nullptr);
|
||||
if (spherePipeline_ != VK_NULL_HANDLE)
|
||||
@@ -807,6 +808,8 @@ bool VulkanRenderer::createPipelines() {
|
||||
|
||||
return buildPipeline("quad.vert.spv", "quad.frag.spv", pipelineLayout_,
|
||||
pipeline_) &&
|
||||
buildPipeline("quad.vert.spv", "ambilight_nv12.frag.spv", pipelineLayout_,
|
||||
ambilightPipeline_, true) &&
|
||||
buildPipeline("sphere.vert.spv", "sphere.frag.spv",
|
||||
spherePipelineLayout_, spherePipeline_) &&
|
||||
buildPipeline("ui.vert.spv", "ui.frag.spv", uiPipelineLayout_,
|
||||
@@ -1367,7 +1370,7 @@ void VulkanRenderer::updatePointer() {
|
||||
pointerHit_[1] = world.y;
|
||||
pointerHit_[2] = world.z;
|
||||
|
||||
if (actionMenu_.page == 0 || actionMenu_.page == 1) {
|
||||
if (actionMenu_.page == 0 || actionMenu_.page == 1 || actionMenu_.page == 5) {
|
||||
const int optCount = static_cast<int>(actionMenu_.options.size());
|
||||
const float stepY = 0.068f;
|
||||
const float spanY = static_cast<float>(std::max(0, optCount - 1)) * stepY;
|
||||
@@ -1978,9 +1981,7 @@ void VulkanRenderer::renderView(uint32_t viewIndex, uint32_t imageIndex,
|
||||
if (projectionMode_ == ProjectionMode::Flat) {
|
||||
VkPipeline pipe = ycbcr ? ycbcrQuadPipeline_ : pipeline_;
|
||||
VkPipelineLayout layout = ycbcr ? ycbcrQuadLayout_ : pipelineLayout_;
|
||||
vkCmdBindPipeline(commandBuffer_, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe);
|
||||
vkCmdBindDescriptorSets(commandBuffer_, VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
layout, 0, 1, &videoSet, 0, nullptr);
|
||||
|
||||
const Mat4 viewMat = rigidInverse(rigidTransform(
|
||||
Quat{view.pose.orientation.x, view.pose.orientation.y,
|
||||
view.pose.orientation.z, view.pose.orientation.w},
|
||||
@@ -2004,6 +2005,45 @@ void VulkanRenderer::renderView(uint32_t viewIndex, uint32_t imageIndex,
|
||||
} else if (stereoMode_ == StereoMode::TopBottom) {
|
||||
if (videoAspect_ <= 1.2f) eyeAspect = videoAspect_ * 2.0f;
|
||||
}
|
||||
|
||||
// Cinema Screen Ambilight Glow: soft ambient lighting cast into the room
|
||||
if (ambilightIntensity_ > 0.01f) {
|
||||
VkPipeline ambPipe = ycbcr ? ycbcrAmbilightPipeline_ : ambilightPipeline_;
|
||||
if (ambPipe != VK_NULL_HANDLE) {
|
||||
vkCmdBindPipeline(commandBuffer_, VK_PIPELINE_BIND_POINT_GRAPHICS, ambPipe);
|
||||
vkCmdBindDescriptorSets(commandBuffer_, VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
layout, 0, 1, &videoSet, 0, nullptr);
|
||||
const Vec3 ambCenter{center.x + screenForward_[0] * 0.015f,
|
||||
center.y + screenForward_[1] * 0.015f,
|
||||
center.z + screenForward_[2] * 0.015f};
|
||||
const Mat4 ambModel = multiply(
|
||||
multiply(translation(ambCenter), yawRot),
|
||||
scaling(Vec3{(eyeAspect / (16.0f / 9.0f)) * 1.55f, 1.90f, 1.0f}));
|
||||
const Mat4 ambMvp = multiply(multiply(proj, viewMat), ambModel);
|
||||
PushConstants ambPush{};
|
||||
std::memcpy(ambPush.mvp, ambMvp.m, sizeof(ambPush.mvp));
|
||||
std::memcpy(ambPush.conversion, conversion_, sizeof(ambPush.conversion));
|
||||
std::memcpy(ambPush.range, range_, sizeof(ambPush.range));
|
||||
ambPush.range[3] = ambilightIntensity_;
|
||||
ambPush.stereo[0] = static_cast<float>(static_cast<int>(stereoMode_));
|
||||
ambPush.stereo[1] = static_cast<float>(viewIndex);
|
||||
ambPush.stereo[2] = swapEyes_ ? 1.0f : 0.0f;
|
||||
ambPush.stereo[3] = screenCurvature_;
|
||||
if (ycbcr) {
|
||||
ambPush.conversion[0] = drmUvScaleX_;
|
||||
ambPush.conversion[1] = drmUvScaleY_;
|
||||
}
|
||||
vkCmdPushConstants(commandBuffer_, layout,
|
||||
VK_SHADER_STAGE_VERTEX_BIT |
|
||||
VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||
0, sizeof(PushConstants), &ambPush);
|
||||
vkCmdDraw(commandBuffer_, 64 * 6, 1, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
vkCmdBindPipeline(commandBuffer_, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe);
|
||||
vkCmdBindDescriptorSets(commandBuffer_, VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
layout, 0, 1, &videoSet, 0, nullptr);
|
||||
const Mat4 model = multiply(
|
||||
multiply(translation(center), yawRot),
|
||||
scaling(Vec3{eyeAspect / (16.0f / 9.0f), 1.0f, 1.0f}));
|
||||
@@ -2212,6 +2252,7 @@ void VulkanRenderer::renderView(uint32_t viewIndex, uint32_t imageIndex,
|
||||
if (cp == 0x2122) return 136; // ™
|
||||
return '?';
|
||||
};
|
||||
const Mat4* curTextVp = &vpPanel;
|
||||
auto drawString = [&](float x, float baseline, float scale, const float col[4],
|
||||
const std::string& s) {
|
||||
float penX = x;
|
||||
@@ -2221,7 +2262,7 @@ void VulkanRenderer::renderView(uint32_t viewIndex, uint32_t imageIndex,
|
||||
const Glyph& g = fontAtlas_.glyphs[c];
|
||||
if (g.width > 0.0f && g.height > 0.0f) {
|
||||
TextPush push{};
|
||||
std::memcpy(push.mvp, vpPanel.m, sizeof(push.mvp));
|
||||
std::memcpy(push.mvp, curTextVp->m, sizeof(push.mvp));
|
||||
std::memcpy(push.color, col, sizeof(push.color));
|
||||
push.rect[0] = penX + g.bearingX * scale;
|
||||
push.rect[1] = baseline - (g.height - g.bearingY) * scale;
|
||||
@@ -2554,12 +2595,17 @@ void VulkanRenderer::renderView(uint32_t viewIndex, uint32_t imageIndex,
|
||||
drawUiElement(vpPanel, thumbX, trackY, 0.011f, 0.024f,
|
||||
UiShape::RoundedBox, thumbCol, 0.45f, 0.03f, themeAccentCol);
|
||||
|
||||
// Floating scrub hover tooltip pill
|
||||
// Floating scrub hover tooltip speech bubble
|
||||
if (pointerOnTimeline_ && transportDuration_ > 0.0) {
|
||||
const float scrubX = -trackHalfW + 2.0f * trackHalfW * pointerScrub_;
|
||||
const float tipBg[4] = {0.04f, 0.07f, 0.12f, 0.96f};
|
||||
drawUiElement(vpPanel, scrubX, trackY + 0.058f, 0.076f, 0.028f,
|
||||
UiShape::RoundedBox, tipBg, 0.35f, 0.03f, themeAccentCol);
|
||||
const float rawScrubX = -trackHalfW + 2.0f * trackHalfW * pointerScrub_;
|
||||
const float bubbleX = std::clamp(rawScrubX, -trackHalfW + 0.09f, trackHalfW - 0.09f);
|
||||
const float tipBg[4] = {0.04f, 0.07f, 0.13f, 0.98f};
|
||||
// Main bubble capsule
|
||||
drawUiElement(vpPanel, bubbleX, trackY + 0.062f, 0.086f, 0.028f,
|
||||
UiShape::RoundedBox, tipBg, 0.45f, 0.025f, themeAccentCol);
|
||||
// Stem notch connecting bubble to timeline track
|
||||
drawUiElement(vpPanel, rawScrubX, trackY + 0.032f, 0.005f, 0.008f,
|
||||
UiShape::RoundedBox, themeAccentCol, 0.30f);
|
||||
}
|
||||
|
||||
// Button hover tooltip pills
|
||||
@@ -2593,9 +2639,10 @@ void VulkanRenderer::renderView(uint32_t viewIndex, uint32_t imageIndex,
|
||||
drawFit(trackHalfW + 0.026f, trackY, 0.25f, timeSc, themeAccentCol, rightStr);
|
||||
|
||||
if (pointerOnTimeline_) {
|
||||
const float scrubX = -trackHalfW + 2.0f * trackHalfW * pointerScrub_;
|
||||
const float rawScrubX = -trackHalfW + 2.0f * trackHalfW * pointerScrub_;
|
||||
const float bubbleX = std::clamp(rawScrubX, -trackHalfW + 0.09f, trackHalfW - 0.09f);
|
||||
const std::string tipStr = format_time(pointerScrub_ * transportDuration_);
|
||||
drawFitCentered(scrubX, trackY + 0.058f, 0.14f, pillSc, themeTextCol, tipStr);
|
||||
drawFitCentered(bubbleX, trackY + 0.062f, 0.16f, pillSc * 1.05f, themeTextCol, tipStr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2886,6 +2933,18 @@ void VulkanRenderer::renderView(uint32_t viewIndex, uint32_t imageIndex,
|
||||
const float thumbBg[4] = {0.035f, 0.050f, 0.080f, 0.98f};
|
||||
const float thumbBorder[4] = {0.22f, 0.32f, 0.48f, 0.80f};
|
||||
|
||||
static auto sLastCardAnimT = std::chrono::steady_clock::now();
|
||||
const auto sNowCardT = std::chrono::steady_clock::now();
|
||||
float dtCard = std::chrono::duration<float>(sNowCardT - sLastCardAnimT).count();
|
||||
if (dtCard <= 0.0f || dtCard > 0.1f) dtCard = 0.016f;
|
||||
sLastCardAnimT = sNowCardT;
|
||||
|
||||
for (int i = 0; i < perPage && i < 12; ++i) {
|
||||
const int entryIdx = first + i;
|
||||
const float target = (entryIdx >= 0 && entryIdx < total && entryIdx == browserHovered_) ? 1.0f : 0.0f;
|
||||
cardHoverAnim_[i] += (target - cardHoverAnim_[i]) * std::clamp(14.0f * dtCard, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
for (int i = 0; i < perPage; ++i) {
|
||||
const int entryIdx = first + i;
|
||||
if (entryIdx >= total) break;
|
||||
@@ -2893,22 +2952,24 @@ void VulkanRenderer::renderView(uint32_t viewIndex, uint32_t imageIndex,
|
||||
const CardLayout lay = getCardLayout(i);
|
||||
const bool hovered = (entryIdx == browserHovered_);
|
||||
const bool selected = (entryIdx == browserSelected_);
|
||||
const float zLift = (i < 12) ? (0.012f * cardHoverAnim_[i]) : 0.0f;
|
||||
const Mat4 vpCard = (zLift > 0.0002f) ? multiply(vpPanel, translation(Vec3{0.0f, 0.0f, zLift})) : vpPanel;
|
||||
|
||||
if (hovered) {
|
||||
drawUiElement(vpPanel, lay.cx, lay.cy, lay.cardHalfW * 1.035f, lay.cardHalfH * 1.035f,
|
||||
drawUiElement(vpCard, lay.cx, lay.cy, lay.cardHalfW * 1.035f, lay.cardHalfH * 1.035f,
|
||||
UiShape::RoundedBox, glowCol, 0.08f);
|
||||
drawUiElement(vpPanel, lay.cx, lay.cy, lay.cardHalfW, lay.cardHalfH,
|
||||
drawUiElement(vpCard, lay.cx, lay.cy, lay.cardHalfW, lay.cardHalfH,
|
||||
UiShape::RoundedBox, hovCardBg, 0.06f, 0.032f, tabActiveBord);
|
||||
} else if (selected) {
|
||||
drawUiElement(vpPanel, lay.cx, lay.cy, lay.cardHalfW, lay.cardHalfH,
|
||||
drawUiElement(vpCard, lay.cx, lay.cy, lay.cardHalfW, lay.cardHalfH,
|
||||
UiShape::RoundedBox, selCardBg, 0.06f, 0.026f, tabActiveBord);
|
||||
} else {
|
||||
drawUiElement(vpPanel, lay.cx, lay.cy, lay.cardHalfW, lay.cardHalfH,
|
||||
drawUiElement(vpCard, lay.cx, lay.cy, lay.cardHalfW, lay.cardHalfH,
|
||||
UiShape::RoundedBox, normCardBg, 0.06f, 0.018f, normCardBorder);
|
||||
}
|
||||
|
||||
// Thumbnail box
|
||||
drawUiElement(vpPanel, lay.thumbCx, lay.thumbCy, lay.thumbHalfW, lay.thumbHalfH,
|
||||
drawUiElement(vpCard, lay.thumbCx, lay.thumbCy, lay.thumbHalfW, lay.thumbHalfH,
|
||||
UiShape::RoundedBox, thumbBg, 0.05f, 0.018f, thumbBorder);
|
||||
|
||||
// Folder tab visual cue for directory cards
|
||||
@@ -2918,7 +2979,7 @@ void VulkanRenderer::renderView(uint32_t viewIndex, uint32_t imageIndex,
|
||||
const float tabHalfH = 0.007f;
|
||||
const float tabCx = lay.thumbCx - lay.thumbHalfW + tabHalfW + 0.010f;
|
||||
const float tabCy = lay.thumbCy + lay.thumbHalfH + 0.005f;
|
||||
drawUiElement(vpPanel, tabCx, tabCy, tabHalfW, tabHalfH,
|
||||
drawUiElement(vpCard, tabCx, tabCy, tabHalfW, tabHalfH,
|
||||
UiShape::RoundedBox, thumbBg, 0.40f, 0.020f, thumbBorder);
|
||||
}
|
||||
}
|
||||
@@ -2931,7 +2992,7 @@ void VulkanRenderer::renderView(uint32_t viewIndex, uint32_t imageIndex,
|
||||
else if (item.isVideo) iconShape = UiShape::Play;
|
||||
const float iconSize = (browserViewMode_ == BrowserViewMode::CompactList) ? 0.020f :
|
||||
(browserViewMode_ == BrowserViewMode::MiniCards) ? 0.026f : 0.032f;
|
||||
drawUiElement(vpPanel, lay.thumbCx, lay.thumbCy, iconSize, iconSize, iconShape, themeAccentCol);
|
||||
drawUiElement(vpCard, lay.thumbCx, lay.thumbCy, iconSize, iconSize, iconShape, themeAccentCol);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2948,8 +3009,10 @@ void VulkanRenderer::renderView(uint32_t viewIndex, uint32_t imageIndex,
|
||||
vkCmdBindDescriptorSets(commandBuffer_, VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
textPipelineLayout_, 0, 1, &cardThumbs_[i].set, 0, nullptr);
|
||||
const CardLayout lay = getCardLayout(i);
|
||||
const float zLift = (i < 12) ? (0.012f * cardHoverAnim_[i]) : 0.0f;
|
||||
const Mat4 vpCard = (zLift > 0.0002f) ? multiply(vpPanel, translation(Vec3{0.0f, 0.0f, zLift})) : vpPanel;
|
||||
TextPush push{};
|
||||
std::memcpy(push.mvp, vpPanel.m, sizeof(push.mvp));
|
||||
std::memcpy(push.mvp, vpCard.m, sizeof(push.mvp));
|
||||
const float white4[4] = {1.0f, 1.0f, 1.0f, 1.0f};
|
||||
std::memcpy(push.color, white4, sizeof(push.color));
|
||||
push.rect[0] = lay.thumbCx - lay.thumbHalfW;
|
||||
@@ -2979,24 +3042,26 @@ void VulkanRenderer::renderView(uint32_t viewIndex, uint32_t imageIndex,
|
||||
if (entryIdx >= total) break;
|
||||
const auto& item = browserItems_[static_cast<std::size_t>(entryIdx)];
|
||||
const CardLayout lay = getCardLayout(i);
|
||||
const float zLift = (i < 12) ? (0.012f * cardHoverAnim_[i]) : 0.0f;
|
||||
const Mat4 vpCard = (zLift > 0.0002f) ? multiply(vpPanel, translation(Vec3{0.0f, 0.0f, zLift})) : vpPanel;
|
||||
|
||||
if (browserViewMode_ == BrowserViewMode::BigCards) {
|
||||
drawUiElement(vpPanel, lay.cx - lay.thumbHalfW + 0.062f, lay.thumbCy + lay.thumbHalfH - 0.024f,
|
||||
drawUiElement(vpCard, lay.cx - lay.thumbHalfW + 0.062f, lay.thumbCy + lay.thumbHalfH - 0.024f,
|
||||
0.056f, 0.018f, UiShape::RoundedBox, badgeBg, 0.45f, 0.03f, tabActiveBord);
|
||||
if (!item.durationTag.empty()) {
|
||||
drawUiElement(vpPanel, lay.cx + lay.thumbHalfW - 0.050f, lay.thumbCy - lay.thumbHalfH + 0.024f,
|
||||
drawUiElement(vpCard, lay.cx + lay.thumbHalfW - 0.050f, lay.thumbCy - lay.thumbHalfH + 0.024f,
|
||||
0.044f, 0.017f, UiShape::RoundedBox, durBg, 0.40f, 0.02f, normCardBorder);
|
||||
}
|
||||
} else if (browserViewMode_ == BrowserViewMode::MiniCards) {
|
||||
drawUiElement(vpPanel, lay.cx - lay.thumbHalfW + 0.046f, lay.thumbCy + lay.thumbHalfH - 0.018f,
|
||||
drawUiElement(vpCard, lay.cx - lay.thumbHalfW + 0.046f, lay.thumbCy + lay.thumbHalfH - 0.018f,
|
||||
0.042f, 0.014f, UiShape::RoundedBox, badgeBg, 0.45f, 0.025f, tabActiveBord);
|
||||
if (!item.durationTag.empty()) {
|
||||
drawUiElement(vpPanel, lay.cx + lay.thumbHalfW - 0.038f, lay.thumbCy - lay.thumbHalfH + 0.018f,
|
||||
drawUiElement(vpCard, lay.cx + lay.thumbHalfW - 0.038f, lay.thumbCy - lay.thumbHalfH + 0.018f,
|
||||
0.034f, 0.014f, UiShape::RoundedBox, durBg, 0.40f, 0.02f, normCardBorder);
|
||||
}
|
||||
} else { // CompactList
|
||||
if (listShowFormat_) {
|
||||
drawUiElement(vpPanel, listCols.fmtCx, lay.cy,
|
||||
drawUiElement(vpCard, listCols.fmtCx, lay.cy,
|
||||
0.055f, 0.016f, UiShape::RoundedBox, badgeBg, 0.45f, 0.025f, tabActiveBord);
|
||||
}
|
||||
}
|
||||
@@ -3107,6 +3172,9 @@ void VulkanRenderer::renderView(uint32_t viewIndex, uint32_t imageIndex,
|
||||
const CardLayout lay = getCardLayout(i);
|
||||
const std::string cardDet = item.detail.empty() ? (item.isDir ? "Folder" : "Media") : item.detail;
|
||||
const std::string& bTag = item.tag.empty() ? (item.isVideo ? "VIDEO" : "FILE") : item.tag;
|
||||
const float zLift = (i < 12) ? (0.012f * cardHoverAnim_[i]) : 0.0f;
|
||||
const Mat4 vpCard = (zLift > 0.0002f) ? multiply(vpPanel, translation(Vec3{0.0f, 0.0f, zLift})) : vpPanel;
|
||||
curTextVp = &vpCard;
|
||||
|
||||
if (browserViewMode_ == BrowserViewMode::BigCards) {
|
||||
const float textLeft = lay.cx - lay.cardHalfW + 0.016f;
|
||||
@@ -3149,6 +3217,7 @@ void VulkanRenderer::renderView(uint32_t viewIndex, uint32_t imageIndex,
|
||||
drawFitCentered(listCols.durCx, lay.cy, 0.11f, scDetail * 0.95f, slateCol, durStr);
|
||||
}
|
||||
}
|
||||
curTextVp = &vpPanel;
|
||||
}
|
||||
|
||||
// Bottom status bar
|
||||
@@ -3603,7 +3672,7 @@ void VulkanRenderer::renderView(uint32_t viewIndex, uint32_t imageIndex,
|
||||
themeAccentCol[0], themeAccentCol[1], themeAccentCol[2], 1.0f};
|
||||
const float slateCol[4] = {0.60f, 0.70f, 0.84f, 1.0f};
|
||||
|
||||
if (actionMenu_.page == 0 || actionMenu_.page == 1) {
|
||||
if (actionMenu_.page == 0 || actionMenu_.page == 1 || actionMenu_.page == 5) {
|
||||
const float btnHalfW = 0.38f;
|
||||
const float btnHalfH = 0.028f;
|
||||
for (int i = 0; i < menuOptCount; ++i) {
|
||||
@@ -3695,7 +3764,7 @@ void VulkanRenderer::renderView(uint32_t viewIndex, uint32_t imageIndex,
|
||||
drawFitCentered(0.0f, mHalfH - 0.090f, mHalfW * 1.8f, sc * 0.85f, slateCol, actionMenu_.subtitle);
|
||||
}
|
||||
|
||||
if (actionMenu_.page == 0 || actionMenu_.page == 1) {
|
||||
if (actionMenu_.page == 0 || actionMenu_.page == 1 || actionMenu_.page == 5) {
|
||||
for (int i = 0; i < menuOptCount; ++i) {
|
||||
const float cy = menuStartY - static_cast<float>(i) * menuStepY;
|
||||
const bool hov = (actionMenuBtnHovered_ == i) || (actionMenu_.selectedOption == i);
|
||||
@@ -4559,6 +4628,8 @@ bool VulkanRenderer::createYcbcrPipeline(uint32_t fourcc, int colorspace,
|
||||
|
||||
if (!buildPipeline("quad.vert.spv", "video_quad.frag.spv", ycbcrQuadLayout_,
|
||||
ycbcrQuadPipeline_) ||
|
||||
!buildPipeline("quad.vert.spv", "ambilight.frag.spv", ycbcrQuadLayout_,
|
||||
ycbcrAmbilightPipeline_, true) ||
|
||||
!buildPipeline("sphere.vert.spv", "video_sphere.frag.spv",
|
||||
ycbcrSphereLayout_, ycbcrSpherePipeline_)) {
|
||||
return false;
|
||||
@@ -4592,6 +4663,10 @@ void VulkanRenderer::destroyYcbcr() {
|
||||
vkDestroyPipeline(device_, ycbcrQuadPipeline_, nullptr);
|
||||
ycbcrQuadPipeline_ = VK_NULL_HANDLE;
|
||||
}
|
||||
if (ycbcrAmbilightPipeline_ != VK_NULL_HANDLE) {
|
||||
vkDestroyPipeline(device_, ycbcrAmbilightPipeline_, nullptr);
|
||||
ycbcrAmbilightPipeline_ = VK_NULL_HANDLE;
|
||||
}
|
||||
if (ycbcrSpherePipeline_ != VK_NULL_HANDLE) {
|
||||
vkDestroyPipeline(device_, ycbcrSpherePipeline_, nullptr);
|
||||
ycbcrSpherePipeline_ = VK_NULL_HANDLE;
|
||||
|
||||
@@ -172,6 +172,10 @@ public:
|
||||
uiAccentColorIdx_ = accentColorIdx;
|
||||
uiDistance_ = uiDistance;
|
||||
}
|
||||
void setAmbilightIntensity(float intensity) {
|
||||
ambilightIntensity_ = intensity < 0.0f ? 0.0f : (intensity > 1.0f ? 1.0f : intensity);
|
||||
}
|
||||
float ambilightIntensity() const { return ambilightIntensity_; }
|
||||
|
||||
enum class BrowserViewMode : int {
|
||||
BigCards = 0, // 3x2, 6 per page (Large cards with full 16:9 thumbnail)
|
||||
@@ -445,11 +449,14 @@ private:
|
||||
VkRenderPass renderPass_ = VK_NULL_HANDLE;
|
||||
VkPipelineLayout pipelineLayout_ = VK_NULL_HANDLE;
|
||||
VkPipeline pipeline_ = VK_NULL_HANDLE;
|
||||
VkPipeline ambilightPipeline_ = VK_NULL_HANDLE;
|
||||
VkPipelineLayout spherePipelineLayout_ = VK_NULL_HANDLE;
|
||||
VkPipeline spherePipeline_ = VK_NULL_HANDLE;
|
||||
VkPipelineLayout uiPipelineLayout_ = VK_NULL_HANDLE;
|
||||
VkPipeline uiPipeline_ = VK_NULL_HANDLE;
|
||||
|
||||
float ambilightIntensity_ = 0.55f;
|
||||
|
||||
ProjectionMode projectionMode_ = ProjectionMode::Flat;
|
||||
StereoMode stereoMode_ = StereoMode::Mono;
|
||||
bool swapEyes_ = false;
|
||||
@@ -518,6 +525,7 @@ private:
|
||||
bool ready = false;
|
||||
};
|
||||
CardThumbnail cardThumbs_[12];
|
||||
float cardHoverAnim_[12]{};
|
||||
VkPipeline thumbPipeline_ = VK_NULL_HANDLE;
|
||||
VkDescriptorPool thumbPool_ = VK_NULL_HANDLE;
|
||||
BrowserViewMode browserViewMode_ = BrowserViewMode::BigCards;
|
||||
@@ -652,6 +660,7 @@ private:
|
||||
VkDescriptorSet ycbcrSet_ = VK_NULL_HANDLE;
|
||||
VkPipelineLayout ycbcrQuadLayout_ = VK_NULL_HANDLE;
|
||||
VkPipeline ycbcrQuadPipeline_ = VK_NULL_HANDLE;
|
||||
VkPipeline ycbcrAmbilightPipeline_ = VK_NULL_HANDLE;
|
||||
VkPipelineLayout ycbcrSphereLayout_ = VK_NULL_HANDLE;
|
||||
VkPipeline ycbcrSpherePipeline_ = VK_NULL_HANDLE;
|
||||
VkFormat ycbcrFormat_ = VK_FORMAT_UNDEFINED;
|
||||
|
||||
@@ -61,6 +61,10 @@ const char* kHoverHapticsStrengthNames[] = {
|
||||
const float kHoverHapticsStrengthValues[] = {0.08f, 0.15f, 0.28f, 0.50f};
|
||||
constexpr int kHoverHapticsStrengthCount = 4;
|
||||
|
||||
const char* kAmbilightNames[] = {"Off", "Subtle", "Medium", "Strong"};
|
||||
const float kAmbilightValues[] = {0.0f, 0.30f, 0.55f, 0.85f};
|
||||
constexpr int kAmbilightCount = 4;
|
||||
|
||||
std::string getConfigPath() {
|
||||
#ifdef _WIN32
|
||||
const char* appData = std::getenv("APPDATA");
|
||||
@@ -129,6 +133,8 @@ void SettingsMenu::refresh() {
|
||||
Action::CycleStereo});
|
||||
rows_.push_back({"Cinema Screen Curve", kCurvatureNames[curvatureIdx_ % kCurvatureCount],
|
||||
Action::CycleCurvature});
|
||||
rows_.push_back({"Ambient Screen Glow", kAmbilightNames[ambilightIdx_ % kAmbilightCount],
|
||||
Action::CycleAmbilight, projectionMode_ == 0});
|
||||
rows_.push_back({"Screen Distance", distStream.str(),
|
||||
Action::CycleDistance});
|
||||
rows_.push_back({"Swap Left/Right Eye", swapEyes_ ? "On" : "Off",
|
||||
@@ -396,6 +402,11 @@ SettingsMenu::Action SettingsMenu::activate() {
|
||||
saveConfig();
|
||||
refresh();
|
||||
break;
|
||||
case Action::CycleAmbilight:
|
||||
ambilightIdx_ = (ambilightIdx_ + 1) % kAmbilightCount;
|
||||
saveConfig();
|
||||
refresh();
|
||||
break;
|
||||
case Action::ListenQuickPath:
|
||||
listeningFunc_ = 4;
|
||||
status_ = "Press a controller button for Bookmarks shortcut";
|
||||
@@ -584,6 +595,10 @@ const char* SettingsMenu::hoverHapticsStrengthStr() const {
|
||||
return kHoverHapticsStrengthNames[hoverHapticsStrengthIdx_ % kHoverHapticsStrengthCount];
|
||||
}
|
||||
|
||||
float SettingsMenu::ambilightIntensity() const {
|
||||
return kAmbilightValues[ambilightIdx_ % kAmbilightCount];
|
||||
}
|
||||
|
||||
void SettingsMenu::loadConfig() {
|
||||
const std::string path = getConfigPath();
|
||||
std::ifstream file(path);
|
||||
@@ -627,6 +642,7 @@ void SettingsMenu::loadConfig() {
|
||||
else if (key == "quick_path") quickPath_ = val;
|
||||
else if (key == "hover_haptics") hoverHapticsEnabled_ = (val == "1" || val == "true");
|
||||
else if (key == "hover_haptics_strength") hoverHapticsStrengthIdx_ = std::clamp(std::stoi(val), 0, kHoverHapticsStrengthCount - 1);
|
||||
else if (key == "ambilight") ambilightIdx_ = std::clamp(std::stoi(val), 0, kAmbilightCount - 1);
|
||||
}
|
||||
PIXELVR_LOG_INFO("Loaded settings from %s", path.c_str());
|
||||
}
|
||||
@@ -665,6 +681,7 @@ void SettingsMenu::saveConfig() const {
|
||||
file << "list_show_dur=" << (listShowDuration_ ? 1 : 0) << "\n";
|
||||
file << "hover_haptics=" << (hoverHapticsEnabled_ ? 1 : 0) << "\n";
|
||||
file << "hover_haptics_strength=" << hoverHapticsStrengthIdx_ << "\n";
|
||||
file << "ambilight=" << ambilightIdx_ << "\n";
|
||||
if (!quickPath_.empty()) {
|
||||
file << "quick_path=" << quickPath_ << "\n";
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ public:
|
||||
ToggleListColDuration,
|
||||
ToggleHoverHaptics,
|
||||
CycleHoverHapticsStrength,
|
||||
CycleAmbilight,
|
||||
BackToUI,
|
||||
Close
|
||||
};
|
||||
@@ -145,6 +146,10 @@ public:
|
||||
float hoverHapticsStrength() const;
|
||||
const char* hoverHapticsStrengthStr() const;
|
||||
|
||||
// Ambient screen glow ("ambilight") behind the flat cinema screen.
|
||||
int ambilightIdx() const { return ambilightIdx_; }
|
||||
float ambilightIntensity() const; // 0 = off
|
||||
|
||||
void onKeyboardSubmit(const std::string& text) { (void)text; }
|
||||
void onKeyboardCancel() {}
|
||||
|
||||
@@ -183,6 +188,7 @@ private:
|
||||
std::string quickPath_;
|
||||
bool hoverHapticsEnabled_ = true;
|
||||
int hoverHapticsStrengthIdx_ = 1;
|
||||
int ambilightIdx_ = 2; // 0=Off, 1=Subtle, 2=Medium (default), 3=Strong
|
||||
|
||||
int section_ = 0; // 0=Display, 1=UI, 2=Audio, 3=Controls, 4=Vault; 10..12=sub-lists
|
||||
std::vector<std::pair<int, std::string>> videoStreams_; // (index, label)
|
||||
|
||||
Reference in new issue
Block a user