fix(dock): correct texture orientation and separate hold feedback timing

This commit is contained in:
toorux committed 2026-10-07 11:15:52 +08:00
1 parent de65f8dbef
commit 550ca817e5
3 files changed
+31 -11

No files matched your search

+8 -4
View File
@@ -10,7 +10,7 @@
struct EntryHold {
using Clock=std::chrono::steady_clock;
enum class Action { Idle, Launcher, QuickPanel };
static constexpr auto duration=std::chrono::milliseconds(600);
static constexpr auto quick_panel_delay=std::chrono::milliseconds(600);
static constexpr auto ring_delay=std::chrono::milliseconds(100);
bool pressed=false,fired=false;
unsigned device=~0u,button=0;
@@ -22,7 +22,7 @@ struct EntryHold {
if(inside)last_inside=now;
// Brief pointer jitter is tolerated, but leaving cannot activate a hold.
if(!inside){if(now-last_inside>std::chrono::milliseconds(150))cancel();return Action::Idle;}
if(!fired&&now-started>=duration){fired=true;return Action::QuickPanel;}
if(!fired&&now-started>=quick_panel_delay){fired=true;return Action::QuickPanel;}
return Action::Idle;
}
Action event(bool down,unsigned source,unsigned mouse_button,bool available,bool inside,Clock::time_point now){
@@ -30,14 +30,18 @@ struct EntryHold {
if(!pressed&&available&&inside){pressed=true;fired=false;device=source;button=mouse_button;started=last_inside=now;}
return Action::Idle;
}
if(!pressed||source!=device||mouse_button!=button)return Action::Idle;
// Steam may omit the device on release; known other devices still cannot release this hold.
if(!pressed||(source!=device&&source!=~0u&&device!=~0u)||mouse_button!=button)return Action::Idle;
auto action=tick(now,available,inside);
if(action==Action::Idle&&pressed&&!fired&&available&&inside)action=Action::Launcher;
cancel();return action;
}
float progress(Clock::time_point now)const{
if(!pressed||now-started<ring_delay)return 0.f;
return std::clamp(std::chrono::duration<float>(now-started).count()/.6f,0.f,1.f);
const float elapsed=std::chrono::duration<float>(now-started-ring_delay).count();
const float remaining=std::chrono::duration<float>(quick_panel_delay-ring_delay).count();
// A tiny nonzero value reveals the track at 100ms without jumping to 1/6 full.
return std::clamp(elapsed/remaining,.001f,1.f);
}
};
+7 -5
View File
@@ -172,7 +172,7 @@ static std::vector<uint8_t> dock_icon(bool hover,bool active,float progress=0){
// Keep texture storage alive while OpenVR samples the preceding frame. Raw
// overlay replacement during a hold can disturb the image and hover target.
struct DockIconTexture {
GLuint textures[2]{};int slot=0;
GLuint textures[2]{};int slot=0;std::vector<uint8_t> upload;
void init(){
glGenTextures(2,textures);
for(auto texture:textures){
@@ -187,7 +187,9 @@ struct DockIconTexture {
const int next=1-slot;glBindTexture(GL_TEXTURE_2D,textures[next]);
glPixelStorei(GL_UNPACK_ALIGNMENT,4);glPixelStorei(GL_UNPACK_ROW_LENGTH,0);
glPixelStorei(GL_UNPACK_SKIP_PIXELS,0);glPixelStorei(GL_UNPACK_SKIP_ROWS,0);
glTexSubImage2D(GL_TEXTURE_2D,0,0,0,dock_canvas_width,dock_canvas_height,GL_RGBA,GL_UNSIGNED_BYTE,pixels.data());
// Raster icons use top-left rows; OpenVR OpenGL textures use bottom-left.
merge_paint(upload,pixels.data(),dock_canvas_width,dock_canvas_height,0,static_cast<const cef_rect_t*>(nullptr),true);
glTexSubImage2D(GL_TEXTURE_2D,0,0,0,dock_canvas_width,dock_canvas_height,GL_RGBA,GL_UNSIGNED_BYTE,upload.data());
glFinish();vr::Texture_t texture{reinterpret_cast<void*>(uintptr_t(textures[next])),vr::TextureType_OpenGL,vr::ColorSpace_Auto};
const auto error=overlays->SetOverlayTexture(overlay,&texture);glFlush();
if(error){std::cerr<<"Dock texture submission failed "<<error<<"\n";return false;}
@@ -616,12 +618,12 @@ int main(int argc,char** argv){
if(event.eventType==vr::VREvent_MouseMove||event.eventType==vr::VREvent_MouseButtonDown||event.eventType==vr::VREvent_MouseButtonUp)entry_pointer.move(event.trackedDeviceIndex,event.data.mouse.x,event.data.mouse.y);
if(event.eventType==vr::VREvent_FocusLeave)entry_pointer.leave();
if(event.eventType==vr::VREvent_MouseButtonDown||event.eventType==vr::VREvent_MouseButtonUp){
activate_entry(entry_hold.event(event.eventType==vr::VREvent_MouseButtonDown,event.trackedDeviceIndex,event.data.mouse.button,anchor_ok&&(event.trackedDeviceIndex==vr::k_unTrackedDeviceIndexInvalid||vr_system->IsTrackedDeviceConnected(event.trackedDeviceIndex)),entry_pointer.inside,now));
activate_entry(entry_hold.event(event.eventType==vr::VREvent_MouseButtonDown,event.trackedDeviceIndex,event.data.mouse.button,anchor_ok&&(event.trackedDeviceIndex==vr::k_unTrackedDeviceIndexInvalid||vr_system->IsTrackedDeviceConnected(event.trackedDeviceIndex)),entry_pointer.inside,std::chrono::steady_clock::now()));
}
}
if(!anchor_ok)entry_pointer.leave();
const bool entry_connected=entry_hold.device==vr::k_unTrackedDeviceIndexInvalid||vr_system->IsTrackedDeviceConnected(entry_hold.device);
activate_entry(entry_hold.tick(now,anchor_ok&&entry_connected,entry_pointer.inside&&entry_pointer.device==entry_hold.device));
activate_entry(entry_hold.tick(std::chrono::steady_clock::now(),anchor_ok&&entry_connected,entry_pointer.inside&&entry_pointer.device==entry_hold.device));
for(auto&[key,v]:views)v->stick_scrolling=false;
entry_actions(button,anchor_ok);
if(launcher_open){bool held=false;for(auto& hand:entry_buttons)for(auto& trigger:hand)held|=trigger.held;launcher_dismiss.observe(held);}
@@ -690,7 +692,7 @@ int main(int argc,char** argv){
close_keyboard(false);
}
for(auto i=views.begin();i!=views.end();){auto& v=*i->second;if(v.closed){overlays->ClearOverlayTexture(v.overlay);overlays->DestroyOverlay(v.overlay);if(v.thumbnail)overlays->DestroyOverlay(v.thumbnail);glDeleteTextures(2,v.textures);i=views.erase(i);}else ++i;}
bool hovered=anchor_ok&&(entry_hold.pressed||overlays->IsHoverTargetOverlay(button));if(hovered&&!icon_hover)hover_haptic(button,button_haptic_at);const auto badge=notification_badge_count.load();const int progress=std::lround(entry_hold.progress(now)*120);if(icon_progress!=progress||icon_active!=(menu_open||launcher_open)||icon_hover!=hovered||icon_badge!=badge){pixels=dock_icon(hovered,menu_open||launcher_open,progress/120.f);if(icon_texture.submit(button,pixels)){icon_progress=progress;icon_badge=badge;icon_active=menu_open||launcher_open;}}icon_hover=hovered;
bool hovered=anchor_ok&&(entry_hold.pressed||overlays->IsHoverTargetOverlay(button));if(hovered&&!icon_hover)hover_haptic(button,button_haptic_at);const auto badge=notification_badge_count.load();const float ring=entry_hold.progress(std::chrono::steady_clock::now());const int progress=ring>0?std::max(1,int(std::lround(ring*120))):0;if(icon_progress!=progress||icon_active!=(menu_open||launcher_open)||icon_hover!=hovered||icon_badge!=badge){pixels=dock_icon(hovered,menu_open||launcher_open,progress/120.f);if(icon_texture.submit(button,pixels)){icon_progress=progress;icon_badge=badge;icon_active=menu_open||launcher_open;}}icon_hover=hovered;
auto sync=overlays->WaitFrameSync(8);
// A timeout already waited: never append another sleep to it. Bound retries
// only for immediate API failures, so a broken sync call cannot busy-spin.
+16 -2
View File
@@ -1,4 +1,5 @@
#include "entry_hold.h"
#include "paint_buffer.h"
#include <cassert>
#include <iostream>
@@ -16,9 +17,11 @@ int main(){
EntryHold hold;
hold.event(true,1,1,true,true,start);
assert(hold.progress(start+milliseconds(99))==0);
assert(std::abs(hold.progress(start+milliseconds(100))-1.f/6)<.001f);
assert(std::abs(hold.progress(start+milliseconds(300))-.5f)<.001f);
assert(hold.tick(start+milliseconds(100),true,true)==A::Idle);
assert(hold.progress(start+milliseconds(100))>0&&hold.progress(start+milliseconds(100))<.01f);
assert(std::abs(hold.progress(start+milliseconds(300))-.4f)<.001f);
assert(hold.tick(start+milliseconds(599),true,true)==A::Idle);
assert(hold.progress(start+milliseconds(599))<1);
assert(hold.tick(start+milliseconds(600),true,true)==A::QuickPanel);
assert(hold.tick(start+milliseconds(900),true,true)==A::Idle);
assert(hold.event(false,1,1,true,true,start+seconds(1))==A::Idle);
@@ -30,6 +33,10 @@ int main(){
assert(hold.event(false,2,1,true,true,start+milliseconds(100))==A::Idle);
assert(hold.event(false,1,2,true,true,start+milliseconds(110))==A::Idle);
assert(hold.event(false,1,1,true,true,start+milliseconds(120))==A::Launcher);
// A release with no tracked device must not leave an old progress ring held.
hold.event(true,1,1,true,true,start);
assert(hold.event(false,~0u,1,true,true,start+milliseconds(80))==A::Launcher);
assert(!hold.pressed&&hold.progress(start+milliseconds(100))==0);
// Small jitter does not restart the timer; a sustained leave or loss cancels.
hold.event(true,1,1,true,true,start);
hold.tick(start+milliseconds(480),true,true);
@@ -63,6 +70,13 @@ int main(){
assert(pixels[top]>original[top]&&pixels[top]<255);
assert(pixels[top]==pixels[top+1]&&pixels[top+1]==pixels[top+2]);
assert(pixels[(8*128+64)*4]>original[(8*128+64)*4]);
// The GPU upload must flip top-left artwork exactly once, including the ring.
struct Rect{int x,y,width,height;};
std::vector<uint8_t> upload;
merge_paint(upload,pixels.data(),128,128,0,static_cast<const Rect*>(nullptr),true);
assert(upload[((127-5)*128+64)*4]==pixels[top]);
assert(upload[((127-8)*128+64)*4]==pixels[(8*128+64)*4]);
assert(pixels[(5*128+64)*4]>pixels[(5*128+63)*4]);
auto partial=pixels;entry_progress_ring(pixels,1);
assert(pixels!=partial);
std::cout<<"entry_hold: passed\n";