diff --git a/client/src/launcher/tests/ui_runtime.rs b/client/src/launcher/tests/ui_runtime.rs index c3b0eb5..66bbe2f 100644 --- a/client/src/launcher/tests/ui_runtime.rs +++ b/client/src/launcher/tests/ui_runtime.rs @@ -3,6 +3,7 @@ use crate::launcher::{ devices::DeviceCatalog, preview::PreviewBinding, state::LauncherState, ui_components::build_launcher_view, }; +use gtk::prelude::*; use serial_test::serial; use std::{cell::RefCell, rc::Rc}; @@ -32,6 +33,45 @@ fn gpio_power_label_tracks_detected_devices() { assert_eq!(gpio_power_label(&power), "2 Eyes"); } +#[gtk::test] +#[serial] +fn launcher_shell_measures_inside_a_1080p_desktop_budget() { + if gtk::gdk::Display::default().is_none() { + return; + } + + let app = gtk::Application::builder() + .application_id("dev.lesavka.test-layout-budget") + .build(); + let _ = app.register(None::<>k::gio::Cancellable>); + + let state = LauncherState::new(); + let view = build_launcher_view( + &app, + "http://127.0.0.1:50051", + &DeviceCatalog::default(), + &state, + ); + + let (min_width, natural_width, _, _) = view.window.measure(gtk::Orientation::Horizontal, -1); + let (tall_min_width, tall_natural_width, _, _) = + view.window.measure(gtk::Orientation::Horizontal, 1080); + let (min_height, natural_height, _, _) = view.window.measure(gtk::Orientation::Vertical, 1920); + + assert!( + min_width <= 1920 && natural_width <= 1920, + "launcher width budget regressed: min={min_width}, natural={natural_width}" + ); + assert!( + tall_min_width <= 1920 && tall_natural_width <= 1920, + "launcher 1080p-tall width regressed: min={tall_min_width}, natural={tall_natural_width}" + ); + assert!( + min_height <= 1080 && natural_height <= 1080, + "launcher height budget regressed: min={min_height}, natural={natural_height}" + ); +} + #[test] fn server_chip_state_tracks_connection_not_just_reachability() { let mut state = LauncherState::new(); diff --git a/client/src/launcher/ui_components/build_shell.rs b/client/src/launcher/ui_components/build_shell.rs index 281dd3a..fcdf6e6 100644 --- a/client/src/launcher/ui_components/build_shell.rs +++ b/client/src/launcher/ui_components/build_shell.rs @@ -77,7 +77,8 @@ let display_row = gtk::Box::new(gtk::Orientation::Horizontal, 8); display_row.set_hexpand(true); - display_row.set_vexpand(true); + display_row.set_vexpand(false); + display_row.set_valign(gtk::Align::Start); display_row.set_homogeneous(true); let left_pane = build_display_pane("Left Eye", "/dev/lesavka_l_eye"); let right_pane = build_display_pane("Right Eye", "/dev/lesavka_r_eye"); diff --git a/client/src/launcher/ui_components/display_pane.rs b/client/src/launcher/ui_components/display_pane.rs index 323c51a..4360f2a 100644 --- a/client/src/launcher/ui_components/display_pane.rs +++ b/client/src/launcher/ui_components/display_pane.rs @@ -34,10 +34,10 @@ fn build_display_pane(title: &str, capture_path: &str) -> DisplayPaneWidgets { preview_box.set_valign(gtk::Align::Fill); preview_box.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(true); - preview_frame.set_vexpand(true); - preview_frame.set_halign(gtk::Align::Fill); - preview_frame.set_valign(gtk::Align::Fill); + 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.set_size_request(EYE_PREVIEW_MIN_WIDTH, EYE_PREVIEW_MIN_HEIGHT); preview_frame.set_child(Some(&picture)); preview_box.append(&preview_frame); diff --git a/client/src/launcher/ui_components/style.rs b/client/src/launcher/ui_components/style.rs index 63696e9..3d8b833 100644 --- a/client/src/launcher/ui_components/style.rs +++ b/client/src/launcher/ui_components/style.rs @@ -115,7 +115,7 @@ pub fn install_css(window: >k::ApplicationWindow) { background: rgba(255, 255, 255, 0.08); } progressbar.audio-check-meter.vertical trough { - min-height: 116px; + min-height: 96px; } progressbar.audio-check-meter progress { border-radius: 999px; diff --git a/client/src/launcher/ui_components/types.rs b/client/src/launcher/ui_components/types.rs index ae895a2..4b3f240 100644 --- a/client/src/launcher/ui_components/types.rs +++ b/client/src/launcher/ui_components/types.rs @@ -184,8 +184,8 @@ const LESAVKA_ICON_SEARCH_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/ass const LAUNCHER_DEFAULT_WIDTH: i32 = 1360; const LAUNCHER_DEFAULT_HEIGHT: i32 = 900; const OPERATIONS_RAIL_WIDTH: i32 = 304; -const CAMERA_PREVIEW_VIEWPORT_HEIGHT: i32 = 158; +const CAMERA_PREVIEW_VIEWPORT_HEIGHT: i32 = 146; const CAMERA_PREVIEW_VIEWPORT_WIDTH: i32 = 280; -const EYE_PREVIEW_MIN_HEIGHT: i32 = 300; +const EYE_PREVIEW_MIN_HEIGHT: i32 = 258; const EYE_PREVIEW_MIN_WIDTH: i32 = 460; const SIDE_LOG_MIN_HEIGHT: i32 = 124; diff --git a/scripts/ci/hygiene_gate_baseline.json b/scripts/ci/hygiene_gate_baseline.json index 169e1c5..13cd09c 100644 --- a/scripts/ci/hygiene_gate_baseline.json +++ b/scripts/ci/hygiene_gate_baseline.json @@ -368,7 +368,7 @@ "client/src/launcher/ui_components/build_shell.rs": { "clippy_warnings": 0, "doc_debt": 0, - "loc": 110 + "loc": 111 }, "client/src/launcher/ui_components/combo_helpers.rs": { "clippy_warnings": 0, diff --git a/testing/tests/client_launcher_layout_contract.rs b/testing/tests/client_launcher_layout_contract.rs index 0d0fdea..cc75d6a 100644 --- a/testing/tests/client_launcher_layout_contract.rs +++ b/testing/tests/client_launcher_layout_contract.rs @@ -46,20 +46,21 @@ fn launcher_default_size_stays_inside_1080p() { const_i32("LAUNCHER_DEFAULT_HEIGHT") <= 900, "leave room for desktop panels and window chrome on a 1080p monitor" ); + assert!( + UI_LAYOUT_SRC + .contains("window.set_size_request(LAUNCHER_DEFAULT_WIDTH, LAUNCHER_DEFAULT_HEIGHT);") + ); } #[test] -fn eye_panes_keep_the_locked_larger_preview_footprint() { +fn eye_panes_keep_the_docked_preview_footprint_without_forcing_maximized_width() { assert_eq!(const_i32("EYE_PREVIEW_MIN_WIDTH"), 460); - assert_eq!(const_i32("EYE_PREVIEW_MIN_HEIGHT"), 300); - let estimated_min_width = 20 - + 8 - + const_i32("OPERATIONS_RAIL_WIDTH") - + 8 - + 2 * (const_i32("EYE_PREVIEW_MIN_WIDTH") + 32); + assert_eq!(const_i32("EYE_PREVIEW_MIN_HEIGHT"), 258); + assert!(UI_LAYOUT_SRC.contains("display_row.set_vexpand(false);")); + assert!(UI_LAYOUT_SRC.contains("display_row.set_valign(gtk::Align::Start);")); assert!( - estimated_min_width <= const_i32("LAUNCHER_DEFAULT_WIDTH"), - "the eye panes must not push the operations rail off a 1360px launcher" + !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("caption_label.set_halign(gtk::Align::End)") @@ -115,7 +116,7 @@ fn device_staging_and_testing_bottoms_stay_locked_together() { #[test] fn device_testing_keeps_webcam_and_mic_playback_as_equal_bottom_columns() { assert_eq!(const_i32("CAMERA_PREVIEW_VIEWPORT_WIDTH"), 280); - assert_eq!(const_i32("CAMERA_PREVIEW_VIEWPORT_HEIGHT"), 158); + assert_eq!(const_i32("CAMERA_PREVIEW_VIEWPORT_HEIGHT"), 146); assert!(UI_LAYOUT_SRC.contains("webcam_group.set_valign(gtk::Align::Fill);")); assert!(UI_LAYOUT_SRC.contains("playback_group.set_valign(gtk::Align::Fill);")); assert!(UI_LAYOUT_SRC.contains("preview_body.set_vexpand(false);")); @@ -184,6 +185,7 @@ fn relay_controls_keep_connect_inline_with_server_entry() { assert!(UI_LAYOUT_SRC.contains("build_panel(\"Relay Controls\")")); assert_eq!(const_i32("OPERATIONS_RAIL_WIDTH"), 304); assert_eq!(const_i32("RAIL_BUTTON_WIDTH"), 92); + assert_eq!(const_i32("RAIL_BUTTON_LABEL_CHARS"), 14); assert!(UI_LAYOUT_SRC.contains("let relay_grid = gtk::Grid::new();")); assert!(UI_LAYOUT_SRC.contains("relay_grid.set_column_homogeneous(true);")); assert!(UI_LAYOUT_SRC.contains("relay_grid.attach(&server_entry, 0, 0, 2, 1);"));