diff options
10 files changed, 217 insertions, 18 deletions
diff --git a/installer-downloader/src/controller.rs b/installer-downloader/src/controller.rs index 100fcf1796..e241daf4d8 100644 --- a/installer-downloader/src/controller.rs +++ b/installer-downloader/src/controller.rs @@ -19,6 +19,8 @@ enum TaskMessage { SetVersionInfo(VersionInfo), BeginDownload, Cancel, + TryBeta, + TryStable, } /// Provide a directory to use for [AppDownloader] @@ -78,6 +80,7 @@ impl AppController { delegate.disable_download_button(); delegate.hide_cancel_button(); delegate.hide_beta_text(); + delegate.hide_stable_text(); let (task_tx, task_rx) = mpsc::channel(1); tokio::spawn(handle_action_messages::<D, A, DirProvider>( @@ -105,6 +108,14 @@ impl AppController { delegate.on_cancel(move || { let _ = tx.try_send(TaskMessage::Cancel); }); + let tx = task_tx.clone(); + delegate.on_beta_link(move || { + let _ = tx.try_send(TaskMessage::TryBeta); + }); + let tx = task_tx.clone(); + delegate.on_stable_link(move || { + let _ = tx.try_send(TaskMessage::TryStable); + }); } } @@ -139,6 +150,12 @@ where } } +#[derive(Clone, Copy, PartialEq)] +enum TargetVersion { + Beta, + Stable, +} + /// Async worker that handles actions such as initiating a download, cancelling it, and updating /// labels. async fn handle_action_messages<D, A, DirProvider>( @@ -152,6 +169,8 @@ async fn handle_action_messages<D, A, DirProvider>( let mut version_info = None; let mut active_download = None; + let mut target_version = TargetVersion::Stable; + while let Some(msg) = rx.recv().await { match msg { TaskMessage::SetVersionInfo(new_version_info) => { @@ -166,6 +185,38 @@ async fn handle_action_messages<D, A, DirProvider>( }); version_info = Some(new_version_info); } + TaskMessage::TryBeta => { + let Some(version_info) = version_info.as_ref() else { + continue; + }; + let Some(beta_info) = version_info.beta.as_ref() else { + continue; + }; + + target_version = TargetVersion::Beta; + let version_label = format_latest_version(beta_info); + + queue.queue_main(move |self_| { + self_.show_stable_text(); + self_.hide_beta_text(); + self_.set_status_text(&version_label); + }); + } + TaskMessage::TryStable => { + let Some(version_info) = version_info.as_ref() else { + continue; + }; + let stable_info = &version_info.stable; + + target_version = TargetVersion::Stable; + let version_label = format_latest_version(stable_info); + + queue.queue_main(move |self_| { + self_.hide_stable_text(); + self_.show_beta_text(); + self_.set_status_text(&version_label); + }); + } TaskMessage::BeginDownload => { if active_download.is_some() { continue; @@ -188,17 +239,25 @@ async fn handle_action_messages<D, A, DirProvider>( // Begin download let (tx, rx) = oneshot::channel(); queue.queue_main(move |self_| { + let selected_version = match target_version { + TargetVersion::Stable => &version_info.stable, + TargetVersion::Beta => { + version_info.beta.as_ref().expect("selected version exists") + } + }; + // TODO: Select appropriate URLs - let Some(app_url) = version_info.stable.urls.first() else { + let Some(app_url) = selected_version.urls.first() else { return; }; - let app_version = version_info.stable.version; - let app_sha256 = version_info.stable.sha256; - let app_size = version_info.stable.size; + let app_version = selected_version.version.clone(); + let app_sha256 = selected_version.sha256; + let app_size = selected_version.size; self_.set_download_text(""); self_.hide_download_button(); self_.hide_beta_text(); + self_.hide_stable_text(); self_.show_cancel_button(); self_.enable_cancel_button(); self_.show_download_progress(); @@ -224,22 +283,33 @@ async fn handle_action_messages<D, A, DirProvider>( active_download.abort(); let _ = active_download.await; - let (version_label, has_beta) = if let Some(version_info) = &version_info { - ( - format_latest_version(&version_info.stable), - version_info.beta.is_some(), - ) - } else { - ("".to_owned(), false) + let Some(version_info) = version_info.as_ref() else { + continue; }; + let selected_version = match target_version { + TargetVersion::Stable => &version_info.stable, + TargetVersion::Beta => { + version_info.beta.as_ref().expect("selected version exists") + } + }; + + let version_label = format_latest_version(&selected_version); + let has_beta = version_info.beta.is_some(); + queue.queue_main(move |self_| { self_.set_status_text(&version_label); self_.set_download_text(""); self_.show_download_button(); - if has_beta { - self_.show_beta_text(); + + if target_version == TargetVersion::Stable { + if has_beta { + self_.show_beta_text(); + } + } else { + self_.show_stable_text(); } + self_.hide_cancel_button(); self_.hide_download_progress(); self_.set_download_progress(0); diff --git a/installer-downloader/src/delegate.rs b/installer-downloader/src/delegate.rs index 3e82d2ef31..de4313aeb2 100644 --- a/installer-downloader/src/delegate.rs +++ b/installer-downloader/src/delegate.rs @@ -17,6 +17,16 @@ pub trait AppDelegate { where F: Fn() + Send + 'static; + /// Register click handler for the beta link + fn on_beta_link<F>(&mut self, callback: F) + where + F: Fn() + Send + 'static; + + /// Register click handler for the stable link + fn on_stable_link<F>(&mut self, callback: F) + where + F: Fn() + Send + 'static; + /// Set status text fn set_status_text(&mut self, text: &str); @@ -62,6 +72,12 @@ pub trait AppDelegate { /// Hide beta text fn hide_beta_text(&mut self); + /// Show stable text + fn show_stable_text(&mut self); + + /// Hide stable text + fn hide_stable_text(&mut self); + /// Exit the application fn quit(&mut self); diff --git a/installer-downloader/src/resource.rs b/installer-downloader/src/resource.rs index b5f5a0bf85..af54455872 100644 --- a/installer-downloader/src/resource.rs +++ b/installer-downloader/src/resource.rs @@ -16,6 +16,9 @@ pub const BETA_PREFACE_DESC: &str = "Want to try the new Beta version? "; /// Beta link text pub const BETA_LINK_TEXT: &str = "Click here!"; +/// Stable link text +pub const STABLE_LINK_TEXT: &str = "Back to stable version"; + /// Dimensions of cancel button (including padding) pub const CANCEL_BUTTON_SIZE: (usize, usize) = (150, 40); diff --git a/installer-downloader/src/winapi_impl/delegate.rs b/installer-downloader/src/winapi_impl/delegate.rs index 353dff9691..69176cf897 100644 --- a/installer-downloader/src/winapi_impl/delegate.rs +++ b/installer-downloader/src/winapi_impl/delegate.rs @@ -24,6 +24,20 @@ impl AppDelegate for AppWindow { register_click_handler(self.window.handle, self.cancel_button.handle, callback); } + fn on_beta_link<F>(&mut self, callback: F) + where + F: Fn() + Send + 'static, + { + register_label_click_handler(self.window.handle, self.beta_link.handle, callback); + } + + fn on_stable_link<F>(&mut self, callback: F) + where + F: Fn() + Send + 'static, + { + register_label_click_handler(self.window.handle, self.stable_link.handle, callback); + } + fn set_status_text(&mut self, text: &str) { self.status_text.set_text(text); } @@ -91,6 +105,14 @@ impl AppDelegate for AppWindow { self.beta_link.set_visible(false); } + fn show_stable_text(&mut self) { + self.stable_link.set_visible(true); + } + + fn hide_stable_text(&mut self) { + self.stable_link.set_visible(false); + } + fn quit(&mut self) { nwg::stop_thread_dispatch(); } @@ -108,8 +130,27 @@ fn register_click_handler( button: nwg::ControlHandle, callback: impl Fn() + 'static, ) { + register_click_handler_inner(parent, button, callback, Event::OnButtonClick); +} + +/// Register a window message for clicking this button that triggers `callback`. +fn register_label_click_handler( + parent: nwg::ControlHandle, + button: nwg::ControlHandle, + callback: impl Fn() + 'static, +) { + register_click_handler_inner(parent, button, callback, Event::OnLabelClick); +} + +/// Register a window message for clicking this button that triggers `callback`. +fn register_click_handler_inner( + parent: nwg::ControlHandle, + button: nwg::ControlHandle, + callback: impl Fn() + 'static, + click_event: Event, +) { nwg::bind_event_handler(&button, &parent, move |evt, _, handle| { - if evt == Event::OnButtonClick && handle == button { + if evt == click_event && handle == button { callback(); } }); diff --git a/installer-downloader/src/winapi_impl/ui.rs b/installer-downloader/src/winapi_impl/ui.rs index 1b4704ad3d..f53d00dccd 100644 --- a/installer-downloader/src/winapi_impl/ui.rs +++ b/installer-downloader/src/winapi_impl/ui.rs @@ -14,7 +14,8 @@ use windows_sys::Win32::UI::WindowsAndMessaging::WM_CTLCOLORSTATIC; use crate::resource::{ BANNER_DESC, BETA_LINK_TEXT, BETA_PREFACE_DESC, CANCEL_BUTTON_SIZE, CANCEL_BUTTON_TEXT, - DOWNLOAD_BUTTON_SIZE, DOWNLOAD_BUTTON_TEXT, WINDOW_HEIGHT, WINDOW_TITLE, WINDOW_WIDTH, + DOWNLOAD_BUTTON_SIZE, DOWNLOAD_BUTTON_TEXT, STABLE_LINK_TEXT, WINDOW_HEIGHT, WINDOW_TITLE, + WINDOW_WIDTH, }; use super::delegate::QueueContext; @@ -32,6 +33,8 @@ pub const SET_LABEL_HANDLER_ID: usize = 0x10000; pub const QUEUE_MESSAGE_HANDLER_ID: usize = 0x10001; /// Custom window message used to process requests from other threads. pub const QUEUE_MESSAGE: u32 = 0x10001; +/// Unique ID of the handler for the stable link. +pub const STABLE_LINK_HANDLER_ID: usize = 0x10003; /// Unique ID of the handler for the beta link. pub const BETA_LINK_HANDLER_ID: usize = 0x10002; @@ -57,6 +60,7 @@ pub struct AppWindow { pub beta_prefix: nwg::Label, pub beta_link: nwg::Label, + pub stable_link: nwg::Label, } impl AppWindow { @@ -121,14 +125,26 @@ impl AppWindow { .text(BETA_PREFACE_DESC) .h_align(nwg::HTextAlign::Left) .build(&mut self.beta_prefix)?; + + let link_font = create_link_font()?; + nwg::Label::builder() .parent(&self.window) .size((128, 24)) .text(BETA_LINK_TEXT) - .font(Some(&create_link_font()?)) + .font(Some(&link_font)) .h_align(nwg::HTextAlign::Left) .build(&mut self.beta_link)?; + nwg::Label::builder() + .parent(&self.window) + .size((240, 24)) + .text(STABLE_LINK_TEXT) + .font(Some(&link_font)) + .h_align(nwg::HTextAlign::Left) + .build(&mut self.stable_link)?; + self.stable_link.set_visible(false); + const PROGRESS_BAR_MARGIN: i32 = 48; nwg::ProgressBar::builder() .parent(&self.window) @@ -167,6 +183,11 @@ impl AppWindow { + LOWER_AREA_YPADDING, ); + self.stable_link.set_position( + 24, + self.window.size().1 as i32 - 24 - self.stable_link.size().1 as i32, + ); + handle_link_messages(&self.window, &self.stable_link, STABLE_LINK_HANDLER_ID)?; self.beta_prefix.set_position( 24, self.window.size().1 as i32 - 24 - self.beta_prefix.size().1 as i32, @@ -175,7 +196,7 @@ impl AppWindow { self.beta_prefix.position().0 + self.beta_prefix.size().0 as i32, self.beta_prefix.position().1, ); - handle_beta_link_messages(&self.window, &self.beta_link, BETA_LINK_HANDLER_ID)?; + handle_link_messages(&self.window, &self.beta_link, BETA_LINK_HANDLER_ID)?; self.window.set_visible(true); @@ -277,7 +298,7 @@ fn handle_banner_label_colors( } /// Register a window message handler for the beta link component -fn handle_beta_link_messages( +fn handle_link_messages( parent: &nwg::Window, link: &nwg::Label, handler_id: usize, diff --git a/installer-downloader/tests/controller.rs b/installer-downloader/tests/controller.rs index 03a4842ec7..8ac544b98a 100644 --- a/installer-downloader/tests/controller.rs +++ b/installer-downloader/tests/controller.rs @@ -147,6 +147,10 @@ pub struct FakeAppDelegate { pub download_callback: Option<Box<dyn Fn() + Send>>, /// Callback registered by `on_cancel` pub cancel_callback: Option<Box<dyn Fn() + Send>>, + /// Callback registered by `on_beta_link` + pub beta_callback: Option<Box<dyn Fn() + Send>>, + /// Callback registered by `on_stable_link` + pub stable_callback: Option<Box<dyn Fn() + Send>>, /// State of delegate pub state: DelegateState, /// Queue used to simulate the main thread @@ -165,6 +169,7 @@ pub struct DelegateState { pub download_progress: u32, pub download_progress_visible: bool, pub beta_text_visible: bool, + pub stable_text_visible: bool, pub quit: bool, /// Record of method calls. pub call_log: Vec<String>, @@ -189,6 +194,22 @@ impl AppDelegate for FakeAppDelegate { self.cancel_callback = Some(Box::new(callback)); } + fn on_beta_link<F>(&mut self, callback: F) + where + F: Fn() + Send + 'static, + { + self.state.call_log.push("on_beta_link".into()); + self.beta_callback = Some(Box::new(callback)); + } + + fn on_stable_link<F>(&mut self, callback: F) + where + F: Fn() + Send + 'static, + { + self.state.call_log.push("on_stable_link".into()); + self.stable_callback = Some(Box::new(callback)); + } + fn set_status_text(&mut self, text: &str) { self.state .call_log @@ -270,6 +291,16 @@ impl AppDelegate for FakeAppDelegate { self.state.beta_text_visible = false; } + fn show_stable_text(&mut self) { + self.state.call_log.push("show_stable_text".into()); + self.state.stable_text_visible = true; + } + + fn hide_stable_text(&mut self) { + self.state.call_log.push("hide_stable_text".into()); + self.state.stable_text_visible = false; + } + fn quit(&mut self) { self.state.call_log.push("quit".into()); self.state.quit = true; diff --git a/installer-downloader/tests/snapshots/controller__download.snap b/installer-downloader/tests/snapshots/controller__download.snap index 3d79a1922e..ded40dd4a4 100644 --- a/installer-downloader/tests/snapshots/controller__download.snap +++ b/installer-downloader/tests/snapshots/controller__download.snap @@ -12,6 +12,7 @@ download_button_enabled: true download_progress: 0 download_progress_visible: false beta_text_visible: false +stable_text_visible: false quit: false call_log: - hide_download_progress @@ -19,8 +20,11 @@ call_log: - disable_download_button - hide_cancel_button - hide_beta_text + - hide_stable_text - "set_status_text: Loading version details..." - on_download - on_cancel + - on_beta_link + - on_stable_link - "set_status_text: Version: 2025.1" - enable_download_button diff --git a/installer-downloader/tests/snapshots/controller__failed_directory_creation.snap b/installer-downloader/tests/snapshots/controller__failed_directory_creation.snap index 9cb6d6fcb1..2abc026735 100644 --- a/installer-downloader/tests/snapshots/controller__failed_directory_creation.snap +++ b/installer-downloader/tests/snapshots/controller__failed_directory_creation.snap @@ -12,6 +12,7 @@ download_button_enabled: true download_progress: 0 download_progress_visible: false beta_text_visible: false +stable_text_visible: false quit: false call_log: - hide_download_progress @@ -19,9 +20,12 @@ call_log: - disable_download_button - hide_cancel_button - hide_beta_text + - hide_stable_text - "set_status_text: Loading version details..." - on_download - on_cancel + - on_beta_link + - on_stable_link - "set_status_text: Version: 2025.1" - enable_download_button - "set_status_text: Failed to create download directory" diff --git a/installer-downloader/tests/snapshots/controller__failed_verification.snap b/installer-downloader/tests/snapshots/controller__failed_verification.snap index b7825fb1d5..acf41ff257 100644 --- a/installer-downloader/tests/snapshots/controller__failed_verification.snap +++ b/installer-downloader/tests/snapshots/controller__failed_verification.snap @@ -12,6 +12,7 @@ download_button_enabled: true download_progress: 100 download_progress_visible: true beta_text_visible: false +stable_text_visible: false quit: false call_log: - hide_download_progress @@ -19,14 +20,18 @@ call_log: - disable_download_button - hide_cancel_button - hide_beta_text + - hide_stable_text - "set_status_text: Loading version details..." - on_download - on_cancel + - on_beta_link + - on_stable_link - "set_status_text: Version: 2025.1" - enable_download_button - "set_download_text: " - hide_download_button - hide_beta_text + - hide_stable_text - show_cancel_button - enable_cancel_button - show_download_progress diff --git a/installer-downloader/tests/snapshots/controller__fetch_version.snap b/installer-downloader/tests/snapshots/controller__fetch_version.snap index 24c54ce95c..17f1b954cf 100644 --- a/installer-downloader/tests/snapshots/controller__fetch_version.snap +++ b/installer-downloader/tests/snapshots/controller__fetch_version.snap @@ -12,6 +12,7 @@ download_button_enabled: false download_progress: 0 download_progress_visible: false beta_text_visible: false +stable_text_visible: false quit: false call_log: - hide_download_progress @@ -19,6 +20,9 @@ call_log: - disable_download_button - hide_cancel_button - hide_beta_text + - hide_stable_text - "set_status_text: Loading version details..." - on_download - on_cancel + - on_beta_link + - on_stable_link |
