feat(clients/windows): all-vendor video pipeline rewrite + app icon + hosts-page tiles
Decode+present rewrite (first real pixels on glass for this client): - Decode: FFmpeg D3D11VA on NVIDIA/AMD/Intel. get_format now only returns AV_PIX_FMT_D3D11 and lets libavcodec build the decode pool from hw_device_ctx (hand-built frames contexts failed three different ways: NVIDIA rejects DECODER|SHADER_RESOURCE arrays, BindFlags=0 fails texture creation, Intel rejects non-128-aligned HEVC surfaces at the first SubmitDecoderBuffers). A DXVA profile probe before the hwdevice commits hardware-vs-software up front instead of burning the opening IDR; extra_hw_frames covers the frames the client holds. - Present: the decoded slice is copied with ONE display-size-boxed CopySubresourceRegion (a planar slice is a single subresource in D3D11; the old two-copy D3D12-style code silently no-opped - the black screen) into a sampleable NV12/P010 texture, per-plane SRVs + YUV->RGB shaders. - New dedicated render thread (render.rs): presenting is decoupled from the XAML thread; frame-latency-waitable swapchain + SetMaximumFrameLatency(1), newest-wins drain after the wait, crossbeam frame channel with pts for a capture->presented p50 log. - HiDPI: pixel-sized buffers + SetMatrixTransform(96/dpi) - was blurry at 125/150 % scaling. - Software fallback now feeds the same shaders (swscale -> NV12/P010 planes -> two dynamic plane textures); ps_rgba/X2BGR10 path deleted, hw/sw colour math identical. - Adapter selection for hybrid boxes: PUNKTFUNK_ADAPTER > the window's monitor's adapter > default; PUNKTFUNK_D3D_DEBUG=1 debug layer. - Session pump: request_keyframe at start and on hw->sw demotion (infinite GOP would otherwise sit on a black screen). Validated live on the Arc Pro + RTX 3500 Ada laptop against the local Windows host: 60 fps D3D11VA on both vendors, software path, GUI on glass. Also: embedded app icon (build.rs winresource + WM_SETICON, MSIX Square44x44 targetsize assets, pack-msix stages them) and the hosts-page tile rework (tap-to-connect tiles with sibling overflow menu - fixes forget-also-connects - in-tile rename editor, add-host modal via root state). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -27,26 +27,67 @@ pub(crate) fn card(child: impl Into<Element>) -> Border {
|
||||
.padding(uniform(16.0))
|
||||
}
|
||||
|
||||
/// Card chrome with no padding — for cards whose interactive regions (tap-to-connect area vs.
|
||||
/// action buttons) must own their padding so hit areas reach the card edges.
|
||||
pub(crate) fn card_flush(child: impl Into<Element>) -> Border {
|
||||
card(child).padding(uniform(0.0))
|
||||
}
|
||||
|
||||
/// An OPAQUE modal/dialog surface. `card`'s `CardBackground` is a translucent acrylic brush — fine
|
||||
/// layered on the page, but a floating dialog over a scrim needs a solid fill or the content behind
|
||||
/// bleeds through (looks "transparent"). `SolidBackground` is the opaque base-layer brush.
|
||||
pub(crate) fn dialog_surface(child: impl Into<Element>) -> Border {
|
||||
border(child.into())
|
||||
.background(ThemeRef::SolidBackground)
|
||||
.border_brush(ThemeRef::SurfaceStroke)
|
||||
.border_thickness(uniform(1.0))
|
||||
.corner_radius(8.0)
|
||||
.padding(uniform(20.0))
|
||||
}
|
||||
|
||||
/// A fully transparent brush: paints nothing but (unlike a null background) makes the whole
|
||||
/// element hit-testable, so a tap region catches clicks in its blank space too.
|
||||
pub(crate) fn hit_test_backstop() -> Color {
|
||||
Color {
|
||||
a: 0,
|
||||
r: 0,
|
||||
g: 0,
|
||||
b: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// A small all-caps section label above a group of cards.
|
||||
pub(crate) fn section(label: &str) -> Element {
|
||||
text_block(label)
|
||||
.font_size(12.0)
|
||||
.semibold()
|
||||
.foreground(ThemeRef::SecondaryText)
|
||||
.margin(edges(2.0, 10.0, 0.0, 0.0))
|
||||
.margin(edges(2.0, 14.0, 0.0, 2.0))
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Wrap a screen's children in a scrollable, centred, max-width column.
|
||||
/// Wrap a screen's children in a scrollable, centred, max-width column. Alignment stays the
|
||||
/// default Stretch: with a MaxWidth that still centres the column, but the children get the
|
||||
/// column's REAL width — an explicit Center would size the column to its content and leave
|
||||
/// every card at its minimum width no matter how large the window is.
|
||||
pub(crate) fn page(children: Vec<Element>) -> Element {
|
||||
let col = vstack(children)
|
||||
.spacing(10.0)
|
||||
.max_width(640.0)
|
||||
.horizontal_alignment(HorizontalAlignment::Center)
|
||||
.margin(edges(24.0, 24.0, 24.0, 40.0));
|
||||
scroll_view(col).into()
|
||||
}
|
||||
|
||||
/// Like [`page`], but wide and airier — for screens whose cards lay out in a responsive grid
|
||||
/// and should use the window instead of a narrow settings column.
|
||||
pub(crate) fn page_wide(children: Vec<Element>) -> Element {
|
||||
let col = vstack(children)
|
||||
.spacing(14.0)
|
||||
.max_width(1120.0)
|
||||
.margin(edges(32.0, 28.0, 32.0, 48.0));
|
||||
scroll_view(col).into()
|
||||
}
|
||||
|
||||
/// A page header: a large bold title on the left, one action button on the right.
|
||||
pub(crate) fn page_header(title: &str, action: Button) -> Element {
|
||||
grid((
|
||||
@@ -103,7 +144,9 @@ pub(crate) fn avatar(name: &str) -> Border {
|
||||
text_block(initial)
|
||||
.font_size(17.0)
|
||||
.semibold()
|
||||
.foreground(ThemeRef::AccentText)
|
||||
// NOT ThemeRef::AccentText — that's accent-COLOURED text for normal surfaces;
|
||||
// on an accent fill it's accent-on-accent (unreadable). This is the on-accent brush.
|
||||
.foreground(ThemeRef::custom("TextOnAccentFillColorPrimaryBrush"))
|
||||
.horizontal_alignment(HorizontalAlignment::Center)
|
||||
.vertical_alignment(VerticalAlignment::Center),
|
||||
)
|
||||
@@ -116,20 +159,39 @@ pub(crate) fn avatar(name: &str) -> Border {
|
||||
/// Pill chip colour intent.
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) enum Pill {
|
||||
Accent,
|
||||
Info,
|
||||
Good,
|
||||
Neutral,
|
||||
}
|
||||
|
||||
/// A small rounded status chip (paired/PIN/HDR/etc.).
|
||||
/// A small rounded status chip (paired/PIN/HDR/etc.) — subtle tinted fills with matching
|
||||
/// system foregrounds (the InfoBar palette), never solid accent (white-on-bright is unreadable).
|
||||
pub(crate) fn pill(text: &str, kind: Pill) -> Border {
|
||||
let (bg, fg) = match kind {
|
||||
Pill::Accent => (ThemeRef::Accent, ThemeRef::AccentText),
|
||||
Pill::Info => (
|
||||
ThemeRef::SystemAttentionBackground,
|
||||
ThemeRef::SystemAttention,
|
||||
),
|
||||
Pill::Good => (ThemeRef::SystemSuccessBackground, ThemeRef::SystemSuccess),
|
||||
Pill::Neutral => (ThemeRef::SubtleFill, ThemeRef::SecondaryText),
|
||||
};
|
||||
border(text_block(text).font_size(11.0).semibold().foreground(fg))
|
||||
.background(bg)
|
||||
.border_brush(ThemeRef::CardStroke)
|
||||
.border_thickness(uniform(1.0))
|
||||
.corner_radius(10.0)
|
||||
.padding(edges(9.0, 3.0, 9.0, 3.0))
|
||||
.padding(edges(9.0, 2.0, 9.0, 2.0))
|
||||
}
|
||||
|
||||
/// A small presence dot (host online/offline).
|
||||
pub(crate) fn presence_dot(online: bool) -> Border {
|
||||
border(vstack(Vec::<Element>::new()))
|
||||
.background(if online {
|
||||
ThemeRef::SystemSuccess
|
||||
} else {
|
||||
ThemeRef::SystemNeutral
|
||||
})
|
||||
.corner_radius(4.0)
|
||||
.width(8.0)
|
||||
.height(8.0)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user