feat(client/windows): 4:4:4 reaches Windows, and the Overridden marker stops moving the page

The GTK client grew its "Full chroma (4:4:4)" switch in the same commit that fixed its
override marker; Windows got neither until now. The row lands in the Quality group under
HDR with the GTK wording verbatim (same setting, same constraints), a full overridable
profile row — the core overlay and the session binary already spoke `enable_444`, so the
shell was the only gap. OverrideFlags grows the matching flag, and a unit test now pins
the whole overlay -> row-flag mapping (tri-state resolution included): a field that
records without marking its row is the original Overridden-row bug wearing a new face.

And the marker obeys the GTK rule learned the hard way there: nothing that appears on an
edit may move what was edited. Both states of a profile-scope row reserve the same
caption-line height, so the marker + Reset materialise in place instead of shoving every
row below them down by a button's height.

Scope-blind audit (the bitrate-box failure class): clean. Decoder and GPU are hidden in
profile scope as device facts (GTK parity), the forwarded controller stays gated, and
every visible profile row commits through `commit`.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-29 18:31:50 +02:00
co-authored by Claude Fable 5
parent 09d1691af4
commit e8eb803a3f
+86 -3
View File
@@ -393,6 +393,7 @@ struct OverrideFlags {
bitrate_kbps: bool,
codec: bool,
hdr_enabled: bool,
enable_444: bool,
compositor: bool,
audio_channels: bool,
mic_enabled: bool,
@@ -419,6 +420,7 @@ impl OverrideFlags {
bitrate_kbps: o.bitrate_kbps.is_some(),
codec: o.codec.is_some(),
hdr_enabled: o.hdr_enabled.is_some(),
enable_444: o.enable_444.is_some(),
compositor: o.compositor.is_some(),
audio_channels: o.audio_channels.is_some(),
mic_enabled: o.mic_enabled.is_some(),
@@ -511,9 +513,30 @@ fn described_overridable(
control: impl Into<Element>,
caption: &str,
) -> Element {
if scope.is_empty() || !overridden {
if scope.is_empty() {
return described(control, caption);
}
// Nothing that appears on an edit may move what was edited (the GTK client's rule,
// learned there the hard way): both states of a profile-scope row reserve the same
// caption-line height, so the marker + Reset materialise IN PLACE instead of shoving
// every row below them down by a button's height.
const CAPTION_MIN_H: f64 = 34.0;
if !overridden {
return vstack((
control.into(),
hstack(vec![text_block(caption)
.font_size(12.0)
.foreground(ThemeRef::SecondaryText)
.wrap()
.max_width(420.0)
.horizontal_alignment(HorizontalAlignment::Left)
.vertical_alignment(VerticalAlignment::Center)
.into()])
.min_height(CAPTION_MIN_H),
))
.spacing(5.0)
.into();
}
let (rev, set_rev) = (rev.0, rev.1.clone());
let scope = scope.to_string();
vstack((
@@ -524,7 +547,8 @@ fn described_overridable(
.foreground(ThemeRef::AccentText)
.wrap()
.max_width(360.0)
.horizontal_alignment(HorizontalAlignment::Left),
.horizontal_alignment(HorizontalAlignment::Left)
.vertical_alignment(VerticalAlignment::Center),
button("Reset").on_click(move || {
let mut catalog = ProfilesFile::load();
if let Some(p) = catalog.profiles.iter_mut().find(|p| p.id == scope) {
@@ -539,7 +563,8 @@ fn described_overridable(
set_rev.call(rev + 1);
}),
))
.spacing(8.0),
.spacing(8.0)
.min_height(CAPTION_MIN_H),
))
.spacing(5.0)
.into()
@@ -827,6 +852,14 @@ pub(crate) fn settings_page(
s.hdr_enabled,
|s, on| s.hdr_enabled = on,
);
let chroma_toggle = setting_toggle(
ctx,
scope,
(rev, set_rev),
"Full chroma (4:4:4)",
s.enable_444,
|s, on| s.enable_444 = on,
);
// --- Input -----------------------------------------------------------------------------
// Controller forwarding: Automatic forwards EVERY real controller, each as its own pad;
@@ -1058,6 +1091,17 @@ pub(crate) fn settings_page(
"HDR10, when the host has HDR content and this display supports it. \
HEVC only; otherwise the stream stays SDR.",
),
// Wording shared with the GTK client (its chroma_row) — same setting,
// same constraints.
described_overridable(
(rev, set_rev),
scope,
"enable_444",
over.enable_444,
chroma_toggle,
"Full-colour video: crisp small text and thin lines, at more \
bandwidth. HEVC only, and only where the host can encode it.",
),
],
None,
));
@@ -1581,3 +1625,42 @@ pub(crate) fn settings_page(
}
grid(layers).into()
}
#[cfg(test)]
mod tests {
use super::*;
use pf_client_core::profiles::SettingsOverlay;
/// Every overlay field maps to its row flag — including the tri-state resolution
/// (any of width/height/match_window marks the one Resolution row) and the 4:4:4
/// switch added for GTK parity. A field that records without marking its row is the
/// original Overridden-row bug wearing a new face.
#[test]
fn override_flags_mirror_the_overlay() {
let none = OverrideFlags::of(None);
assert!(!none.resolution && !none.enable_444 && !none.codec);
let mut p = StreamProfile::new("t".to_string());
p.overrides = SettingsOverlay {
match_window: Some(true),
enable_444: Some(true),
codec: Some("hevc".into()),
bitrate_kbps: Some(20000),
..Default::default()
};
let f = OverrideFlags::of(Some(&p));
assert!(f.resolution, "match_window alone marks the Resolution row");
assert!(f.enable_444);
assert!(f.codec);
assert!(f.bitrate_kbps);
assert!(!f.hdr_enabled && !f.compositor && !f.render_scale);
let mut p2 = StreamProfile::new("t2".to_string());
p2.overrides = SettingsOverlay {
width: Some(3840),
height: Some(2160),
..Default::default()
};
assert!(OverrideFlags::of(Some(&p2)).resolution);
}
}