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:
@@ -393,6 +393,7 @@ struct OverrideFlags {
|
|||||||
bitrate_kbps: bool,
|
bitrate_kbps: bool,
|
||||||
codec: bool,
|
codec: bool,
|
||||||
hdr_enabled: bool,
|
hdr_enabled: bool,
|
||||||
|
enable_444: bool,
|
||||||
compositor: bool,
|
compositor: bool,
|
||||||
audio_channels: bool,
|
audio_channels: bool,
|
||||||
mic_enabled: bool,
|
mic_enabled: bool,
|
||||||
@@ -419,6 +420,7 @@ impl OverrideFlags {
|
|||||||
bitrate_kbps: o.bitrate_kbps.is_some(),
|
bitrate_kbps: o.bitrate_kbps.is_some(),
|
||||||
codec: o.codec.is_some(),
|
codec: o.codec.is_some(),
|
||||||
hdr_enabled: o.hdr_enabled.is_some(),
|
hdr_enabled: o.hdr_enabled.is_some(),
|
||||||
|
enable_444: o.enable_444.is_some(),
|
||||||
compositor: o.compositor.is_some(),
|
compositor: o.compositor.is_some(),
|
||||||
audio_channels: o.audio_channels.is_some(),
|
audio_channels: o.audio_channels.is_some(),
|
||||||
mic_enabled: o.mic_enabled.is_some(),
|
mic_enabled: o.mic_enabled.is_some(),
|
||||||
@@ -511,9 +513,30 @@ fn described_overridable(
|
|||||||
control: impl Into<Element>,
|
control: impl Into<Element>,
|
||||||
caption: &str,
|
caption: &str,
|
||||||
) -> Element {
|
) -> Element {
|
||||||
if scope.is_empty() || !overridden {
|
if scope.is_empty() {
|
||||||
return described(control, caption);
|
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 (rev, set_rev) = (rev.0, rev.1.clone());
|
||||||
let scope = scope.to_string();
|
let scope = scope.to_string();
|
||||||
vstack((
|
vstack((
|
||||||
@@ -524,7 +547,8 @@ fn described_overridable(
|
|||||||
.foreground(ThemeRef::AccentText)
|
.foreground(ThemeRef::AccentText)
|
||||||
.wrap()
|
.wrap()
|
||||||
.max_width(360.0)
|
.max_width(360.0)
|
||||||
.horizontal_alignment(HorizontalAlignment::Left),
|
.horizontal_alignment(HorizontalAlignment::Left)
|
||||||
|
.vertical_alignment(VerticalAlignment::Center),
|
||||||
button("Reset").on_click(move || {
|
button("Reset").on_click(move || {
|
||||||
let mut catalog = ProfilesFile::load();
|
let mut catalog = ProfilesFile::load();
|
||||||
if let Some(p) = catalog.profiles.iter_mut().find(|p| p.id == scope) {
|
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);
|
set_rev.call(rev + 1);
|
||||||
}),
|
}),
|
||||||
))
|
))
|
||||||
.spacing(8.0),
|
.spacing(8.0)
|
||||||
|
.min_height(CAPTION_MIN_H),
|
||||||
))
|
))
|
||||||
.spacing(5.0)
|
.spacing(5.0)
|
||||||
.into()
|
.into()
|
||||||
@@ -827,6 +852,14 @@ pub(crate) fn settings_page(
|
|||||||
s.hdr_enabled,
|
s.hdr_enabled,
|
||||||
|s, on| s.hdr_enabled = on,
|
|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 -----------------------------------------------------------------------------
|
// --- Input -----------------------------------------------------------------------------
|
||||||
// Controller forwarding: Automatic forwards EVERY real controller, each as its own pad;
|
// 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. \
|
"HDR10, when the host has HDR content and this display supports it. \
|
||||||
HEVC only; otherwise the stream stays SDR.",
|
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,
|
None,
|
||||||
));
|
));
|
||||||
@@ -1581,3 +1625,42 @@ pub(crate) fn settings_page(
|
|||||||
}
|
}
|
||||||
grid(layers).into()
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user