lesavka: stabilize diagnostics scrolling

This commit is contained in:
Brad Stein 2026-04-20 08:57:13 -03:00
parent c6f9001323
commit c24bef1bf2
7 changed files with 55 additions and 15 deletions

View File

@ -4,7 +4,7 @@ path = "src/main.rs"
[package] [package]
name = "lesavka_client" name = "lesavka_client"
version = "0.11.23" version = "0.11.24"
edition = "2024" edition = "2024"
[dependencies] [dependencies]

View File

@ -1359,7 +1359,12 @@ pub fn run_gui_launcher(server_addr: String) -> Result<()> {
let widgets = widgets.clone(); let widgets = widgets.clone();
let diagnostics_popout = Rc::clone(&diagnostics_popout); let diagnostics_popout = Rc::clone(&diagnostics_popout);
widgets.diagnostics_popout_button.connect_clicked(move |_| { widgets.diagnostics_popout_button.connect_clicked(move |_| {
open_diagnostics_popout(&app, &diagnostics_popout, &widgets.diagnostics_buffer); open_diagnostics_popout(
&app,
&diagnostics_popout,
&widgets.diagnostics_popout_scroll,
&widgets.diagnostics_buffer,
);
widgets widgets
.status_label .status_label
.set_text("Diagnostics report moved into its own window."); .set_text("Diagnostics report moved into its own window.");

View File

@ -54,6 +54,7 @@ pub struct LauncherWidgets {
pub diagnostics_log: Rc<RefCell<DiagnosticsLog>>, pub diagnostics_log: Rc<RefCell<DiagnosticsLog>>,
pub diagnostics_buffer: gtk::TextBuffer, pub diagnostics_buffer: gtk::TextBuffer,
pub diagnostics_scroll: gtk::ScrolledWindow, pub diagnostics_scroll: gtk::ScrolledWindow,
pub diagnostics_popout_scroll: Rc<RefCell<Option<gtk::ScrolledWindow>>>,
pub session_log_buffer: gtk::TextBuffer, pub session_log_buffer: gtk::TextBuffer,
pub session_log_view: gtk::TextView, pub session_log_view: gtk::TextView,
pub summary: SummaryWidgets, pub summary: SummaryWidgets,
@ -620,12 +621,14 @@ pub fn build_launcher_view(
state.breakout_size_options(1), state.breakout_size_options(1),
state.breakout_size_preset(1), state.breakout_size_preset(1),
); );
let diagnostics_popout_scroll = Rc::new(RefCell::new(None));
let widgets = LauncherWidgets { let widgets = LauncherWidgets {
status_label: status_label.clone(), status_label: status_label.clone(),
diagnostics_log: diagnostics_log.clone(), diagnostics_log: diagnostics_log.clone(),
diagnostics_buffer: diagnostics_buffer.clone(), diagnostics_buffer: diagnostics_buffer.clone(),
diagnostics_scroll: diagnostics_scroll.clone(), diagnostics_scroll: diagnostics_scroll.clone(),
diagnostics_popout_scroll: diagnostics_popout_scroll.clone(),
session_log_buffer: session_log_buffer.clone(), session_log_buffer: session_log_buffer.clone(),
session_log_view: session_log_view.clone(), session_log_view: session_log_view.clone(),
summary: SummaryWidgets { summary: SummaryWidgets {

View File

@ -948,15 +948,21 @@ pub fn refresh_diagnostics_report(
child_running: bool, child_running: bool,
) { ) {
let diagnostics_adjustment = widgets.diagnostics_scroll.vadjustment(); let diagnostics_adjustment = widgets.diagnostics_scroll.vadjustment();
let previous_value = diagnostics_adjustment.value();
let previous_max = let previous_max =
(diagnostics_adjustment.upper() - diagnostics_adjustment.page_size()).max(0.0); (diagnostics_adjustment.upper() - diagnostics_adjustment.page_size()).max(0.0);
let was_at_bottom = let was_at_bottom = previous_max <= 0.0 || previous_value >= (previous_max - 4.0);
previous_max <= 0.0 || diagnostics_adjustment.value() >= (previous_max - 4.0); let popout_adjustment = widgets
let previous_ratio = if previous_max > 0.0 { .diagnostics_popout_scroll
(diagnostics_adjustment.value() / previous_max).clamp(0.0, 1.0) .borrow()
} else { .as_ref()
0.0 .map(|scroll| scroll.vadjustment());
}; let popout_state = popout_adjustment.as_ref().map(|adjustment| {
let previous_value = adjustment.value();
let previous_max = (adjustment.upper() - adjustment.page_size()).max(0.0);
let was_at_bottom = previous_max <= 0.0 || previous_value >= (previous_max - 4.0);
(adjustment.clone(), previous_value, was_at_bottom)
});
let mut snapshot = SnapshotReport::from_state( let mut snapshot = SnapshotReport::from_state(
state, state,
&widgets.diagnostics_log.borrow(), &widgets.diagnostics_log.borrow(),
@ -976,9 +982,18 @@ pub fn refresh_diagnostics_report(
let target = if was_at_bottom { let target = if was_at_bottom {
max max
} else { } else {
(previous_ratio * max).clamp(0.0, max) previous_value.min(max)
}; };
diagnostics_adjustment.set_value(target); diagnostics_adjustment.set_value(target);
if let Some((adjustment, previous_value, was_at_bottom)) = popout_state {
let max = (adjustment.upper() - adjustment.page_size()).max(0.0);
let target = if was_at_bottom {
max
} else {
previous_value.min(max)
};
adjustment.set_value(target);
}
}); });
} }
@ -987,20 +1002,29 @@ pub fn open_session_log_popout(
handle: &Rc<RefCell<Option<gtk::ApplicationWindow>>>, handle: &Rc<RefCell<Option<gtk::ApplicationWindow>>>,
buffer: &gtk::TextBuffer, buffer: &gtk::TextBuffer,
) { ) {
open_text_buffer_popout(app, handle, buffer, "Lesavka Log", "Copy Log"); open_text_buffer_popout(app, handle, None, buffer, "Lesavka Log", "Copy Log");
} }
pub fn open_diagnostics_popout( pub fn open_diagnostics_popout(
app: &gtk::Application, app: &gtk::Application,
handle: &Rc<RefCell<Option<gtk::ApplicationWindow>>>, handle: &Rc<RefCell<Option<gtk::ApplicationWindow>>>,
scroll_handle: &Rc<RefCell<Option<gtk::ScrolledWindow>>>,
buffer: &gtk::TextBuffer, buffer: &gtk::TextBuffer,
) { ) {
open_text_buffer_popout(app, handle, buffer, "Lesavka Diagnostics", "Copy Report"); open_text_buffer_popout(
app,
handle,
Some(scroll_handle),
buffer,
"Lesavka Diagnostics",
"Copy Report",
);
} }
fn open_text_buffer_popout( fn open_text_buffer_popout(
app: &gtk::Application, app: &gtk::Application,
handle: &Rc<RefCell<Option<gtk::ApplicationWindow>>>, handle: &Rc<RefCell<Option<gtk::ApplicationWindow>>>,
scroll_handle: Option<&Rc<RefCell<Option<gtk::ScrolledWindow>>>>,
buffer: &gtk::TextBuffer, buffer: &gtk::TextBuffer,
title: &str, title: &str,
copy_button_label: &str, copy_button_label: &str,
@ -1041,6 +1065,9 @@ fn open_text_buffer_popout(
.vexpand(true) .vexpand(true)
.child(&view) .child(&view)
.build(); .build();
if let Some(scroll_handle) = scroll_handle {
*scroll_handle.borrow_mut() = Some(scroll.clone());
}
root.append(&scroll); root.append(&scroll);
window.set_child(Some(&root)); window.set_child(Some(&root));
window.maximize(); window.maximize();
@ -1054,8 +1081,12 @@ fn open_text_buffer_popout(
{ {
let handle = Rc::clone(handle); let handle = Rc::clone(handle);
let scroll_handle = scroll_handle.cloned();
window.connect_close_request(move |_| { window.connect_close_request(move |_| {
handle.borrow_mut().take(); handle.borrow_mut().take();
if let Some(scroll_handle) = &scroll_handle {
scroll_handle.borrow_mut().take();
}
glib::Propagation::Proceed glib::Propagation::Proceed
}); });
} }
@ -1115,6 +1146,7 @@ pub fn shutdown_launcher_runtime(
} }
if let Some(window) = diagnostics_popout.borrow_mut().take() { if let Some(window) = diagnostics_popout.borrow_mut().take() {
widgets.diagnostics_popout_scroll.borrow_mut().take();
window.set_child(Option::<&gtk::Widget>::None); window.set_child(Option::<&gtk::Widget>::None);
window.hide(); window.hide();
} }

View File

@ -1,6 +1,6 @@
[package] [package]
name = "lesavka_common" name = "lesavka_common"
version = "0.11.23" version = "0.11.24"
edition = "2024" edition = "2024"
build = "build.rs" build = "build.rs"

View File

@ -17,6 +17,6 @@ mod tests {
#[test] #[test]
fn banner_includes_version() { fn banner_includes_version() {
assert_eq!(banner("0.11.23"), "lesavka-common CLI (v0.11.23)"); assert_eq!(banner("0.11.24"), "lesavka-common CLI (v0.11.24)");
} }
} }

View File

@ -10,7 +10,7 @@ bench = false
[package] [package]
name = "lesavka_server" name = "lesavka_server"
version = "0.11.23" version = "0.11.24"
edition = "2024" edition = "2024"
autobins = false autobins = false