lesavka/client/src/launcher/ui_components/build_operations_rail.rs

236 lines
10 KiB
Rust

{
let (connection_panel, connection_body) = build_panel("Relay Controls");
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_text(server_addr);
server_entry.set_tooltip_text(Some("Relay host address."));
let relay_row = gtk::Box::new(gtk::Orientation::Horizontal, 8);
relay_row.set_halign(gtk::Align::Fill);
relay_row.set_hexpand(true);
relay_row.append(&server_entry);
let start_button = gtk::Button::with_label("Connect");
start_button.add_css_class("suggested-action");
start_button.set_hexpand(false);
stabilize_button(&start_button, 108);
relay_row.append(&start_button);
connection_body.append(&relay_row);
let live_actions_row = gtk::Box::new(gtk::Orientation::Horizontal, 8);
live_actions_row.set_homogeneous(true);
let clipboard_button = gtk::Button::with_label("Send Clipboard");
clipboard_button.set_hexpand(true);
stabilize_button(&clipboard_button, 108);
clipboard_button.set_tooltip_text(Some("Type clipboard remotely."));
let probe_button = gtk::Button::with_label("Copy Gate Probe");
probe_button.set_hexpand(true);
stabilize_button(&probe_button, 108);
probe_button.set_tooltip_text(Some("Copy quality probe."));
let usb_recover_button = gtk::Button::with_label("Recover USB");
usb_recover_button.set_hexpand(true);
stabilize_button(&usb_recover_button, 108);
usb_recover_button.set_tooltip_text(Some("Re-enumerate remote USB."));
live_actions_row.append(&clipboard_button);
live_actions_row.append(&probe_button);
live_actions_row.append(&usb_recover_button);
connection_body.append(&live_actions_row);
connection_body.append(&gtk::Separator::new(gtk::Orientation::Horizontal));
let power_heading = gtk::Label::new(Some("GPIO Power"));
power_heading.add_css_class("subgroup-title");
power_heading.set_halign(gtk::Align::Start);
let power_shell = gtk::Box::new(gtk::Orientation::Vertical, 6);
power_shell.set_halign(gtk::Align::Fill);
let power_row = gtk::Box::new(gtk::Orientation::Horizontal, 8);
power_row.set_hexpand(true);
power_heading.set_width_chars(10);
power_row.append(&power_heading);
let power_buttons = gtk::Box::new(gtk::Orientation::Horizontal, 8);
power_buttons.set_hexpand(true);
power_buttons.set_homogeneous(true);
let power_on_button = gtk::Button::with_label("On");
power_on_button.set_hexpand(true);
stabilize_button(&power_on_button, 52);
power_on_button.add_css_class("pill-toggle");
let power_auto_button = gtk::Button::with_label("Auto");
power_auto_button.set_hexpand(true);
stabilize_button(&power_auto_button, 52);
power_auto_button.add_css_class("pill-toggle");
let power_off_button = gtk::Button::with_label("Off");
power_off_button.set_hexpand(true);
stabilize_button(&power_off_button, 52);
power_off_button.add_css_class("pill-toggle");
let power_detail = gtk::Label::new(Some("Capture power status is loading..."));
power_detail.add_css_class("dim-label");
power_detail.set_wrap(true);
power_detail.set_xalign(0.0);
power_buttons.append(&power_on_button);
power_buttons.append(&power_auto_button);
power_buttons.append(&power_off_button);
power_row.append(&power_buttons);
power_shell.append(&power_row);
connection_body.append(&power_shell);
let routing_heading = gtk::Label::new(Some("Inputs"));
routing_heading.add_css_class("subgroup-title");
routing_heading.set_halign(gtk::Align::Start);
connection_body.append(&gtk::Separator::new(gtk::Orientation::Horizontal));
let routing_row = gtk::Box::new(gtk::Orientation::Horizontal, 8);
routing_row.set_hexpand(true);
routing_heading.set_width_chars(10);
routing_row.append(&routing_heading);
let routing_buttons = gtk::Box::new(gtk::Orientation::Horizontal, 8);
routing_buttons.set_hexpand(true);
routing_buttons.set_homogeneous(true);
let input_toggle_button = gtk::Button::with_label("Route");
input_toggle_button.set_hexpand(true);
stabilize_button(&input_toggle_button, 106);
input_toggle_button.set_tooltip_text(Some("Swap input ownership."));
let swap_key_button = gtk::Button::with_label("Set Swap Key");
swap_key_button.set_hexpand(true);
stabilize_button(&swap_key_button, 106);
routing_buttons.append(&input_toggle_button);
routing_buttons.append(&swap_key_button);
routing_row.append(&routing_buttons);
connection_body.append(&routing_row);
operations.append(&connection_panel);
let (diagnostics_panel, diagnostics_body) = build_panel("Diagnostics");
diagnostics_panel.set_vexpand(true);
diagnostics_panel.set_valign(gtk::Align::Fill);
diagnostics_body.set_vexpand(true);
let diagnostics_toolbar = gtk::Box::new(gtk::Orientation::Horizontal, 8);
diagnostics_toolbar.set_homogeneous(true);
let diagnostics_copy_button = gtk::Button::with_label("Copy Report");
stabilize_button(&diagnostics_copy_button, 112);
let diagnostics_popout_button = gtk::Button::with_label("Break Out");
stabilize_button(&diagnostics_popout_button, 112);
diagnostics_toolbar.append(&diagnostics_copy_button);
diagnostics_toolbar.append(&diagnostics_popout_button);
let diagnostics_log = Rc::new(RefCell::new(DiagnosticsLog::new(16)));
let diagnostics_label = gtk::Label::new(None);
diagnostics_label.add_css_class("status-log");
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_halign(gtk::Align::Start);
diagnostics_label.set_valign(gtk::Align::Start);
diagnostics_label.set_hexpand(true);
let diagnostics_shell = gtk::Box::new(gtk::Orientation::Vertical, 0);
diagnostics_shell.set_hexpand(true);
diagnostics_shell.set_vexpand(false);
diagnostics_shell.append(&diagnostics_label);
let diagnostics_scroll = gtk::ScrolledWindow::builder()
.hexpand(true)
.vexpand(true)
.min_content_height(SIDE_LOG_MIN_HEIGHT)
.child(&diagnostics_shell)
.build();
diagnostics_body.append(&diagnostics_toolbar);
diagnostics_body.append(&diagnostics_scroll);
operations.append(&diagnostics_panel);
let (console_panel, console_body) = build_panel("Session Console");
console_panel.set_vexpand(true);
console_panel.set_valign(gtk::Align::Fill);
console_body.set_vexpand(true);
let console_toolbar = gtk::Box::new(gtk::Orientation::Horizontal, 8);
let session_log_level = Rc::new(RefCell::new(ConsoleLogLevel::default()));
let console_level_combo = gtk::ComboBoxText::new();
for level in ConsoleLogLevel::ALL {
console_level_combo.append(Some(level.id()), level.label());
}
console_level_combo.set_active_id(Some(ConsoleLogLevel::default().id()));
console_level_combo.set_size_request(78, 36);
console_level_combo.set_tooltip_text(Some("Show relay logs at this level or higher."));
let console_copy_button = gtk::Button::with_label("Copy");
console_copy_button.set_tooltip_text(Some("Copy visible log."));
let console_popout_button = gtk::Button::with_label("Pop Out");
console_popout_button.set_tooltip_text(Some("Open log window."));
let console_buttons = gtk::Box::new(gtk::Orientation::Horizontal, 8);
console_buttons.set_hexpand(true);
console_buttons.set_homogeneous(true);
console_copy_button.set_hexpand(true);
console_popout_button.set_hexpand(true);
console_buttons.append(&console_copy_button);
console_buttons.append(&console_popout_button);
console_toolbar.append(&console_level_combo);
console_toolbar.append(&console_buttons);
let status_label = gtk::Label::new(Some("Session log ready."));
status_label.add_css_class("status-line");
status_label.set_halign(gtk::Align::Start);
status_label.set_wrap(true);
status_label.set_xalign(0.0);
let session_log_buffer = gtk::TextBuffer::new(None);
session_log_buffer.create_tag(Some("log-launcher"), &[("foreground", &"#8bd5ca")]);
session_log_buffer.create_tag(Some("log-relay"), &[("foreground", &"#89b4fa")]);
session_log_buffer.create_tag(Some("log-preview"), &[("foreground", &"#cba6f7")]);
session_log_buffer.create_tag(Some("log-stderr"), &[("foreground", &"#f9e2af")]);
session_log_buffer.create_tag(Some("log-warn"), &[("foreground", &"#fab387")]);
session_log_buffer.create_tag(Some("log-error"), &[("foreground", &"#f38ba8")]);
super::ui_runtime::append_session_log(&session_log_buffer, "[launcher] Session log ready.");
let session_log_view = gtk::TextView::with_buffer(&session_log_buffer);
session_log_view.add_css_class("status-log");
session_log_view.set_editable(false);
session_log_view.set_cursor_visible(false);
session_log_view.set_monospace(true);
session_log_view.set_wrap_mode(gtk::WrapMode::WordChar);
let log_scroll = gtk::ScrolledWindow::builder()
.hexpand(true)
.vexpand(true)
.min_content_height(SIDE_LOG_MIN_HEIGHT)
.child(&session_log_view)
.build();
console_body.append(&console_toolbar);
console_body.append(&log_scroll);
operations.append(&console_panel);
{
let buffer = session_log_buffer.clone();
let view = session_log_view.clone();
status_label.connect_notify_local(Some("label"), move |label, _| {
super::ui_runtime::append_session_log(&buffer, &format!("[launcher] {}", label.text()));
let mut end = buffer.end_iter();
view.scroll_to_iter(&mut end, 0.0, false, 0.0, 1.0);
});
}
let preview = match LauncherPreview::new(server_addr.to_string()) {
Ok(preview) => Some(Rc::new(preview)),
Err(err) => {
status_label.set_text(&format!("Preview unavailable: {err}"));
None
}
};
OperationsRailContext {
server_entry,
start_button,
clipboard_button,
probe_button,
usb_recover_button,
power_auto_button,
power_on_button,
power_off_button,
power_detail,
input_toggle_button,
swap_key_button,
diagnostics_copy_button,
diagnostics_popout_button,
diagnostics_log,
diagnostics_label,
diagnostics_scroll,
console_copy_button,
console_popout_button,
console_level_combo,
session_log_level,
status_label,
session_log_buffer,
session_log_view,
preview,
}
}