From bc8db71691128671b8f46d6b68c6be0a91407d51 Mon Sep 17 00:00:00 2001 From: Kevin Reid Date: Fri, 27 Oct 2023 15:32:15 -0700 Subject: [PATCH] =?UTF-8?q?ui=20&=20desktop:=20Add=20a=20basic=20=E2=80=9C?= =?UTF-8?q?Quit=E2=80=9D=20button.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- all-is-cubes-desktop/src/main.rs | 10 +++- all-is-cubes-ui/src/apps/session.rs | 28 +++++++++++ all-is-cubes-ui/src/ui_content/hud.rs | 1 + all-is-cubes-ui/src/ui_content/pages.rs | 48 ++++++++++++------- all-is-cubes-ui/src/ui_content/vui_manager.rs | 5 +- all-is-cubes-ui/src/vui/blocks.rs | 9 ++++ 6 files changed, 83 insertions(+), 18 deletions(-) diff --git a/all-is-cubes-desktop/src/main.rs b/all-is-cubes-desktop/src/main.rs index b8b211c9c..83d065891 100644 --- a/all-is-cubes-desktop/src/main.rs +++ b/all-is-cubes-desktop/src/main.rs @@ -163,7 +163,15 @@ fn main() -> Result<(), anyhow::Error> { )); let start_session_time = Instant::now(); - let session = runtime.block_on(Session::builder().ui(viewport_cell.as_source()).build()); + let session = runtime.block_on( + Session::builder() + .ui(viewport_cell.as_source()) + .quit(Arc::new(|| { + // TODO: command the event loop to exit instead + std::process::exit(0) + })) + .build(), + ); session.graphics_options_mut().set(graphics_options); let session_done_time = Instant::now(); log::debug!( diff --git a/all-is-cubes-ui/src/apps/session.rs b/all-is-cubes-ui/src/apps/session.rs index 576d35bc5..a5a59b3d8 100644 --- a/all-is-cubes-ui/src/apps/session.rs +++ b/all-is-cubes-ui/src/apps/session.rs @@ -531,6 +531,8 @@ pub struct SessionBuilder { fullscreen_state: ListenableSource, set_fullscreen: FullscreenSetter, + quit: Option, + _instant: PhantomData, } @@ -540,6 +542,7 @@ impl Default for SessionBuilder { viewport_for_ui: None, fullscreen_state: ListenableSource::constant(None), set_fullscreen: None, + quit: None, _instant: PhantomData, } } @@ -556,6 +559,7 @@ impl SessionBuilder { viewport_for_ui, fullscreen_state, set_fullscreen, + quit, _instant: _, } = self; let game_universe = Universe::new(); @@ -579,6 +583,7 @@ impl SessionBuilder { viewport, fullscreen_state, set_fullscreen, + quit, ) .await, ), @@ -627,6 +632,15 @@ impl SessionBuilder { self.set_fullscreen = setter; self } + + /// Enable a “quit”/“exit” command in the session's user interface. + /// + /// This does not cause the session to self-destruct; rather, the provided callback + /// function should cause the session’s owner to stop presenting it to the user. + pub fn quit(mut self, quit_fn: QuitFn) -> Self { + self.quit = Some(quit_fn); + self + } } // TODO: these should be in one struct or something. @@ -758,6 +772,20 @@ pub enum CursorIcon { PointingHand, } +/// TODO: this should be an async fn +pub(crate) type QuitFn = Arc Result + Send + Sync>; + +/// Return type of a [`SessionBuilder::quit()`] callback on successful quit. +/// This is uninhabited (cannot happen) since the callback should never be observed to +/// finish if it successfully quits. +pub type QuitSucceeded = std::convert::Infallible; + +/// Return type of a [`SessionBuilder::quit()`] callback if other considerations cancelled +/// the quit operation. In this case, the session will return to normal operation. +#[derive(Clone, Copy, Debug, PartialEq)] +#[allow(clippy::exhaustive_structs)] +pub struct QuitCancelled; + #[cfg(test)] mod tests { use super::*; diff --git a/all-is-cubes-ui/src/ui_content/hud.rs b/all-is-cubes-ui/src/ui_content/hud.rs index 1e6804dc9..80be87746 100644 --- a/all-is-cubes-ui/src/ui_content/hud.rs +++ b/all-is-cubes-ui/src/ui_content/hud.rs @@ -40,6 +40,7 @@ pub(crate) struct HudInputs { pub mouselook_mode: ListenableSource, pub fullscreen_mode: ListenableSource, pub set_fullscreen: FullscreenSetter, + pub(crate) quit: Option, } impl fmt::Debug for HudInputs { diff --git a/all-is-cubes-ui/src/ui_content/pages.rs b/all-is-cubes-ui/src/ui_content/pages.rs index 11872192f..36bde6587 100644 --- a/all-is-cubes-ui/src/ui_content/pages.rs +++ b/all-is-cubes-ui/src/ui_content/pages.rs @@ -23,24 +23,40 @@ pub(super) fn new_paused_widget_tree( ) -> Result { use parts::{heading, shrink}; + let mut children = vec![ + // TODO: establish standard resolutions for logo etc + LayoutTree::leaf(shrink(u, R16, LayoutTree::leaf(logo_text()))?), + LayoutTree::leaf(shrink(u, R32, heading("Paused"))?), + LayoutTree::leaf(open_page_button( + hud_inputs, + VuiPageState::AboutText, + hud_inputs.hud_blocks.blocks[UiBlocks::AboutButtonLabel].clone(), + )), + LayoutTree::leaf(open_page_button( + hud_inputs, + VuiPageState::Options, + hud_inputs.hud_blocks.blocks[UiBlocks::OptionsButtonLabel].clone(), + )), + LayoutTree::leaf(pause_toggle_button(hud_inputs)), + ]; + if let Some(quit_fn) = hud_inputs.quit.as_ref().cloned() { + children.push(LayoutTree::leaf(widgets::ActionButton::new( + hud_inputs.hud_blocks.blocks[UiBlocks::QuitButtonLabel].clone(), + &hud_inputs.hud_blocks.blocks, + // TODO: quit_fn should be an async function, but we don't have a way to + // kick off a “Quitting...” task yet. + move || match quit_fn() { + Ok(s) => match s {}, + Err(crate::apps::QuitCancelled) => { + + // TODO: display message indicating failure + } + }, + ))) + } let contents = Arc::new(LayoutTree::Stack { direction: Face6::NY, - children: vec![ - // TODO: establish standard resolutions for logo etc - LayoutTree::leaf(shrink(u, R16, LayoutTree::leaf(logo_text()))?), - LayoutTree::leaf(shrink(u, R32, heading("Paused"))?), - LayoutTree::leaf(open_page_button( - hud_inputs, - VuiPageState::AboutText, - hud_inputs.hud_blocks.blocks[UiBlocks::AboutButtonLabel].clone(), - )), - LayoutTree::leaf(open_page_button( - hud_inputs, - VuiPageState::Options, - hud_inputs.hud_blocks.blocks[UiBlocks::OptionsButtonLabel].clone(), - )), - LayoutTree::leaf(pause_toggle_button(hud_inputs)), - ], + children, }); Ok(page_modal_backdrop(Arc::new(LayoutTree::Shrink( hud_inputs diff --git a/all-is-cubes-ui/src/ui_content/vui_manager.rs b/all-is-cubes-ui/src/ui_content/vui_manager.rs index d65877400..d0719eaee 100644 --- a/all-is-cubes-ui/src/ui_content/vui_manager.rs +++ b/all-is-cubes-ui/src/ui_content/vui_manager.rs @@ -14,7 +14,7 @@ use all_is_cubes::time; use all_is_cubes::transaction::{self, Transaction}; use all_is_cubes::universe::{URef, Universe, UniverseStepInfo}; -use crate::apps::{ControlMessage, FullscreenSetter, FullscreenState, InputProcessor}; +use crate::apps::{ControlMessage, FullscreenSetter, FullscreenState, InputProcessor, QuitFn}; use crate::ui_content::hud::{HudBlocks, HudInputs}; use crate::ui_content::pages; use crate::vui::widgets::TooltipState; @@ -80,6 +80,7 @@ impl Vui { viewport_source: ListenableSource, fullscreen_source: ListenableSource, set_fullscreen: FullscreenSetter, + quit: Option, ) -> Self { let mut universe = Universe::new(); // TODO: take YieldProgress as a parameter @@ -111,6 +112,7 @@ impl Vui { mouselook_mode: input_processor.mouselook_mode(), fullscreen_mode: fullscreen_source, set_fullscreen, + quit, }; let hud_widget_tree = super::hud::new_hud_widget_tree( character_source.clone(), @@ -471,6 +473,7 @@ mod tests { ListenableSource::constant(Viewport::ARBITRARY), ListenableSource::constant(None), None, + None, ) .await; (vui, ccrx) diff --git a/all-is-cubes-ui/src/vui/blocks.rs b/all-is-cubes-ui/src/vui/blocks.rs index 10f6e2538..c21fdfad8 100644 --- a/all-is-cubes-ui/src/vui/blocks.rs +++ b/all-is-cubes-ui/src/vui/blocks.rs @@ -54,6 +54,7 @@ pub enum UiBlocks { AboutButtonLabel, PauseButtonLabel, SaveButtonLabel, + QuitButtonLabel, OptionsButtonLabel, MouselookButtonLabel, FullscreenButtonLabel, @@ -86,6 +87,7 @@ impl fmt::Display for UiBlocks { UiBlocks::AboutButtonLabel => write!(f, "about-button"), UiBlocks::PauseButtonLabel => write!(f, "pause-button"), UiBlocks::SaveButtonLabel => write!(f, "save-button"), + UiBlocks::QuitButtonLabel => write!(f, "quit-button"), UiBlocks::OptionsButtonLabel => write!(f, "options-button"), UiBlocks::MouselookButtonLabel => write!(f, "mouselook-button"), UiBlocks::FullscreenButtonLabel => write!(f, "fullscreen-button"), @@ -203,6 +205,13 @@ impl UiBlocks { )? .build(), + UiBlocks::QuitButtonLabel => make_button_label_block( + universe, + "Quit", + ButtonIcon::Text(&font::FONT_7X13, "Quit"), + )? + .build(), + UiBlocks::OptionsButtonLabel => make_button_label_block( universe, "Options",