fix(ui): restore compact launcher layout
This commit is contained in:
parent
7fbba1314a
commit
09c877a204
@ -1,11 +1,13 @@
|
||||
use super::*;
|
||||
use crate::launcher::{
|
||||
devices::DeviceCatalog, preview::PreviewBinding, state::LauncherState,
|
||||
devices::{CameraMode, DeviceCatalog},
|
||||
preview::PreviewBinding,
|
||||
state::LauncherState,
|
||||
ui_components::build_launcher_view,
|
||||
};
|
||||
use gtk::prelude::*;
|
||||
use serial_test::serial;
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
use std::{cell::RefCell, collections::BTreeMap, rc::Rc};
|
||||
|
||||
#[test]
|
||||
fn local_test_detail_mentions_idle_and_running_modes() {
|
||||
@ -59,19 +61,121 @@ fn launcher_shell_measures_inside_a_1080p_desktop_budget() {
|
||||
let (min_height, natural_height, _, _) = view.window.measure(gtk::Orientation::Vertical, 1920);
|
||||
|
||||
assert!(
|
||||
min_width <= 1920 && natural_width <= 1920,
|
||||
min_width <= 1280 && natural_width <= 1280,
|
||||
"launcher width budget regressed: min={min_width}, natural={natural_width}"
|
||||
);
|
||||
assert!(
|
||||
tall_min_width <= 1920 && tall_natural_width <= 1920,
|
||||
tall_min_width <= 1280 && tall_natural_width <= 1280,
|
||||
"launcher 1080p-tall width regressed: min={tall_min_width}, natural={tall_natural_width}"
|
||||
);
|
||||
assert!(
|
||||
min_height <= 1080 && natural_height <= 1080,
|
||||
min_height <= 860 && natural_height <= 1080,
|
||||
"launcher height budget regressed: min={min_height}, natural={natural_height}"
|
||||
);
|
||||
}
|
||||
|
||||
#[gtk::test]
|
||||
#[serial]
|
||||
fn populated_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-populated")
|
||||
.build();
|
||||
let _ = app.register(None::<>k::gio::Cancellable>);
|
||||
|
||||
let catalog = realistic_device_catalog();
|
||||
let mut state = LauncherState::new();
|
||||
state.apply_catalog_defaults(&catalog);
|
||||
|
||||
let view = build_launcher_view(&app, "http://127.0.0.1:50051", &catalog, &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 <= 1280 && natural_width <= 1280,
|
||||
"populated launcher width budget regressed: min={min_width}, natural={natural_width}"
|
||||
);
|
||||
assert!(
|
||||
tall_min_width <= 1280 && tall_natural_width <= 1280,
|
||||
"populated launcher 1080p-tall width regressed: min={tall_min_width}, natural={tall_natural_width}"
|
||||
);
|
||||
assert!(
|
||||
min_height <= 860 && natural_height <= 1080,
|
||||
"populated launcher height budget regressed: min={min_height}, natural={natural_height}"
|
||||
);
|
||||
}
|
||||
|
||||
#[gtk::test]
|
||||
#[serial]
|
||||
fn populated_launcher_runtime_widgets_stay_compact() {
|
||||
if gtk::gdk::Display::default().is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
let app = gtk::Application::builder()
|
||||
.application_id("dev.lesavka.test-layout-widget-budget")
|
||||
.build();
|
||||
let _ = app.register(None::<>k::gio::Cancellable>);
|
||||
|
||||
let catalog = realistic_device_catalog();
|
||||
let mut state = LauncherState::new();
|
||||
state.apply_catalog_defaults(&catalog);
|
||||
|
||||
let view = build_launcher_view(&app, "http://127.0.0.1:50051", &catalog, &state);
|
||||
|
||||
let (camera_min_w, camera_nat_w, _, _) = view
|
||||
.device_stage
|
||||
.camera_preview
|
||||
.measure(gtk::Orientation::Horizontal, -1);
|
||||
let (camera_min_h, camera_nat_h, _, _) = view
|
||||
.device_stage
|
||||
.camera_preview
|
||||
.measure(gtk::Orientation::Vertical, 160);
|
||||
let (testing_panel_min_h, testing_panel_nat_h, _, _) = view
|
||||
.device_stage
|
||||
._preview_panel
|
||||
.measure(gtk::Orientation::Vertical, 320);
|
||||
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);
|
||||
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 preview width regressed: min={camera_min_w}, natural={camera_nat_w}"
|
||||
);
|
||||
assert!(
|
||||
camera_min_h <= 90 && camera_nat_h <= 90,
|
||||
"camera preview height regressed: min={camera_min_h}, natural={camera_nat_h}"
|
||||
);
|
||||
assert!(
|
||||
testing_panel_min_h <= 260 && testing_panel_nat_h <= 260,
|
||||
"device testing panel height regressed: min={testing_panel_min_h}, natural={testing_panel_nat_h}"
|
||||
);
|
||||
assert!(
|
||||
left_min_w <= 445 && left_nat_w <= 445,
|
||||
"eye pane width regressed: min={left_min_w}, natural={left_nat_w}"
|
||||
);
|
||||
assert!(
|
||||
left_min_h <= 520 && left_nat_h <= 520,
|
||||
"eye pane height regressed: min={left_min_h}, natural={left_nat_h}"
|
||||
);
|
||||
assert!(
|
||||
server_min_w <= 168 && server_nat_w <= 180,
|
||||
"server entry width regressed: min={server_min_w}, natural={server_nat_w}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn server_chip_state_tracks_connection_not_just_reachability() {
|
||||
let mut state = LauncherState::new();
|
||||
@ -292,6 +396,30 @@ fn dock_all_displays_to_preview_handles_reentrant_close_callbacks() {
|
||||
assert_eq!(state.borrow().display_surface(0), DisplaySurface::Preview);
|
||||
}
|
||||
|
||||
fn realistic_device_catalog() -> DeviceCatalog {
|
||||
DeviceCatalog {
|
||||
cameras: vec!["usb-046d_Logitech_BRIO_5F6EB379-video-index0".to_string()],
|
||||
camera_modes: [(
|
||||
"usb-046d_Logitech_BRIO_5F6EB379-video-index0".to_string(),
|
||||
vec![
|
||||
CameraMode::new(1920, 1080, 30),
|
||||
CameraMode::new(1280, 720, 30),
|
||||
],
|
||||
)]
|
||||
.into_iter()
|
||||
.collect::<BTreeMap<_, _>>(),
|
||||
microphones: vec![
|
||||
"alsa_input.usb-Focusrite_Scarlett_2i2_USB_Y7ABC12345-00.analog-stereo".to_string(),
|
||||
],
|
||||
speakers: vec![
|
||||
"alsa_output.pci-0000_00_1f.3.analog-stereo".to_string(),
|
||||
"bluez_output.80_C3_BA_76_26_AB.1".to_string(),
|
||||
],
|
||||
keyboards: vec!["usb-Corsair_K70_RGB_PRO_Mechanical_Gaming_Keyboard-event-kbd".to_string()],
|
||||
mice: vec!["usb-Logitech_G502_X_LIGHTSPEED_Gaming_Mouse-event-mouse".to_string()],
|
||||
}
|
||||
}
|
||||
|
||||
#[gtk::test]
|
||||
#[serial]
|
||||
fn shutdown_launcher_runtime_closes_preview_bindings_and_popouts() {
|
||||
|
||||
@ -37,6 +37,7 @@ use {
|
||||
gtk::prelude::*,
|
||||
lesavka_common::lesavka::CapturePowerCommand,
|
||||
lesavka_common::process_metrics::ProcessCpuSampler,
|
||||
serde_json::json,
|
||||
std::cell::{Cell, RefCell},
|
||||
std::collections::VecDeque,
|
||||
std::process::Command,
|
||||
@ -53,6 +54,10 @@ include!("ui/diagnostic_sampling.rs");
|
||||
include!("ui/preview_profiles.rs");
|
||||
#[cfg(not(coverage))]
|
||||
include!("ui/activation_context.rs");
|
||||
#[cfg(not(coverage))]
|
||||
include!("ui/startup_window_guard.rs");
|
||||
#[cfg(coverage)]
|
||||
include!("ui/session_preview_coverage.rs");
|
||||
|
||||
#[cfg(not(coverage))]
|
||||
pub fn run_gui_launcher(server_addr: String) -> Result<()> {
|
||||
@ -109,6 +114,7 @@ pub fn run_gui_launcher(server_addr: String) -> Result<()> {
|
||||
app.connect_activate(move |app| {
|
||||
let ActivationContext {
|
||||
window,
|
||||
launcher_size,
|
||||
server_entry,
|
||||
camera_combo,
|
||||
camera_quality_combo,
|
||||
@ -154,6 +160,7 @@ pub fn run_gui_launcher(server_addr: String) -> Result<()> {
|
||||
let _: () = include!("ui/runtime_poll.rs");
|
||||
|
||||
window.present();
|
||||
schedule_launcher_window_guard(app, &window, launcher_size);
|
||||
});
|
||||
}
|
||||
|
||||
@ -166,15 +173,6 @@ pub fn run_gui_launcher(_server_addr: String) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Keep the coverage stub aligned with the real preview activation rule.
|
||||
#[cfg(coverage)]
|
||||
fn session_preview_active(
|
||||
state: &crate::launcher::state::LauncherState,
|
||||
child_running: bool,
|
||||
) -> bool {
|
||||
(child_running || state.remote_active) && state.capture_power.mode != "forced-off"
|
||||
}
|
||||
|
||||
#[cfg(all(test, not(coverage)))]
|
||||
#[path = "tests/ui_preview_profiles.rs"]
|
||||
mod tests;
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
#[cfg(not(coverage))]
|
||||
struct ActivationContext {
|
||||
window: gtk::ApplicationWindow,
|
||||
launcher_size: (i32, i32),
|
||||
server_entry: gtk::Entry,
|
||||
camera_combo: gtk::ComboBoxText,
|
||||
camera_quality_combo: gtk::ComboBoxText,
|
||||
|
||||
@ -132,6 +132,7 @@
|
||||
|
||||
ActivationContext {
|
||||
window,
|
||||
launcher_size: (launcher_width, launcher_height),
|
||||
server_entry,
|
||||
camera_combo,
|
||||
camera_quality_combo,
|
||||
|
||||
7
client/src/launcher/ui/session_preview_coverage.rs
Normal file
7
client/src/launcher/ui/session_preview_coverage.rs
Normal file
@ -0,0 +1,7 @@
|
||||
/// Keep the coverage stub aligned with the real preview activation rule.
|
||||
fn session_preview_active(
|
||||
state: &crate::launcher::state::LauncherState,
|
||||
child_running: bool,
|
||||
) -> bool {
|
||||
(child_running || state.remote_active) && state.capture_power.mode != "forced-off"
|
||||
}
|
||||
53
client/src/launcher/ui/startup_window_guard.rs
Normal file
53
client/src/launcher/ui/startup_window_guard.rs
Normal file
@ -0,0 +1,53 @@
|
||||
/// Re-applies the compact launcher size after `present()` so persisted window-manager
|
||||
/// geometry cannot silently reopen the launcher too large for a 1080p desktop.
|
||||
fn schedule_launcher_window_guard(
|
||||
app: >k::Application,
|
||||
window: >k::ApplicationWindow,
|
||||
launcher_size: (i32, i32),
|
||||
) {
|
||||
let guard_window = window.clone();
|
||||
glib::timeout_add_local_once(Duration::from_millis(120), move || {
|
||||
if guard_window.is_maximized() {
|
||||
guard_window.unmaximize();
|
||||
}
|
||||
guard_window.set_default_size(launcher_size.0, launcher_size.1);
|
||||
guard_window.queue_allocate();
|
||||
});
|
||||
|
||||
let Ok(path) = std::env::var("LESAVKA_LAUNCHER_MEASURE_PATH") else {
|
||||
return;
|
||||
};
|
||||
let measure_window = window.clone();
|
||||
let app = app.clone();
|
||||
glib::timeout_add_local_once(Duration::from_millis(320), move || {
|
||||
write_launcher_measurement(&measure_window, launcher_size, &path);
|
||||
if std::env::var("LESAVKA_LAUNCHER_MEASURE_EXIT").ok().as_deref() == Some("1") {
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Emits a one-shot launcher size snapshot for local verification runs.
|
||||
fn write_launcher_measurement(
|
||||
window: >k::ApplicationWindow,
|
||||
launcher_size: (i32, i32),
|
||||
path: &str,
|
||||
) {
|
||||
let (min_width, natural_width, _, _) = window.measure(gtk::Orientation::Horizontal, -1);
|
||||
let (min_height, natural_height, _, _) =
|
||||
window.measure(gtk::Orientation::Vertical, launcher_size.0);
|
||||
let payload = json!({
|
||||
"requested_width": launcher_size.0,
|
||||
"requested_height": launcher_size.1,
|
||||
"window_width": window.width(),
|
||||
"window_height": window.height(),
|
||||
"min_width": min_width,
|
||||
"natural_width": natural_width,
|
||||
"min_height": min_height,
|
||||
"natural_height": natural_height,
|
||||
});
|
||||
let _ = std::fs::write(
|
||||
path,
|
||||
serde_json::to_string_pretty(&payload).unwrap_or_else(|_| payload.to_string()),
|
||||
);
|
||||
}
|
||||
@ -61,7 +61,7 @@ pub fn build_launcher_view(
|
||||
mic_gain_value,
|
||||
audio_check_detail,
|
||||
audio_check_meter,
|
||||
device_body_height_group,
|
||||
preview_panel,
|
||||
camera_preview,
|
||||
camera_status,
|
||||
camera_test_button,
|
||||
|
||||
@ -148,7 +148,6 @@
|
||||
console_popout_button: console_popout_button.clone(),
|
||||
console_level_combo: console_level_combo.clone(),
|
||||
session_log_level: session_log_level.clone(),
|
||||
_device_body_height_group: device_body_height_group,
|
||||
};
|
||||
let popouts = Rc::new(RefCell::new([None, None]));
|
||||
let diagnostics_popout = Rc::new(RefCell::new(None));
|
||||
@ -168,6 +167,7 @@
|
||||
keyboard_combo,
|
||||
mouse_combo,
|
||||
device_stage: DeviceStageWidgets {
|
||||
_preview_panel: preview_panel,
|
||||
camera_preview,
|
||||
camera_status,
|
||||
},
|
||||
|
||||
@ -31,7 +31,7 @@ struct DeviceControlsContext {
|
||||
mic_gain_value: gtk::Label,
|
||||
audio_check_detail: gtk::Label,
|
||||
audio_check_meter: gtk::ProgressBar,
|
||||
device_body_height_group: gtk::SizeGroup,
|
||||
preview_panel: gtk::Box,
|
||||
camera_preview: gtk::Picture,
|
||||
camera_status: gtk::Label,
|
||||
camera_test_button: gtk::Button,
|
||||
|
||||
@ -185,24 +185,21 @@
|
||||
staging_row.append(&devices_panel);
|
||||
|
||||
let (preview_panel, preview_body) = build_panel("Device Testing");
|
||||
preview_panel.set_hexpand(true);
|
||||
preview_panel.set_hexpand(false);
|
||||
preview_panel.set_vexpand(false);
|
||||
preview_panel.set_valign(gtk::Align::Fill);
|
||||
preview_body.set_vexpand(false);
|
||||
preview_body.set_spacing(8);
|
||||
let testing_row = gtk::Box::new(gtk::Orientation::Horizontal, 8);
|
||||
testing_row.set_hexpand(true);
|
||||
testing_row.set_vexpand(true);
|
||||
testing_row.set_valign(gtk::Align::Fill);
|
||||
let device_body_height_group = gtk::SizeGroup::new(gtk::SizeGroupMode::Vertical);
|
||||
device_body_height_group.add_widget(&devices_body);
|
||||
device_body_height_group.add_widget(&testing_row);
|
||||
testing_row.set_hexpand(false);
|
||||
testing_row.set_vexpand(false);
|
||||
testing_row.set_valign(gtk::Align::Start);
|
||||
let camera_preview = gtk::Picture::new();
|
||||
camera_preview.set_can_shrink(false);
|
||||
camera_preview.set_hexpand(true);
|
||||
camera_preview.set_vexpand(true);
|
||||
camera_preview.set_can_shrink(true);
|
||||
camera_preview.set_hexpand(false);
|
||||
camera_preview.set_vexpand(false);
|
||||
camera_preview.set_halign(gtk::Align::Fill);
|
||||
camera_preview.set_valign(gtk::Align::Fill);
|
||||
camera_preview.set_valign(gtk::Align::Start);
|
||||
camera_preview.set_size_request(
|
||||
CAMERA_PREVIEW_VIEWPORT_WIDTH,
|
||||
CAMERA_PREVIEW_VIEWPORT_HEIGHT,
|
||||
@ -216,19 +213,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(true);
|
||||
camera_preview_shell.set_vexpand(true);
|
||||
camera_preview_shell.set_hexpand(false);
|
||||
camera_preview_shell.set_vexpand(false);
|
||||
camera_preview_shell.set_halign(gtk::Align::Fill);
|
||||
camera_preview_shell.set_valign(gtk::Align::Fill);
|
||||
camera_preview_shell.set_valign(gtk::Align::Start);
|
||||
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(true);
|
||||
camera_preview_frame.set_vexpand(true);
|
||||
camera_preview_frame.set_hexpand(false);
|
||||
camera_preview_frame.set_vexpand(false);
|
||||
camera_preview_frame.set_halign(gtk::Align::Fill);
|
||||
camera_preview_frame.set_valign(gtk::Align::Fill);
|
||||
camera_preview_frame.set_valign(gtk::Align::Start);
|
||||
camera_preview_frame.set_size_request(
|
||||
CAMERA_PREVIEW_VIEWPORT_WIDTH,
|
||||
CAMERA_PREVIEW_VIEWPORT_HEIGHT,
|
||||
@ -236,27 +233,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(true);
|
||||
webcam_group.set_vexpand(true);
|
||||
webcam_group.set_valign(gtk::Align::Fill);
|
||||
webcam_group.set_hexpand(false);
|
||||
webcam_group.set_vexpand(false);
|
||||
webcam_group.set_valign(gtk::Align::Start);
|
||||
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(true);
|
||||
playback_group.set_vexpand(false);
|
||||
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(true);
|
||||
playback_body.set_vexpand(false);
|
||||
playback_body.set_valign(gtk::Align::Fill);
|
||||
let microphone_replay_button = gtk::Button::with_label("Replay");
|
||||
stabilize_button(µphone_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(true);
|
||||
audio_check_meter.set_vexpand(false);
|
||||
audio_check_meter.set_halign(gtk::Align::Center);
|
||||
audio_check_meter.set_size_request(20, 0);
|
||||
audio_check_meter.set_show_text(false);
|
||||
@ -285,7 +282,7 @@
|
||||
mic_gain_value,
|
||||
audio_check_detail,
|
||||
audio_check_meter,
|
||||
device_body_height_group,
|
||||
preview_panel,
|
||||
camera_preview,
|
||||
camera_status,
|
||||
camera_test_button,
|
||||
|
||||
@ -3,12 +3,12 @@
|
||||
let server_entry = gtk::Entry::new();
|
||||
server_entry.add_css_class("server-entry");
|
||||
server_entry.set_hexpand(true);
|
||||
server_entry.set_width_chars(18);
|
||||
server_entry.set_width_chars(14);
|
||||
server_entry.set_text(server_addr);
|
||||
server_entry.set_tooltip_text(Some("Relay host address."));
|
||||
let relay_grid = gtk::Grid::new();
|
||||
relay_grid.set_column_homogeneous(true);
|
||||
relay_grid.set_column_spacing(8);
|
||||
relay_grid.set_column_spacing(6);
|
||||
relay_grid.set_hexpand(true);
|
||||
relay_grid.set_row_spacing(8);
|
||||
relay_grid.attach(&server_entry, 0, 0, 2, 1);
|
||||
@ -17,8 +17,8 @@
|
||||
start_button.add_css_class("suggested-action");
|
||||
relay_grid.attach(&start_button, 2, 0, 1, 1);
|
||||
|
||||
let clipboard_button = rail_button("Send Clipboard", "Type clipboard remotely.");
|
||||
let probe_button = rail_button("Copy Gate Probe", "Copy quality probe.");
|
||||
let clipboard_button = rail_button("Clipboard", "Type clipboard remotely.");
|
||||
let probe_button = rail_button("Gate Probe", "Copy quality probe.");
|
||||
let usb_recover_button = rail_button("Recover USB", "Re-enumerate remote USB.");
|
||||
relay_grid.attach(&clipboard_button, 0, 1, 1, 1);
|
||||
relay_grid.attach(&probe_button, 1, 1, 1, 1);
|
||||
@ -104,10 +104,12 @@
|
||||
diagnostics_label.set_selectable(true);
|
||||
diagnostics_label.set_xalign(0.0);
|
||||
diagnostics_label.set_yalign(0.0);
|
||||
diagnostics_label.set_wrap(false);
|
||||
diagnostics_label.set_wrap(true);
|
||||
diagnostics_label.set_wrap_mode(pango::WrapMode::WordChar);
|
||||
diagnostics_label.set_halign(gtk::Align::Start);
|
||||
diagnostics_label.set_valign(gtk::Align::Start);
|
||||
diagnostics_label.set_hexpand(true);
|
||||
diagnostics_label.set_width_chars(1);
|
||||
let diagnostics_shell = gtk::Box::new(gtk::Orientation::Vertical, 0);
|
||||
diagnostics_shell.set_hexpand(true);
|
||||
diagnostics_shell.set_vexpand(false);
|
||||
@ -118,6 +120,7 @@
|
||||
.min_content_height(SIDE_LOG_MIN_HEIGHT)
|
||||
.child(&diagnostics_shell)
|
||||
.build();
|
||||
diagnostics_scroll.set_propagate_natural_width(false);
|
||||
diagnostics_body.append(&diagnostics_toolbar);
|
||||
diagnostics_body.append(&diagnostics_scroll);
|
||||
operations.append(&diagnostics_panel);
|
||||
@ -173,6 +176,7 @@
|
||||
.min_content_height(SIDE_LOG_MIN_HEIGHT)
|
||||
.child(&session_log_view)
|
||||
.build();
|
||||
log_scroll.set_propagate_natural_width(false);
|
||||
console_body.append(&console_toolbar);
|
||||
console_body.append(&log_scroll);
|
||||
operations.append(&console_panel);
|
||||
|
||||
@ -9,12 +9,12 @@
|
||||
install_css(&window);
|
||||
install_window_icon(&window);
|
||||
|
||||
let root = gtk::Box::new(gtk::Orientation::Vertical, 8);
|
||||
let root = gtk::Box::new(gtk::Orientation::Vertical, 6);
|
||||
root.add_css_class("launcher-root");
|
||||
root.set_margin_start(10);
|
||||
root.set_margin_end(10);
|
||||
root.set_margin_top(10);
|
||||
root.set_margin_bottom(10);
|
||||
root.set_margin_start(7);
|
||||
root.set_margin_end(7);
|
||||
root.set_margin_top(8);
|
||||
root.set_margin_bottom(8);
|
||||
|
||||
let hero = gtk::Box::new(gtk::Orientation::Horizontal, 8);
|
||||
hero.set_hexpand(true);
|
||||
@ -57,24 +57,24 @@
|
||||
hero.append(&chips);
|
||||
root.append(&hero);
|
||||
|
||||
let content = gtk::Box::new(gtk::Orientation::Horizontal, 8);
|
||||
let content = gtk::Box::new(gtk::Orientation::Horizontal, 5);
|
||||
content.set_hexpand(true);
|
||||
content.set_vexpand(true);
|
||||
root.append(&content);
|
||||
|
||||
let workspace = gtk::Box::new(gtk::Orientation::Vertical, 8);
|
||||
let workspace = gtk::Box::new(gtk::Orientation::Vertical, 6);
|
||||
workspace.set_hexpand(true);
|
||||
workspace.set_vexpand(true);
|
||||
content.append(&workspace);
|
||||
|
||||
let operations = gtk::Box::new(gtk::Orientation::Vertical, 8);
|
||||
let operations = gtk::Box::new(gtk::Orientation::Vertical, 6);
|
||||
operations.set_size_request(OPERATIONS_RAIL_WIDTH, -1);
|
||||
operations.set_hexpand(false);
|
||||
operations.set_vexpand(true);
|
||||
operations.set_valign(gtk::Align::Fill);
|
||||
content.append(&operations);
|
||||
|
||||
let display_row = gtk::Box::new(gtk::Orientation::Horizontal, 8);
|
||||
let display_row = gtk::Box::new(gtk::Orientation::Horizontal, 6);
|
||||
display_row.set_hexpand(true);
|
||||
display_row.set_vexpand(false);
|
||||
display_row.set_valign(gtk::Align::Start);
|
||||
@ -85,11 +85,11 @@
|
||||
display_row.append(&right_pane.root);
|
||||
workspace.append(&display_row);
|
||||
|
||||
let staging_row = gtk::Box::new(gtk::Orientation::Horizontal, 8);
|
||||
let staging_row = gtk::Box::new(gtk::Orientation::Horizontal, 6);
|
||||
staging_row.set_hexpand(true);
|
||||
staging_row.set_vexpand(false);
|
||||
staging_row.set_valign(gtk::Align::Start);
|
||||
staging_row.set_homogeneous(true);
|
||||
staging_row.set_homogeneous(false);
|
||||
workspace.append(&staging_row);
|
||||
|
||||
LauncherShellContext {
|
||||
|
||||
@ -128,7 +128,7 @@ fn build_inline_selector_row(label: &str, combo: >k::ComboBoxText) -> gtk::Box
|
||||
let block = gtk::Box::new(gtk::Orientation::Horizontal, 8);
|
||||
let label_widget = gtk::Label::new(Some(label));
|
||||
label_widget.set_halign(gtk::Align::Start);
|
||||
label_widget.set_width_chars(9);
|
||||
label_widget.set_width_chars(7);
|
||||
label_widget.set_xalign(0.0);
|
||||
combo.set_hexpand(true);
|
||||
combo.set_size_request(0, -1);
|
||||
@ -157,9 +157,9 @@ fn append_input_choice(combo: >k::ComboBoxText, value: &str) {
|
||||
let short = value.rsplit('/').next().unwrap_or(value);
|
||||
let label = Device::open(value)
|
||||
.ok()
|
||||
.and_then(|device| device.name().map(|name| format!("{name} • {short}")))
|
||||
.and_then(|device| device.name().map(ToString::to_string))
|
||||
.unwrap_or_else(|| short.to_string());
|
||||
combo.append(Some(value), &label);
|
||||
combo.append(Some(value), &shorten_input_label(&label));
|
||||
}
|
||||
|
||||
fn append_stage_choice(combo: >k::ComboBoxText, value: &str) {
|
||||
@ -243,9 +243,9 @@ fn human_audio_node_label(value: &str) -> String {
|
||||
.replace(['-', '_'], " ");
|
||||
if compact.starts_with("pci ") || compact.starts_with("pci-") {
|
||||
if compact.contains("analog stereo") {
|
||||
"Built-in analog stereo".to_string()
|
||||
"Analog stereo".to_string()
|
||||
} else {
|
||||
"Built-in audio".to_string()
|
||||
"System audio".to_string()
|
||||
}
|
||||
} else {
|
||||
compact
|
||||
@ -253,10 +253,17 @@ fn human_audio_node_label(value: &str) -> String {
|
||||
}
|
||||
|
||||
fn shorten_label(value: &str) -> String {
|
||||
const MAX: usize = 44;
|
||||
shorten_label_with_limit(value, 20)
|
||||
}
|
||||
|
||||
fn shorten_input_label(value: &str) -> String {
|
||||
shorten_label_with_limit(value, 22)
|
||||
}
|
||||
|
||||
fn shorten_label_with_limit(value: &str, max: usize) -> String {
|
||||
let compact = value.replace('_', " ");
|
||||
let mut chars = compact.chars();
|
||||
let preview: String = chars.by_ref().take(MAX).collect();
|
||||
let preview: String = chars.by_ref().take(max).collect();
|
||||
if chars.next().is_some() {
|
||||
format!("{preview}…")
|
||||
} else {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
const RAIL_BUTTON_WIDTH: i32 = 92;
|
||||
const RAIL_BUTTON_LABEL_CHARS: i32 = 14;
|
||||
const RAIL_BUTTON_WIDTH: i32 = 86;
|
||||
const RAIL_BUTTON_LABEL_CHARS: i32 = 11;
|
||||
|
||||
/// Build a rail button that can shrink without forcing the operations column wider.
|
||||
fn rail_button(label: &str, tooltip: &str) -> gtk::Button {
|
||||
|
||||
@ -2,7 +2,7 @@ fn build_display_pane(title: &str, capture_path: &str) -> DisplayPaneWidgets {
|
||||
let root = gtk::Box::new(gtk::Orientation::Vertical, 10);
|
||||
root.add_css_class("display-card");
|
||||
root.set_hexpand(true);
|
||||
root.set_vexpand(true);
|
||||
root.set_vexpand(false);
|
||||
|
||||
let header = gtk::Box::new(gtk::Orientation::Horizontal, 8);
|
||||
header.set_hexpand(true);
|
||||
@ -20,18 +20,18 @@ fn build_display_pane(title: &str, capture_path: &str) -> DisplayPaneWidgets {
|
||||
|
||||
let picture = gtk::Picture::new();
|
||||
picture.set_hexpand(true);
|
||||
picture.set_vexpand(true);
|
||||
picture.set_vexpand(false);
|
||||
picture.set_halign(gtk::Align::Fill);
|
||||
picture.set_valign(gtk::Align::Fill);
|
||||
picture.set_valign(gtk::Align::Start);
|
||||
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_box = gtk::Box::new(gtk::Orientation::Vertical, 0);
|
||||
preview_box.set_hexpand(true);
|
||||
preview_box.set_vexpand(true);
|
||||
preview_box.set_vexpand(false);
|
||||
preview_box.set_halign(gtk::Align::Fill);
|
||||
preview_box.set_valign(gtk::Align::Fill);
|
||||
preview_box.set_valign(gtk::Align::Start);
|
||||
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(false);
|
||||
@ -53,13 +53,13 @@ fn build_display_pane(title: &str, capture_path: &str) -> DisplayPaneWidgets {
|
||||
let placeholder_box = gtk::Box::new(gtk::Orientation::Vertical, 6);
|
||||
placeholder_box.add_css_class("display-placeholder");
|
||||
placeholder_box.set_hexpand(true);
|
||||
placeholder_box.set_vexpand(true);
|
||||
placeholder_box.set_vexpand(false);
|
||||
placeholder_box.set_size_request(EYE_PREVIEW_MIN_WIDTH, EYE_PREVIEW_MIN_HEIGHT);
|
||||
placeholder_box.append(&placeholder);
|
||||
|
||||
let stack = gtk::Stack::new();
|
||||
stack.set_hexpand(true);
|
||||
stack.set_vexpand(true);
|
||||
stack.set_vexpand(false);
|
||||
stack.add_named(&preview_box, Some("preview"));
|
||||
stack.add_named(&placeholder_box, Some("placeholder"));
|
||||
stack.set_visible_child_name("preview");
|
||||
@ -81,7 +81,7 @@ fn build_display_pane(title: &str, capture_path: &str) -> DisplayPaneWidgets {
|
||||
breakout_combo.set_size_request(0, -1);
|
||||
breakout_combo.set_hexpand(true);
|
||||
let action_button = gtk::Button::with_label("Break Out");
|
||||
stabilize_button(&action_button, 104);
|
||||
stabilize_button(&action_button, 96);
|
||||
action_button.set_halign(gtk::Align::End);
|
||||
let stream_status = gtk::Label::new(Some("Preview pending"));
|
||||
stream_status.add_css_class("status-line");
|
||||
@ -91,17 +91,18 @@ fn build_display_pane(title: &str, capture_path: &str) -> DisplayPaneWidgets {
|
||||
stream_status.set_hexpand(true);
|
||||
stream_status.set_ellipsize(pango::EllipsizeMode::End);
|
||||
stream_status.set_single_line_mode(true);
|
||||
stream_status.set_width_chars(12);
|
||||
stream_status.set_max_width_chars(18);
|
||||
stream_status.set_width_chars(10);
|
||||
stream_status.set_max_width_chars(16);
|
||||
stream_status.set_tooltip_text(Some("Eye stream status."));
|
||||
let footer_shell = gtk::Box::new(gtk::Orientation::Vertical, 6);
|
||||
footer_shell.set_vexpand(false);
|
||||
let controls_grid = gtk::Grid::new();
|
||||
controls_grid.set_column_spacing(8);
|
||||
controls_grid.set_row_spacing(8);
|
||||
controls_grid.set_hexpand(true);
|
||||
let feed_row = build_inline_combo_row("Feed", &feed_source_combo, 7);
|
||||
let capture_row = build_inline_combo_row("Capture", &capture_resolution_combo, 7);
|
||||
let breakout_row = build_inline_combo_row("Display", &breakout_combo, 7);
|
||||
let feed_row = build_inline_combo_row("Feed", &feed_source_combo, 6);
|
||||
let capture_row = build_inline_combo_row("Capture", &capture_resolution_combo, 6);
|
||||
let breakout_row = build_inline_combo_row("Display", &breakout_combo, 6);
|
||||
feed_row.set_hexpand(true);
|
||||
capture_row.set_hexpand(true);
|
||||
breakout_row.set_hexpand(true);
|
||||
|
||||
@ -153,11 +153,11 @@ pub struct LauncherWidgets {
|
||||
pub console_popout_button: gtk::Button,
|
||||
pub console_level_combo: gtk::ComboBoxText,
|
||||
pub session_log_level: Rc<RefCell<ConsoleLogLevel>>,
|
||||
pub _device_body_height_group: gtk::SizeGroup,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DeviceStageWidgets {
|
||||
pub _preview_panel: gtk::Box,
|
||||
pub camera_preview: gtk::Picture,
|
||||
pub camera_status: gtk::Label,
|
||||
}
|
||||
@ -183,9 +183,9 @@ 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 OPERATIONS_RAIL_WIDTH: i32 = 304;
|
||||
const CAMERA_PREVIEW_VIEWPORT_HEIGHT: i32 = 146;
|
||||
const CAMERA_PREVIEW_VIEWPORT_WIDTH: i32 = 280;
|
||||
const EYE_PREVIEW_MIN_HEIGHT: i32 = 258;
|
||||
const EYE_PREVIEW_MIN_WIDTH: i32 = 460;
|
||||
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 SIDE_LOG_MIN_HEIGHT: i32 = 124;
|
||||
|
||||
@ -7,6 +7,8 @@
|
||||
//! regressions can hide diagnostics or make eye/device previews unusable.
|
||||
|
||||
const UI_LAYOUT_SRC: &str = concat!(
|
||||
include_str!("../../client/src/launcher/ui.rs"),
|
||||
include_str!("../../client/src/launcher/ui/startup_window_guard.rs"),
|
||||
include_str!("../../client/src/launcher/ui_components/types.rs"),
|
||||
include_str!("../../client/src/launcher/ui_components/build_shell.rs"),
|
||||
include_str!("../../client/src/launcher/ui/preview_profiles.rs"),
|
||||
@ -52,22 +54,22 @@ fn launcher_default_size_stays_inside_1080p() {
|
||||
.contains("window.set_size_request(LAUNCHER_DEFAULT_WIDTH, LAUNCHER_DEFAULT_HEIGHT);"),
|
||||
"the top-level window should not pin a larger minimum than the startup budget"
|
||||
);
|
||||
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("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))"),
|
||||
"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);"));
|
||||
assert!(
|
||||
UI_LAYOUT_SRC.contains("guard_window.set_default_size(launcher_size.0, launcher_size.1);")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
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"), 258);
|
||||
assert_eq!(const_i32("EYE_PREVIEW_MIN_WIDTH"), 360);
|
||||
assert_eq!(const_i32("EYE_PREVIEW_MIN_HEIGHT"), 203);
|
||||
assert!(UI_LAYOUT_SRC.contains("display_row.set_vexpand(false);"));
|
||||
assert!(UI_LAYOUT_SRC.contains("display_row.set_valign(gtk::Align::Start);"));
|
||||
assert!(
|
||||
@ -96,6 +98,8 @@ fn eye_and_device_combos_use_compact_labels_so_the_rail_stays_visible() {
|
||||
assert!(UI_LAYOUT_SRC.contains("fn compact_capture_mode_label("));
|
||||
assert!(UI_LAYOUT_SRC.contains("format!(\"{size}@{fps}\")"));
|
||||
assert!(UI_LAYOUT_SRC.contains("format!(\"Source {}\", compact_size_label(option.height))"));
|
||||
assert!(UI_LAYOUT_SRC.contains("fn shorten_input_label("));
|
||||
assert!(UI_LAYOUT_SRC.contains("shorten_label_with_limit(value, 22)"));
|
||||
assert!(
|
||||
!UI_LAYOUT_SRC.contains("@ {} fps (Device H.264)"),
|
||||
"long capture labels force a huge GTK combo natural width"
|
||||
@ -113,27 +117,33 @@ fn eye_and_device_combos_use_compact_labels_so_the_rail_stays_visible() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn device_staging_and_testing_bottoms_stay_locked_together() {
|
||||
assert!(UI_LAYOUT_SRC.contains("staging_row.set_homogeneous(true);"));
|
||||
fn device_staging_and_testing_stay_independent_so_preview_does_not_fill_dead_height() {
|
||||
assert!(UI_LAYOUT_SRC.contains("staging_row.set_homogeneous(false);"));
|
||||
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(
|
||||
"let device_body_height_group = gtk::SizeGroup::new(gtk::SizeGroupMode::Vertical);"
|
||||
));
|
||||
assert!(UI_LAYOUT_SRC.contains("device_body_height_group.add_widget(&devices_body);"));
|
||||
assert!(UI_LAYOUT_SRC.contains("device_body_height_group.add_widget(&testing_row);"));
|
||||
assert!(UI_LAYOUT_SRC.contains("preview_panel.set_hexpand(false);"));
|
||||
assert!(
|
||||
!UI_LAYOUT_SRC.contains("gtk::SizeGroup::new(gtk::SizeGroupMode::Vertical)"),
|
||||
"the webcam testing column must not inherit the full height of the staging controls"
|
||||
);
|
||||
}
|
||||
|
||||
#[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"), 146);
|
||||
assert!(UI_LAYOUT_SRC.contains("webcam_group.set_valign(gtk::Align::Fill);"));
|
||||
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!(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("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_body.set_valign(gtk::Align::Fill);"));
|
||||
assert!(UI_LAYOUT_SRC.contains("audio_check_meter.set_vexpand(true);"));
|
||||
assert!(UI_LAYOUT_SRC.contains("audio_check_meter.set_vexpand(false);"));
|
||||
assert!(UI_LAYOUT_SRC.contains("playback_body.append(&audio_check_meter);"));
|
||||
assert!(UI_LAYOUT_SRC.contains("playback_body.append(µphone_replay_button);"));
|
||||
}
|
||||
@ -195,17 +205,17 @@ fn status_chip_text_is_centered_inside_each_pill() {
|
||||
#[test]
|
||||
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_eq!(const_i32("OPERATIONS_RAIL_WIDTH"), 288);
|
||||
assert_eq!(const_i32("RAIL_BUTTON_WIDTH"), 86);
|
||||
assert_eq!(const_i32("RAIL_BUTTON_LABEL_CHARS"), 11);
|
||||
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);"));
|
||||
assert!(UI_LAYOUT_SRC.contains("let start_button = rail_button(\"Connect\""));
|
||||
assert!(UI_LAYOUT_SRC.contains("pub(crate) fn set_rail_button_label("));
|
||||
assert!(UI_LAYOUT_SRC.contains("relay_grid.attach(&start_button, 2, 0, 1, 1);"));
|
||||
assert!(UI_LAYOUT_SRC.contains("let clipboard_button = rail_button(\"Send Clipboard\""));
|
||||
assert!(UI_LAYOUT_SRC.contains("let probe_button = rail_button(\"Copy Gate Probe\""));
|
||||
assert!(UI_LAYOUT_SRC.contains("let clipboard_button = rail_button(\"Clipboard\""));
|
||||
assert!(UI_LAYOUT_SRC.contains("let probe_button = rail_button(\"Gate Probe\""));
|
||||
assert!(UI_LAYOUT_SRC.contains("let usb_recover_button = rail_button(\"Recover USB\""));
|
||||
assert!(UI_LAYOUT_SRC.contains("relay_grid.attach(&clipboard_button, 0, 1, 1, 1);"));
|
||||
assert!(UI_LAYOUT_SRC.contains("relay_grid.attach(&probe_button, 1, 1, 1, 1);"));
|
||||
@ -246,6 +256,7 @@ fn media_controls_own_stream_toggles_and_inline_gain_controls() {
|
||||
);
|
||||
assert!(UI_LAYOUT_SRC.contains("speaker_selectors.append(&speaker_combo);"));
|
||||
assert!(UI_LAYOUT_SRC.contains("speaker_selectors.append(&audio_gain_scale);"));
|
||||
assert!(UI_LAYOUT_SRC.contains("label_widget.set_width_chars(7);"));
|
||||
assert!(
|
||||
UI_LAYOUT_SRC
|
||||
.contains("let microphone_selectors = gtk::Box::new(gtk::Orientation::Horizontal, 6);")
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user