mirror of
https://github.com/baketnk/frame-yap.git
synced 2026-10-06 01:00:04 +02:00
Polish FrameYap panel with rounded neon controls
This commit is contained in:
1 parent
04b404a653
commit
18a66b021c
4 files changed
+57
-32
No files matched your search
@@ -13,8 +13,12 @@ past observations; it never proves current device availability or grants a live
|
||||
placeholder installer as functional or conflate an OpenVR app key with a store ID.
|
||||
- Default builds and tests are offline and hardware-free. No implicit package/model
|
||||
downloads, microphone recording, input injection or OpenVR initialization.
|
||||
- Hardware tests must be opt-in and distinguish API discovery from delivered input,
|
||||
transcription quality, performance and human headset acceptance.
|
||||
- For changes intended for Frame, a local build is not completion: after offline
|
||||
checks, upload/install the tested native build on the Frame test device and verify
|
||||
the deployed version when device access is authorized. If unavailable, report
|
||||
explicitly that the change is local-only; do not imply the headset was updated.
|
||||
- Live hardware tests remain opt-in and must distinguish API discovery from delivered
|
||||
input, transcription quality, performance and human headset acceptance.
|
||||
- Preserve existing SSH and user sessions; never terminate SSH/session processes or
|
||||
use broad cleanup/restart commands. Stop only processes this project owns.
|
||||
- Keep audio, transcripts, model weights, credentials and private logs out of Git.
|
||||
|
||||
+5
-2
@@ -19,8 +19,11 @@ CJK coverage is claimed).
|
||||
`src/panel_surface.*` renders **one 1000×680 RGBA canvas** for review, settings,
|
||||
status and controls. One OpenVR handle receives it with `SetOverlayRaw`; tabs do
|
||||
not create extra overlays or render targets. The rounded mint-to-blue perimeter,
|
||||
shallow curved accent, and dark cards borrow kouseki's VR visual language, with
|
||||
an independently implemented CPU renderer and a baked halo rather than GPU bloom.
|
||||
shallow curved accent, and dark cards borrow kouseki's VR visual language. Rounded
|
||||
preview, status and control surfaces use independently rasterized antialiased edges
|
||||
and restrained baked neon halos rather than GPU bloom. The recording indicator and
|
||||
selected controls remain distinguishable by their labels, not color alone. Rounded
|
||||
control hit areas exclude their clipped corners.
|
||||
Rendering/uploads occur only for changed content, page, settings, or pointer
|
||||
feedback; static frames are reused. The caller may call `draw(Panel)` at 10 ms
|
||||
intervals. Tracking transforms do not require repainting the canvas.
|
||||
|
||||
+44
-28
@@ -15,10 +15,17 @@ using Color = std::array<unsigned char, 4>;
|
||||
constexpr Color background{12, 16, 27, 255}, card{20, 28, 43, 255};
|
||||
constexpr Color ink{230, 240, 249, 255}, muted{151, 173, 193, 255};
|
||||
constexpr Color cyan{31, 240, 164, 255}, pink{255, 110, 135, 255};
|
||||
float rounded_distance(float px, float py, int x, int y, int w, int h, float radius) {
|
||||
const float dx = std::abs(px - (x + w / 2.f)) - (w / 2.f - radius);
|
||||
const float dy = std::abs(py - (y + h / 2.f)) - (h / 2.f - radius);
|
||||
return std::hypot(std::max(dx, 0.f), std::max(dy, 0.f)) +
|
||||
std::min(std::max(dx, dy), 0.f) - radius;
|
||||
}
|
||||
struct Rect {
|
||||
int x, y, w, h;
|
||||
bool contains(float px, float py) const {
|
||||
return std::isfinite(px) && std::isfinite(py) && px >= x && px < x + w && py >= y && py < y + h;
|
||||
return std::isfinite(px) && std::isfinite(py) && px >= x && px < x + w && py >= y && py < y + h &&
|
||||
rounded_distance(px, py, x, y, w, h, std::min(16, h / 3)) <= 0.f;
|
||||
}
|
||||
};
|
||||
enum class Control { Review, Settings, Prev, Next, Record, Cancel, Insert, Enter, Quit,
|
||||
@@ -117,28 +124,33 @@ struct PanelSurface::Impl {
|
||||
for (int x = std::max(r.x, 0); x < std::min(W, r.x + r.w); ++x)
|
||||
std::copy(c.begin(), c.end(), pixels.begin() + (size_t(y) * W + x) * 4);
|
||||
}
|
||||
void border(Rect r, Color c, int thickness = 1) {
|
||||
rect({r.x, r.y, r.w, thickness}, c); rect({r.x, r.y + r.h - thickness, r.w, thickness}, c);
|
||||
rect({r.x, r.y, thickness, r.h}, c); rect({r.x + r.w - thickness, r.y, thickness, r.h}, c);
|
||||
void blend(int x, int y, Color color, float amount) {
|
||||
auto* dst = pixels.data() + (size_t(y) * W + x) * 4;
|
||||
for (int k = 0; k < 3; ++k)
|
||||
dst[k] = static_cast<unsigned char>(dst[k] * (1.f - amount) + color[k] * amount);
|
||||
}
|
||||
void neon(Rect r, Color c) {
|
||||
// Baked halo + bright core: no extra overlay, mesh, bloom pass or animation.
|
||||
for (int spread = 5; spread >= 1; --spread) {
|
||||
Color halo = background;
|
||||
for (int k = 0; k < 3; ++k) halo[k] += (c[k] - halo[k]) / (spread * 3 + 2);
|
||||
border({r.x - spread, r.y - spread, r.w + spread * 2, r.h + spread * 2}, halo);
|
||||
}
|
||||
border(r, c, 2);
|
||||
void rounded(Rect r, int radius, Color fill, Color edge, float glow = 0.f, int stroke = 1) {
|
||||
// Distance-field antialiasing and restrained baked glow. No second texture or GPU pass.
|
||||
constexpr int halo = 10;
|
||||
for (int y = std::max(0, r.y - halo); y < std::min(H, r.y + r.h + halo); ++y)
|
||||
for (int x = std::max(0, r.x - halo); x < std::min(W, r.x + r.w + halo); ++x) {
|
||||
const float d = rounded_distance(x + .5f, y + .5f, r.x, r.y, r.w, r.h, radius);
|
||||
if (glow > 0.f && d > 0.f && d < halo) {
|
||||
const float falloff = 1.f - d / halo;
|
||||
blend(x, y, edge, glow * falloff * falloff);
|
||||
}
|
||||
if (d < .5f) blend(x, y, fill, std::clamp(.5f - d, 0.f, 1.f));
|
||||
if (d > -stroke - .5f && d < .5f)
|
||||
blend(x, y, edge, std::clamp(d + stroke + .5f, 0.f, 1.f) *
|
||||
std::clamp(.5f - d, 0.f, 1.f));
|
||||
}
|
||||
}
|
||||
void frame() {
|
||||
// Independently rasterized version of kouseki's HUD visual language:
|
||||
// rounded mint-to-blue perimeter and a second shallow curved accent.
|
||||
for (int y = 0; y < H; ++y) for (int x = 0; x < W; ++x) {
|
||||
if (x > 34 && x < W - 34 && y > 34 && y < H - 34) continue;
|
||||
const float qx = std::abs(x - W / 2.f) - (W / 2.f - 28.f);
|
||||
const float qy = std::abs(y - H / 2.f) - (H / 2.f - 28.f);
|
||||
const float distance = std::hypot(std::max(qx, 0.f), std::max(qy, 0.f)) +
|
||||
std::min(std::max(qx, qy), 0.f) - 18.f;
|
||||
const float distance = rounded_distance(x, y, 10, 10, W - 20, H - 20, 18.f);
|
||||
const float edge = std::abs(distance);
|
||||
const float strength = edge <= 1.f ? 1.f : .30f * std::max(0.f, 1.f - (edge - 1.f) / 6.f);
|
||||
const float t = float(x) / W;
|
||||
@@ -252,14 +264,16 @@ struct PanelSurface::Impl {
|
||||
text("FrameYap", 32, 61, 40, ink, 300);
|
||||
text("ON-DEVICE / REVIEW FIRST", 280, 58, 22, muted, 720);
|
||||
text(mount_label(mount), 756, 58, 24, cyan, 968);
|
||||
rect({32, 84, 7, 34}, panel.recording ? pink : cyan);
|
||||
rounded({32, 78, 936, 48}, 13, {18, 31, 44, 255},
|
||||
panel.recording ? Color{115, 64, 83, 255} : Color{45, 82, 98, 255},
|
||||
panel.recording ? .16f : 0.f);
|
||||
rounded({46, 95, 13, 13}, 6, panel.recording ? pink : cyan,
|
||||
panel.recording ? pink : cyan, panel.recording ? .40f : .20f);
|
||||
auto status = wrap(panel.status, 27, 790);
|
||||
text(status.front(), 54, 109, 27, ink, 844);
|
||||
text(status.front(), 70, 109, 27, ink, 844);
|
||||
if (status.size() > 1) text("[...]", 864, 109, 23, muted, 968);
|
||||
rect({32, 198, 936, 1}, {44, 61, 79, 255});
|
||||
if (!settings) {
|
||||
rect({32, 214, 936, 176}, card);
|
||||
rect({32, 214, 3, 176}, {50, 102, 119, 255});
|
||||
rounded({32, 212, 936, 178}, 16, {22, 34, 49, 255}, {53, 103, 122, 255}, .18f);
|
||||
for (size_t i = 0; i < lines_per_page && page * lines_per_page + i < lines.size(); ++i)
|
||||
text(lines[page * lines_per_page + i], 48, 246 + int(i) * 40, 32,
|
||||
panel.transcript.empty() ? muted : ink, 952);
|
||||
@@ -275,7 +289,7 @@ struct PanelSurface::Impl {
|
||||
32, 486, 23, muted, 968);
|
||||
text("Tracking lost? Wrist placement falls back to world space.", 32, 518, 23, muted, 968);
|
||||
}
|
||||
rect({32, 556, 936, 1}, {44, 61, 79, 255});
|
||||
rect({32, 550, 936, 1}, {39, 65, 80, 255});
|
||||
for (size_t i = 0; i < buttons.size(); ++i) {
|
||||
const auto& b = buttons[i];
|
||||
if (!visible(b.id)) continue;
|
||||
@@ -284,12 +298,14 @@ struct PanelSurface::Impl {
|
||||
(mounting(b.id) && *mounting(b.id) == mount);
|
||||
bool hover = std::find(hovered.begin(), hovered.end(), int(i)) != hovered.end();
|
||||
bool down = std::find(pressed.begin(), pressed.end(), int(i)) != pressed.end();
|
||||
rect(b.r, !on ? Color{18, 23, 34, 255} : down ? Color{37, 74, 89, 255} :
|
||||
hover || selected ? Color{27, 53, 68, 255} : card);
|
||||
Color accent = b.id == Control::Record && panel.recording ? pink : cyan;
|
||||
if (on && (hover || selected || b.id == Control::Record)) neon(b.r, accent);
|
||||
else border(b.r, on ? Color{58, 79, 101, 255} : Color{30, 40, 53, 255});
|
||||
if (selected) rect({b.r.x + 1, b.r.y + 1, 4, b.r.h - 2}, cyan);
|
||||
const Color fill = !on ? Color{17, 23, 33, 255} : down ? Color{35, 77, 88, 255} :
|
||||
hover || selected ? Color{26, 55, 68, 255} : card;
|
||||
const Color accent = b.id == Control::Record && panel.recording ? pink : cyan;
|
||||
const bool highlighted = on && (hover || selected || b.id == Control::Record ||
|
||||
(b.id == Control::Insert && panel.transcript.size()));
|
||||
rounded(b.r, std::min(16, b.r.h / 3), fill,
|
||||
!on ? Color{35, 46, 59, 255} : highlighted ? accent : Color{67, 93, 112, 255},
|
||||
highlighted ? (hover || down ? .40f : .23f) : 0.f, highlighted ? 2 : 1);
|
||||
const auto label = b.id == Control::Record && panel.recording ? "Stop" : b.label;
|
||||
text(label, b.r.x + 16, b.r.y + b.r.h / 2 + 9, 27, on ? ink : Color{81, 96, 113, 255}, b.r.x + b.r.w - 8);
|
||||
if (mounting(b.id) && selected) text("ON", b.r.x + b.r.w - 56, b.r.y + 38, 23, cyan, b.r.x + b.r.w - 12);
|
||||
|
||||
@@ -34,6 +34,8 @@ int main(int argc, char** argv) {
|
||||
assert(surface.pixels()[(40 * PanelSurface::width + 40) * 4 + 3] == 255);
|
||||
// Disabled controls never emit insertion/submission. Retry/Cancel remain available.
|
||||
no_action(click(surface, 480, 610)); no_action(click(surface, 680, 610));
|
||||
no_action(click(surface, 32, 574)); // clipped visual corners are not invisible hit targets
|
||||
no_action(click(surface, 207, 575));
|
||||
assert(click(surface, 100, 610).action == UiAction::BeginRecord);
|
||||
assert(click(surface, 280, 610).action == UiAction::Cancel);
|
||||
no_action(surface.pointer_up(0, 100, 610));
|
||||
|
||||
Reference in new issue
Block a user