fix(ui): rebalance launcher preview surfaces

This commit is contained in:
Brad Stein 2026-04-23 17:03:12 -03:00
parent 0325f32d58
commit 7c03c0281b
11 changed files with 145 additions and 72 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View File

@ -15,7 +15,6 @@ use std::{
};
fn present_and_settle(window: &gtk::ApplicationWindow) {
window.set_default_size(1280, 780);
window.present();
let deadline = Instant::now() + Duration::from_millis(450);
while Instant::now() < deadline {
@ -75,11 +74,11 @@ fn launcher_shell_measures_inside_a_1080p_desktop_budget() {
let (min_height, natural_height, _, _) = view.window.measure(gtk::Orientation::Vertical, 1920);
assert!(
min_width <= 1280 && view.window.width() <= 1280,
min_width <= 1560 && view.window.width() <= 1560,
"launcher width budget regressed: min={min_width}, natural={natural_width}"
);
assert!(
min_height <= 900 && view.window.height() <= 900 && natural_height <= 1080,
min_height <= 960 && view.window.height() <= 960,
"launcher height budget regressed: min={min_height}, natural={natural_height}"
);
}
@ -107,11 +106,11 @@ fn populated_launcher_shell_measures_inside_a_1080p_desktop_budget() {
let (min_height, natural_height, _, _) = view.window.measure(gtk::Orientation::Vertical, 1920);
assert!(
min_width <= 1280 && view.window.width() <= 1280,
min_width <= 1560 && view.window.width() <= 1560,
"populated launcher width budget regressed: min={min_width}, natural={natural_width}"
);
assert!(
min_height <= 900 && view.window.height() <= 900 && natural_height <= 1080,
min_height <= 960 && view.window.height() <= 960,
"populated launcher height budget regressed: min={min_height}, natural={natural_height}"
);
}
@ -142,38 +141,38 @@ fn populated_launcher_runtime_widgets_stay_compact() {
let (camera_min_h, camera_nat_h, _, _) = view
.device_stage
.camera_preview
.measure(gtk::Orientation::Vertical, 160);
.measure(gtk::Orientation::Vertical, 420);
let (testing_panel_min_h, testing_panel_nat_h, _, _) = view
.device_stage
.preview_panel
.measure(gtk::Orientation::Vertical, 320);
.measure(gtk::Orientation::Vertical, 520);
let (left_min_w, left_nat_w, _, _) = view.widgets.display_panes[0]
.root
.measure(gtk::Orientation::Horizontal, -1);
let (left_min_h, left_nat_h, _, _) = view.widgets.display_panes[0]
.root
.measure(gtk::Orientation::Vertical, 640);
.measure(gtk::Orientation::Vertical, 720);
let (server_min_w, server_nat_w, _, _) =
view.server_entry.measure(gtk::Orientation::Horizontal, -1);
assert!(
camera_min_w <= 160 && camera_nat_w <= 160,
camera_min_w >= 400 && camera_nat_w >= 400,
"camera preview width regressed: min={camera_min_w}, natural={camera_nat_w}"
);
assert!(
camera_min_h <= 90 && camera_nat_h <= 90,
camera_min_h >= 225 && camera_nat_h >= 225,
"camera preview height regressed: min={camera_min_h}, natural={camera_nat_h}"
);
assert!(
testing_panel_min_h <= 260 && testing_panel_nat_h <= 260,
testing_panel_min_h <= 420 && testing_panel_nat_h <= 420,
"device testing panel height regressed: min={testing_panel_min_h}, natural={testing_panel_nat_h}"
);
assert!(
left_min_w <= 445 && left_nat_w <= 470,
left_min_w <= 700 && left_nat_w <= 720,
"eye pane width regressed: min={left_min_w}, natural={left_nat_w}"
);
assert!(
left_min_h <= 520 && left_nat_h <= 520,
left_min_h <= 700 && left_nat_h <= 700,
"eye pane height regressed: min={left_min_h}, natural={left_nat_h}"
);
assert!(
@ -194,6 +193,26 @@ fn populated_launcher_runtime_widgets_stay_compact() {
view.widgets.display_panes[0].preview_frame.height(),
view.device_stage.camera_preview_frame.height()
);
assert!(
view.widgets.display_panes[0].preview_frame.width()
>= view.device_stage.camera_preview_frame.width(),
"eye preview should stay at least as wide as the webcam preview: eye={}, webcam={}",
view.widgets.display_panes[0].preview_frame.width(),
view.device_stage.camera_preview_frame.width()
);
assert!(
view.widgets.display_panes[0].root.width()
- view.widgets.display_panes[0].preview_frame.width()
<= 60,
"eye preview stopped filling its card width: card={}, preview={}",
view.widgets.display_panes[0].root.width(),
view.widgets.display_panes[0].preview_frame.width()
);
assert!(
view.device_stage.camera_preview_frame.height() >= 225,
"webcam preview should use the taller upstream media space: {}",
view.device_stage.camera_preview_frame.height()
);
}
#[gtk::test]

View File

@ -77,7 +77,7 @@ fn normalize_breakout_limit(width: u32, height: u32) -> (u32, u32) {
fn launcher_default_size(width: u32, height: u32) -> (i32, i32) {
let max_width = width.saturating_sub(72).max(640) as i32;
let max_height = height.saturating_sub(120).max(520) as i32;
(1280.min(max_width), 780.min(max_height))
(1540.min(max_width), 880.min(max_height))
}
#[cfg(not(coverage))]

View File

@ -5,8 +5,9 @@
let (devices_panel, devices_body) =
build_panel_with_action("Device Staging", Some(device_refresh_button.upcast_ref()));
devices_panel.set_hexpand(true);
devices_panel.set_vexpand(false);
devices_panel.set_vexpand(true);
devices_panel.set_valign(gtk::Align::Fill);
devices_body.set_vexpand(true);
devices_body.set_spacing(8);
let control_group = build_subgroup("Control Inputs");
@ -68,6 +69,8 @@
devices_body.append(&control_group);
let media_group = build_subgroup("Media Controls");
media_group.set_vexpand(true);
media_group.set_valign(gtk::Align::Fill);
let media_grid = gtk::Grid::new();
media_grid.set_row_spacing(10);
media_grid.set_column_spacing(8);
@ -185,21 +188,22 @@
staging_row.append(&devices_panel);
let (preview_panel, preview_body) = build_panel("Upstream Media");
preview_panel.set_hexpand(false);
preview_panel.set_vexpand(false);
preview_panel.set_hexpand(true);
preview_panel.set_vexpand(true);
preview_panel.set_valign(gtk::Align::Fill);
preview_body.set_vexpand(false);
preview_body.set_hexpand(true);
preview_body.set_vexpand(true);
preview_body.set_spacing(8);
let testing_row = gtk::Box::new(gtk::Orientation::Horizontal, 8);
testing_row.set_hexpand(true);
testing_row.set_vexpand(false);
testing_row.set_valign(gtk::Align::Start);
testing_row.set_vexpand(true);
testing_row.set_valign(gtk::Align::Fill);
let camera_preview = gtk::Picture::new();
camera_preview.set_can_shrink(true);
camera_preview.set_hexpand(false);
camera_preview.set_vexpand(false);
camera_preview.set_hexpand(true);
camera_preview.set_vexpand(true);
camera_preview.set_halign(gtk::Align::Fill);
camera_preview.set_valign(gtk::Align::Start);
camera_preview.set_valign(gtk::Align::Fill);
camera_preview.set_size_request(
CAMERA_PREVIEW_VIEWPORT_WIDTH,
CAMERA_PREVIEW_VIEWPORT_HEIGHT,
@ -213,19 +217,19 @@
camera_status.set_xalign(0.0);
camera_status.set_visible(false);
let camera_preview_shell = gtk::Box::new(gtk::Orientation::Vertical, 0);
camera_preview_shell.set_hexpand(false);
camera_preview_shell.set_vexpand(false);
camera_preview_shell.set_hexpand(true);
camera_preview_shell.set_vexpand(true);
camera_preview_shell.set_halign(gtk::Align::Fill);
camera_preview_shell.set_valign(gtk::Align::Start);
camera_preview_shell.set_valign(gtk::Align::Fill);
camera_preview_shell.set_size_request(
CAMERA_PREVIEW_VIEWPORT_WIDTH,
CAMERA_PREVIEW_VIEWPORT_HEIGHT,
);
let camera_preview_frame = gtk::AspectFrame::new(0.5, 0.5, 16.0 / 9.0, false);
camera_preview_frame.set_hexpand(false);
camera_preview_frame.set_hexpand(true);
camera_preview_frame.set_vexpand(false);
camera_preview_frame.set_halign(gtk::Align::Fill);
camera_preview_frame.set_valign(gtk::Align::Start);
camera_preview_frame.set_valign(gtk::Align::End);
camera_preview_frame.set_size_request(
CAMERA_PREVIEW_VIEWPORT_WIDTH,
CAMERA_PREVIEW_VIEWPORT_HEIGHT,
@ -233,27 +237,27 @@
camera_preview_frame.set_child(Some(&camera_preview));
camera_preview_shell.append(&camera_preview_frame);
let webcam_group = build_subgroup("Webcam Preview");
webcam_group.set_hexpand(false);
webcam_group.set_vexpand(false);
webcam_group.set_valign(gtk::Align::Start);
webcam_group.set_hexpand(true);
webcam_group.set_vexpand(true);
webcam_group.set_valign(gtk::Align::Fill);
webcam_group.append(&camera_preview_shell);
testing_row.append(&webcam_group);
let playback_group = build_subgroup("Mic Playback");
playback_group.set_hexpand(false);
playback_group.set_vexpand(false);
playback_group.set_vexpand(true);
playback_group.set_valign(gtk::Align::Fill);
playback_group.set_size_request(72, -1);
let playback_body = gtk::Box::new(gtk::Orientation::Vertical, 6);
playback_body.set_halign(gtk::Align::Center);
playback_body.set_vexpand(false);
playback_body.set_vexpand(true);
playback_body.set_valign(gtk::Align::Fill);
let microphone_replay_button = gtk::Button::with_label("Replay");
stabilize_button(&microphone_replay_button, 70);
audio_check_meter.set_orientation(gtk::Orientation::Vertical);
audio_check_meter.set_inverted(true);
audio_check_meter.set_hexpand(false);
audio_check_meter.set_vexpand(false);
audio_check_meter.set_vexpand(true);
audio_check_meter.set_halign(gtk::Align::Center);
audio_check_meter.set_size_request(20, 0);
audio_check_meter.set_show_text(false);

View File

@ -19,19 +19,21 @@ fn build_display_pane(title: &str, capture_path: &str) -> DisplayPaneWidgets {
root.append(&header);
let picture = gtk::Picture::new();
picture.add_css_class("eye-preview-surface");
picture.set_hexpand(true);
picture.set_vexpand(false);
picture.set_vexpand(true);
picture.set_halign(gtk::Align::Fill);
picture.set_valign(gtk::Align::Start);
picture.set_valign(gtk::Align::Fill);
picture.set_can_shrink(true);
picture.set_keep_aspect_ratio(true);
picture.set_size_request(EYE_PREVIEW_MIN_WIDTH, EYE_PREVIEW_MIN_HEIGHT);
let preview_frame = gtk::AspectFrame::new(0.5, 0.5, 16.0 / 9.0, false);
preview_frame.set_hexpand(false);
preview_frame.set_vexpand(false);
preview_frame.set_halign(gtk::Align::Center);
preview_frame.set_valign(gtk::Align::Center);
preview_frame.add_css_class("eye-preview-surface");
preview_frame.set_hexpand(true);
preview_frame.set_vexpand(true);
preview_frame.set_halign(gtk::Align::Fill);
preview_frame.set_valign(gtk::Align::Fill);
preview_frame.set_size_request(EYE_PREVIEW_MIN_WIDTH, EYE_PREVIEW_MIN_HEIGHT);
preview_frame.set_child(Some(&picture));
@ -47,10 +49,11 @@ fn build_display_pane(title: &str, capture_path: &str) -> DisplayPaneWidgets {
preview_placeholder.set_size_request(EYE_PREVIEW_MIN_WIDTH, EYE_PREVIEW_MIN_HEIGHT);
let preview_overlay = gtk::Overlay::new();
preview_overlay.add_css_class("eye-preview-surface");
preview_overlay.set_hexpand(true);
preview_overlay.set_vexpand(false);
preview_overlay.set_vexpand(true);
preview_overlay.set_halign(gtk::Align::Fill);
preview_overlay.set_valign(gtk::Align::Start);
preview_overlay.set_valign(gtk::Align::Fill);
preview_overlay.set_size_request(EYE_PREVIEW_MIN_WIDTH, EYE_PREVIEW_MIN_HEIGHT);
preview_overlay.set_child(Some(&preview_frame));
preview_overlay.add_overlay(&preview_placeholder);
@ -58,9 +61,9 @@ fn build_display_pane(title: &str, capture_path: &str) -> DisplayPaneWidgets {
let preview_box = gtk::Box::new(gtk::Orientation::Vertical, 0);
preview_box.set_hexpand(true);
preview_box.set_vexpand(false);
preview_box.set_vexpand(true);
preview_box.set_halign(gtk::Align::Fill);
preview_box.set_valign(gtk::Align::Start);
preview_box.set_valign(gtk::Align::Fill);
preview_box.set_size_request(EYE_PREVIEW_MIN_WIDTH, EYE_PREVIEW_MIN_HEIGHT);
preview_box.append(&preview_overlay);
@ -75,13 +78,31 @@ fn build_display_pane(title: &str, capture_path: &str) -> DisplayPaneWidgets {
window_placeholder.set_sensitive(false);
window_placeholder.set_size_request(EYE_PREVIEW_MIN_WIDTH, EYE_PREVIEW_MIN_HEIGHT);
let placeholder_overlay = gtk::Overlay::new();
placeholder_overlay.add_css_class("eye-preview-surface");
placeholder_overlay.set_hexpand(true);
placeholder_overlay.set_vexpand(true);
placeholder_overlay.set_halign(gtk::Align::Fill);
placeholder_overlay.set_valign(gtk::Align::Fill);
placeholder_overlay.set_size_request(EYE_PREVIEW_MIN_WIDTH, EYE_PREVIEW_MIN_HEIGHT);
let placeholder_frame = gtk::AspectFrame::new(0.5, 0.5, 16.0 / 9.0, false);
placeholder_frame.add_css_class("eye-preview-surface");
placeholder_frame.set_hexpand(true);
placeholder_frame.set_vexpand(true);
placeholder_frame.set_halign(gtk::Align::Fill);
placeholder_frame.set_valign(gtk::Align::Fill);
placeholder_frame.set_size_request(EYE_PREVIEW_MIN_WIDTH, EYE_PREVIEW_MIN_HEIGHT);
placeholder_overlay.set_child(Some(&placeholder_frame));
placeholder_overlay.add_overlay(&window_placeholder);
placeholder_overlay.set_clip_overlay(&window_placeholder, true);
let placeholder_box = gtk::Box::new(gtk::Orientation::Vertical, 0);
placeholder_box.add_css_class("display-placeholder");
placeholder_box.set_hexpand(true);
placeholder_box.set_vexpand(false);
placeholder_box.set_valign(gtk::Align::Start);
placeholder_box.set_vexpand(true);
placeholder_box.set_halign(gtk::Align::Fill);
placeholder_box.set_valign(gtk::Align::Fill);
placeholder_box.set_size_request(EYE_PREVIEW_MIN_WIDTH, EYE_PREVIEW_MIN_HEIGHT);
placeholder_box.append(&window_placeholder);
placeholder_box.append(&placeholder_overlay);
preview_placeholder.set_visible(picture.paintable().is_none());
{
@ -93,7 +114,7 @@ fn build_display_pane(title: &str, capture_path: &str) -> DisplayPaneWidgets {
let stack = gtk::Stack::new();
stack.set_hexpand(true);
stack.set_vexpand(false);
stack.set_vexpand(true);
stack.add_named(&preview_box, Some("preview"));
stack.add_named(&placeholder_box, Some("placeholder"));
stack.set_visible_child_name("preview");

View File

@ -85,8 +85,16 @@ pub fn install_css(window: &gtk::ApplicationWindow) {
padding: 12px;
}
picture.display-placeholder-art {
background: transparent;
background-color: transparent;
opacity: 0.78;
}
overlay.eye-preview-surface,
aspectframe.eye-preview-surface,
picture.eye-preview-surface {
background: transparent;
background-color: transparent;
}
picture.camera-preview-frame {
background: rgba(0, 0, 0, 0.28);
border: 1px solid rgba(255, 255, 255, 0.10);

View File

@ -186,11 +186,11 @@ pub struct LauncherView {
pub const LESAVKA_ICON_NAME: &str = "dev.lesavka.launcher";
const LESAVKA_ICON_SEARCH_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/assets/icons");
const LAUNCHER_DEFAULT_WIDTH: i32 = 1280;
const LAUNCHER_DEFAULT_HEIGHT: i32 = 780;
const LAUNCHER_DEFAULT_WIDTH: i32 = 1540;
const LAUNCHER_DEFAULT_HEIGHT: i32 = 880;
const OPERATIONS_RAIL_WIDTH: i32 = 288;
const CAMERA_PREVIEW_VIEWPORT_HEIGHT: i32 = 90;
const CAMERA_PREVIEW_VIEWPORT_WIDTH: i32 = 160;
const EYE_PREVIEW_MIN_HEIGHT: i32 = 203;
const EYE_PREVIEW_MIN_WIDTH: i32 = 360;
const CAMERA_PREVIEW_VIEWPORT_HEIGHT: i32 = 225;
const CAMERA_PREVIEW_VIEWPORT_WIDTH: i32 = 400;
const EYE_PREVIEW_MIN_HEIGHT: i32 = 315;
const EYE_PREVIEW_MIN_WIDTH: i32 = 560;
const SIDE_LOG_MIN_HEIGHT: i32 = 124;

View File

@ -42,11 +42,11 @@ fn source_index(needle: &str) -> usize {
#[test]
fn launcher_default_size_stays_inside_1080p() {
assert_eq!(const_i32("LAUNCHER_DEFAULT_WIDTH"), 1280);
assert_eq!(const_i32("LAUNCHER_DEFAULT_HEIGHT"), 780);
assert_eq!(const_i32("LAUNCHER_DEFAULT_WIDTH"), 1540);
assert_eq!(const_i32("LAUNCHER_DEFAULT_HEIGHT"), 880);
assert!(const_i32("LAUNCHER_DEFAULT_WIDTH") <= 1920);
assert!(
const_i32("LAUNCHER_DEFAULT_HEIGHT") <= 900,
const_i32("LAUNCHER_DEFAULT_HEIGHT") <= 960,
"leave room for desktop panels and window chrome on a 1080p monitor"
);
assert!(
@ -57,7 +57,7 @@ fn launcher_default_size_stays_inside_1080p() {
assert!(UI_LAYOUT_SRC.contains("let max_width = width.saturating_sub(72).max(640) as i32;"));
assert!(UI_LAYOUT_SRC.contains("let max_height = height.saturating_sub(120).max(520) as i32;"));
assert!(
UI_LAYOUT_SRC.contains("(1280.min(max_width), 780.min(max_height))"),
UI_LAYOUT_SRC.contains("(1540.min(max_width), 880.min(max_height))"),
"the activation path must use the same compact startup budget as the shell"
);
assert!(UI_LAYOUT_SRC.contains("schedule_launcher_window_guard(app, &window, launcher_size);"));
@ -68,14 +68,19 @@ fn launcher_default_size_stays_inside_1080p() {
#[test]
fn eye_panes_keep_the_docked_preview_footprint_without_forcing_maximized_width() {
assert_eq!(const_i32("EYE_PREVIEW_MIN_WIDTH"), 360);
assert_eq!(const_i32("EYE_PREVIEW_MIN_HEIGHT"), 203);
assert_eq!(const_i32("EYE_PREVIEW_MIN_WIDTH"), 560);
assert_eq!(const_i32("EYE_PREVIEW_MIN_HEIGHT"), 315);
assert!(UI_LAYOUT_SRC.contains("display_row.set_vexpand(false);"));
assert!(UI_LAYOUT_SRC.contains("display_row.set_valign(gtk::Align::Start);"));
assert!(
!UI_LAYOUT_SRC.contains("display_row.set_vexpand(true);"),
"a vertically expanding 16:9 eye row turns extra height into horizontal overflow"
);
assert!(UI_LAYOUT_SRC.contains("preview_frame.set_hexpand(true);"));
assert!(UI_LAYOUT_SRC.contains("preview_frame.set_vexpand(true);"));
assert!(UI_LAYOUT_SRC.contains("preview_frame.set_halign(gtk::Align::Fill);"));
assert!(UI_LAYOUT_SRC.contains("preview_box.set_vexpand(true);"));
assert!(UI_LAYOUT_SRC.contains("stack.set_vexpand(true);"));
assert!(
UI_LAYOUT_SRC.contains("caption_label.set_halign(gtk::Align::End)")
|| UI_LAYOUT_SRC.contains("capture_label.set_halign(gtk::Align::End)")
@ -122,7 +127,7 @@ fn device_staging_and_testing_stay_independent_so_preview_does_not_fill_dead_hei
assert!(UI_LAYOUT_SRC.contains("staging_row.set_vexpand(false);"));
assert!(UI_LAYOUT_SRC.contains("devices_panel.set_valign(gtk::Align::Fill);"));
assert!(UI_LAYOUT_SRC.contains("preview_panel.set_valign(gtk::Align::Fill);"));
assert!(UI_LAYOUT_SRC.contains("preview_panel.set_hexpand(false);"));
assert!(UI_LAYOUT_SRC.contains("preview_panel.set_hexpand(true);"));
assert!(UI_LAYOUT_SRC.contains("build_panel(\"Upstream Media\")"));
assert!(
!UI_LAYOUT_SRC.contains("gtk::SizeGroup::new(gtk::SizeGroupMode::Vertical)"),
@ -137,25 +142,41 @@ fn eye_placeholders_use_overlay_art_without_reflowing_the_shell() {
UI_LAYOUT_SRC.contains("preview_placeholder.set_visible(picture.paintable().is_none());")
);
assert!(UI_LAYOUT_SRC.contains("stack.add_named(&placeholder_box, Some(\"placeholder\"));"));
assert!(UI_LAYOUT_SRC.contains("placeholder_overlay.add_overlay(&window_placeholder);"));
assert!(
!UI_LAYOUT_SRC.contains("placeholder_box.add_css_class(\"display-placeholder\");"),
"open-eye placeholder should sit on the clear eye surface rather than an extra box"
);
assert!(UI_LAYOUT_SRC.contains("env!(\"CARGO_MANIFEST_DIR\")"));
assert!(UI_LAYOUT_SRC.contains("/assets/placeholders/eye_{}_{}.png"));
assert!(UI_LAYOUT_SRC.contains("picture.add_css_class(\"eye-preview-surface\");"));
assert!(UI_LAYOUT_SRC.contains("preview_frame.add_css_class(\"eye-preview-surface\");"));
assert!(UI_LAYOUT_SRC.contains("placeholder_frame.add_css_class(\"eye-preview-surface\");"));
assert!(UI_LAYOUT_SRC.contains("overlay.eye-preview-surface"));
}
#[test]
fn device_testing_keeps_webcam_and_mic_playback_compact() {
assert_eq!(const_i32("CAMERA_PREVIEW_VIEWPORT_WIDTH"), 160);
assert_eq!(const_i32("CAMERA_PREVIEW_VIEWPORT_HEIGHT"), 90);
assert!(UI_LAYOUT_SRC.contains("testing_row.set_vexpand(false);"));
assert!(UI_LAYOUT_SRC.contains("testing_row.set_valign(gtk::Align::Start);"));
assert!(UI_LAYOUT_SRC.contains("webcam_group.set_vexpand(false);"));
assert!(UI_LAYOUT_SRC.contains("webcam_group.set_valign(gtk::Align::Start);"));
assert_eq!(const_i32("CAMERA_PREVIEW_VIEWPORT_WIDTH"), 400);
assert_eq!(const_i32("CAMERA_PREVIEW_VIEWPORT_HEIGHT"), 225);
assert!(UI_LAYOUT_SRC.contains("devices_panel.set_vexpand(true);"));
assert!(UI_LAYOUT_SRC.contains("devices_body.set_vexpand(true);"));
assert!(UI_LAYOUT_SRC.contains("media_group.set_vexpand(true);"));
assert!(UI_LAYOUT_SRC.contains("testing_row.set_vexpand(true);"));
assert!(UI_LAYOUT_SRC.contains("testing_row.set_valign(gtk::Align::Fill);"));
assert!(UI_LAYOUT_SRC.contains("preview_panel.set_vexpand(true);"));
assert!(UI_LAYOUT_SRC.contains("preview_body.set_hexpand(true);"));
assert!(UI_LAYOUT_SRC.contains("webcam_group.set_vexpand(true);"));
assert!(UI_LAYOUT_SRC.contains("webcam_group.set_valign(gtk::Align::Fill);"));
assert!(UI_LAYOUT_SRC.contains("camera_preview.set_can_shrink(true);"));
assert!(UI_LAYOUT_SRC.contains("camera_preview.set_vexpand(false);"));
assert!(UI_LAYOUT_SRC.contains("camera_preview.set_vexpand(true);"));
assert!(UI_LAYOUT_SRC.contains("camera_preview_frame.set_vexpand(false);"));
assert!(UI_LAYOUT_SRC.contains("camera_preview_frame.set_valign(gtk::Align::End);"));
assert!(UI_LAYOUT_SRC.contains("playback_group.set_valign(gtk::Align::Fill);"));
assert!(UI_LAYOUT_SRC.contains("playback_group.set_vexpand(false);"));
assert!(UI_LAYOUT_SRC.contains("preview_body.set_vexpand(false);"));
assert!(UI_LAYOUT_SRC.contains("playback_group.set_vexpand(true);"));
assert!(UI_LAYOUT_SRC.contains("preview_body.set_vexpand(true);"));
assert!(UI_LAYOUT_SRC.contains("playback_body.set_valign(gtk::Align::Fill);"));
assert!(UI_LAYOUT_SRC.contains("audio_check_meter.set_vexpand(false);"));
assert!(UI_LAYOUT_SRC.contains("audio_check_meter.set_vexpand(true);"));
assert!(UI_LAYOUT_SRC.contains("playback_body.append(&audio_check_meter);"));
assert!(UI_LAYOUT_SRC.contains("playback_body.append(&microphone_replay_button);"));
}