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]
name = "lesavka_client"
version = "0.11.23"
version = "0.11.24"
edition = "2024"
[dependencies]

View File

@ -1359,7 +1359,12 @@ pub fn run_gui_launcher(server_addr: String) -> Result<()> {
let widgets = widgets.clone();
let diagnostics_popout = Rc::clone(&diagnostics_popout);
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
.status_label
.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_buffer: gtk::TextBuffer,
pub diagnostics_scroll: gtk::ScrolledWindow,
pub diagnostics_popout_scroll: Rc<RefCell<Option<gtk::ScrolledWindow>>>,
pub session_log_buffer: gtk::TextBuffer,
pub session_log_view: gtk::TextView,
pub summary: SummaryWidgets,
@ -620,12 +621,14 @@ pub fn build_launcher_view(
state.breakout_size_options(1),
state.breakout_size_preset(1),
);
let diagnostics_popout_scroll = Rc::new(RefCell::new(None));
let widgets = LauncherWidgets {
status_label: status_label.clone(),
diagnostics_log: diagnostics_log.clone(),
diagnostics_buffer: diagnostics_buffer.clone(),
diagnostics_scroll: diagnostics_scroll.clone(),
diagnostics_popout_scroll: diagnostics_popout_scroll.clone(),
session_log_buffer: session_log_buffer.clone(),
session_log_view: session_log_view.clone(),
summary: SummaryWidgets {

View File

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

View File

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

View File

@ -17,6 +17,6 @@ mod tests {
#[test]
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]
name = "lesavka_server"
version = "0.11.23"
version = "0.11.24"
edition = "2024"
autobins = false