diff --git a/src/runner.rs b/src/runner.rs index cb78490..c4d15c2 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -30,7 +30,7 @@ pub struct Runner { #[derive(PartialEq, Eq, Debug)] pub enum RunnerStatus { Running, - Stopped, + Stopped(Option), } macro_rules! logger_thread { @@ -154,10 +154,10 @@ impl Runner { pub fn status(&mut self) -> RunnerStatus { match &mut self.process { - None => RunnerStatus::Stopped, + None => RunnerStatus::Stopped(None), Some(proc) => match proc.try_wait() { Err(_) => RunnerStatus::Running, - Ok(Some(_)) => RunnerStatus::Stopped, + Ok(Some(code)) => RunnerStatus::Stopped(code.code()), Ok(None) => RunnerStatus::Running, }, } @@ -217,7 +217,7 @@ mod tests { runner.start(); sleep(time::Duration::from_millis(10)); runner.terminate(); - assert_eq!(runner.status(), RunnerStatus::Stopped); + assert_eq!(runner.status(), RunnerStatus::Stopped(Some(0))); let out = runner.consume_output(); assert_eq!(out, "REX2TEST: Lorem ipsum dolor\n"); } diff --git a/src/runner_pipeline.rs b/src/runner_pipeline.rs index 9c55a09..a5b3c79 100644 --- a/src/runner_pipeline.rs +++ b/src/runner_pipeline.rs @@ -5,6 +5,7 @@ use crate::runner::{Runner, RunnerStatus}; pub struct RunnerPipeline { runners: Vec>, current_index: usize, + last_exit_status: Option, has_started: bool, pub log: Vec, } @@ -19,6 +20,7 @@ impl RunnerPipeline { runners: c_runners, current_index: 0, has_started: false, + last_exit_status: None, log: vec![], } } @@ -55,12 +57,23 @@ impl RunnerPipeline { self.log.extend(log); match status { RunnerStatus::Running => {}, - RunnerStatus::Stopped => { - self.current_index += 1; - match self.get_current_runner() { - None => {}, - Some(c_runner) => { - c_runner.borrow_mut().start(); + RunnerStatus::Stopped(ecode) => { + match ecode { + None => {} // should never get here + Some(0) => { + self.last_exit_status = Some(0); + self.current_index += 1; + match self.get_current_runner() { + None => {}, + Some(c_runner) => { + c_runner.borrow_mut().start(); + } + } + } + Some(nonzero) => { + self.last_exit_status = Some(nonzero); + // interrupting pipeline by going past last runner + self.current_index = self.runners.len(); } } } @@ -73,7 +86,10 @@ impl RunnerPipeline { self.log.concat() } - pub fn is_running(&self) -> bool { - self.get_current_runner().is_some() + pub fn status(&self) -> RunnerStatus { + match self.get_current_runner() { + None => RunnerStatus::Stopped(self.last_exit_status), + Some(_) => RunnerStatus::Running + } } } diff --git a/src/ui/app.rs b/src/ui/app.rs index b6739af..8550f58 100644 --- a/src/ui/app.rs +++ b/src/ui/app.rs @@ -1,5 +1,5 @@ use super::about_dialog::AboutDialog; -use super::build_window::BuildWindow; +use super::build_window::{BuildStatus, BuildWindow}; use super::debug_view::{DebugView, DebugViewMsg}; use super::libsurvive_setup_window::LibsurviveSetupWindow; use super::main_view::MainViewMsg; @@ -161,7 +161,7 @@ impl SimpleComponent for App { } match runner.status() { RunnerStatus::Running => {} - RunnerStatus::Stopped => { + RunnerStatus::Stopped(_) => { self.main_view .sender() .emit(MainViewMsg::MonadoActiveChanged(false)); @@ -176,12 +176,30 @@ impl SimpleComponent for App { self.build_window .sender() .emit(BuildWindowMsg::UpdateContent(pipeline.get_log())); - if !pipeline.is_running() { - self.setcap_confirm_dialog.present(); - self.build_window - .sender() - .emit(BuildWindowMsg::UpdateCanClose(true)); - self.build_pipeline.take(); + match pipeline.status() { + RunnerStatus::Running | RunnerStatus::Stopped(None) => {} + RunnerStatus::Stopped(Some(code)) => { + self.build_window + .sender() + .emit(BuildWindowMsg::UpdateCanClose(true)); + self.build_pipeline.take(); + match code { + 0 => { + self.build_window.sender().emit( + BuildWindowMsg::UpdateBuildStatus(BuildStatus::Done) + ); + self.setcap_confirm_dialog.present(); + self.build_window + .sender() + .emit(BuildWindowMsg::UpdateCanClose(true)); + } + errcode => self.build_window.sender().emit( + BuildWindowMsg::UpdateBuildStatus(BuildStatus::Error( + format!("Exit status {}", errcode), + )), + ), + } + } } } }; @@ -219,7 +237,7 @@ impl SimpleComponent for App { .sender() .emit(MainViewMsg::MonadoActiveChanged(false)); } - RunnerStatus::Stopped => { + RunnerStatus::Stopped(_) => { self.debug_view .sender() .emit(DebugViewMsg::LogUpdated(vec![])); @@ -301,7 +319,8 @@ impl SimpleComponent for App { .sender() .send(LibsurviveSetupMsg::Present( self.get_selected_profile().clone(), - )); + )) + .expect_dialog("Failed to present Libsurvive Setup Window"); } } } diff --git a/src/ui/build_window.rs b/src/ui/build_window.rs index 8b88036..2e8d4fb 100644 --- a/src/ui/build_window.rs +++ b/src/ui/build_window.rs @@ -1,17 +1,27 @@ use gtk::prelude::*; use relm4::prelude::*; +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum BuildStatus { + Building, + Done, + Error(String), +} + #[tracker::track] pub struct BuildWindow { title: String, content: String, can_close: bool, + build_status: BuildStatus, #[tracker::do_not_track] pub textbuf: gtk::TextBuffer, #[tracker::do_not_track] pub win: Option, #[tracker::do_not_track] + build_status_label: Option, + #[tracker::do_not_track] pub scrolled_win: Option, } @@ -19,6 +29,7 @@ pub struct BuildWindow { pub enum BuildWindowMsg { Present, UpdateTitle(String), + UpdateBuildStatus(BuildStatus), UpdateContent(String), UpdateCanClose(bool), } @@ -34,10 +45,12 @@ impl SimpleComponent for BuildWindow { adw::Window { set_modal: true, set_default_size: (520, 400), + set_hide_on_close: true, gtk::Box { set_vexpand: true, set_hexpand: true, set_orientation: gtk::Orientation::Vertical, + set_spacing: 12, gtk::WindowHandle { set_vexpand: false, set_hexpand: true, @@ -48,11 +61,32 @@ impl SimpleComponent for BuildWindow { #[wrap(Some)] set_title_widget: title_label = >k::Label { #[track = "model.changed(BuildWindow::title())"] - set_label: &model.title, + set_markup: &model.title, add_css_class: "title", }, } }, + gtk::Box { + set_orientation: gtk::Orientation::Horizontal, + set_hexpand: true, + set_halign: gtk::Align::Center, + set_spacing: 12, + #[name(build_status_label)] + gtk::Label { + #[track = "model.changed(BuildWindow::build_status())"] + set_markup: match &model.build_status { + BuildStatus::Building => "Build in progress...".to_string(), + BuildStatus::Done => "Build done, you can close this window".to_string(), + BuildStatus::Error(code) => { + format!("Build failed: \"{c}\"", c = code) + } + }.as_str(), + add_css_class: "title-2", + set_wrap: true, + set_wrap_mode: gtk::pango::WrapMode::Word, + set_justify: gtk::Justification::Center, + } + }, #[name(scrolled_win)] gtk::ScrolledWindow { set_hexpand: true, @@ -65,6 +99,10 @@ impl SimpleComponent for BuildWindow { set_vexpand: true, set_editable: false, set_monospace: true, + set_left_margin: 6, + set_right_margin: 6, + set_top_margin: 6, + set_bottom_margin: 6, set_buffer: Some(&model.textbuf), }, }, @@ -76,7 +114,8 @@ impl SimpleComponent for BuildWindow { #[track = "model.changed(BuildWindow::can_close())"] set_sensitive: model.can_close, connect_clicked[win] => move |_| { - win.hide(); + + win.close(); }, } } @@ -89,10 +128,11 @@ impl SimpleComponent for BuildWindow { match message { BuildWindowMsg::Present => { self.win.as_ref().unwrap().present(); - }, + sender.input(BuildWindowMsg::UpdateBuildStatus(BuildStatus::Building)); + } BuildWindowMsg::UpdateTitle(t) => { self.set_title(t); - }, + } BuildWindowMsg::UpdateContent(c) => { if self.content != c { self.set_content(c); @@ -103,7 +143,18 @@ impl SimpleComponent for BuildWindow { adj.set_value(adj.upper()); sw.set_vadjustment(Some(&adj)); } - }, + } + BuildWindowMsg::UpdateBuildStatus(status) => { + let label = self.build_status_label.as_ref().unwrap(); + label.remove_css_class("success"); + label.remove_css_class("error"); + match status { + BuildStatus::Done => label.add_css_class("success"), + BuildStatus::Error(_) => label.add_css_class("error"), + _ => {} + } + self.set_build_status(status); + } BuildWindowMsg::UpdateCanClose(val) => { self.set_can_close(val); } @@ -121,12 +172,15 @@ impl SimpleComponent for BuildWindow { content: "".into(), can_close: false, textbuf: gtk::TextBuffer::builder().build(), + build_status: BuildStatus::Building, win: None, + build_status_label: None, scrolled_win: None, }; let widgets = view_output!(); model.scrolled_win = Some(widgets.scrolled_win.clone()); model.win = Some(widgets.win.clone()); + model.build_status_label = Some(widgets.build_status_label.clone()); ComponentParts { model, widgets } } }