From 183ce6efb633370c3a689e54e17ce888e3d7956e Mon Sep 17 00:00:00 2001 From: "Andrew J. Stone" Date: Fri, 15 Sep 2023 19:23:39 -0400 Subject: [PATCH] [wicket] Make screens `impl Control` --- wicket/src/events.rs | 4 +- wicket/src/runner.rs | 4 ++ wicket/src/ui/main.rs | 154 +++++++++++++++++++--------------------- wicket/src/ui/mod.rs | 61 ++++++++-------- wicket/src/ui/splash.rs | 31 ++++---- 5 files changed, 132 insertions(+), 122 deletions(-) diff --git a/wicket/src/events.rs b/wicket/src/events.rs index fd0ac086ad..a7fff3e087 100644 --- a/wicket/src/events.rs +++ b/wicket/src/events.rs @@ -81,6 +81,7 @@ pub enum Action { Ignition(ComponentId, IgnitionCommand), StartRackSetup, StartRackReset, + SwitchScreen, } impl Action { @@ -96,7 +97,8 @@ impl Action { | Action::ClearUpdateState(_) | Action::Ignition(_, _) | Action::StartRackSetup - | Action::StartRackReset => true, + | Action::StartRackReset + | Action::SwitchScreen => true, } } } diff --git a/wicket/src/runner.rs b/wicket/src/runner.rs index c37b16d5d9..1dc388daca 100644 --- a/wicket/src/runner.rs +++ b/wicket/src/runner.rs @@ -276,6 +276,10 @@ impl RunnerCore { .blocking_send(wicketd::Request::StartRackReset)?; } } + Action::SwitchScreen => { + self.screen.switch(&mut self.state); + self.screen.draw(&self.state, &mut self.terminal)?; + } } Ok(()) } diff --git a/wicket/src/ui/main.rs b/wicket/src/ui/main.rs index 42cc6bf587..f2a9f24df7 100644 --- a/wicket/src/ui/main.rs +++ b/wicket/src/ui/main.rs @@ -8,7 +8,7 @@ use super::{Control, OverviewPane, RackSetupPane, StatefulList, UpdatePane}; use crate::ui::defaults::colors::*; use crate::ui::defaults::style; use crate::ui::widgets::Fade; -use crate::{Action, Cmd, Frame, State, Term}; +use crate::{Action, Cmd, Frame, State}; use ratatui::layout::{Alignment, Constraint, Direction, Layout, Rect}; use ratatui::style::{Modifier, Style}; use ratatui::text::{Line, Span}; @@ -35,68 +35,71 @@ pub struct MainScreen { pane_rect: Rect, } -impl MainScreen { - pub fn new(log: &Logger) -> MainScreen { - // We want the sidebar ordered in this specific manner - let sidebar_ordered_panes = vec![ - ("overview", Box::new(OverviewPane::new()) as Box), - ("update", Box::new(UpdatePane::new(log))), - ("rack setup", Box::::default()), - ]; - let sidebar_keys: Vec<_> = - sidebar_ordered_panes.iter().map(|&(title, _)| title).collect(); - let log = log.new(o!("component" => "MainScreen")); - MainScreen { - log, - sidebar: Sidebar::new(sidebar_keys), - panes: BTreeMap::from_iter(sidebar_ordered_panes), - rect: Rect::default(), - sidebar_rect: Rect::default(), - pane_rect: Rect::default(), +impl Control for MainScreen { + /// Handle a [`Cmd`] to update state and output any necessary actions for the + /// system to take. + fn on(&mut self, state: &mut State, cmd: Cmd) -> Option { + match cmd { + // There's just two panes, so next and previous do the same thing + // for now. + Cmd::NextPane | Cmd::PrevPane => { + if self.sidebar.active { + self.sidebar.active = false; + Some(Action::Redraw) + } else { + if self.current_pane().is_modal_active() { + self.current_pane().on(state, cmd) + } else { + self.sidebar.active = true; + Some(Action::Redraw) + } + } + } + Cmd::Enter => { + if self.sidebar.active { + self.sidebar.active = false; + Some(Action::Redraw) + } else { + self.current_pane().on(state, cmd) + } + } + _ => self.dispatch_cmd(state, cmd), } } - /// Draw the [`MainScreen`] - pub fn draw( + fn draw( &mut self, state: &State, - terminal: &mut Term, - ) -> anyhow::Result<()> { - terminal.draw(|frame| { - let mut rect = frame.size(); - - rect.height -= 1; - let statusbar_rect = Rect { - y: rect.height - 1, - height: 1, - x: 2, - width: rect.width - 3, - }; + frame: &mut Frame<'_>, + mut rect: Rect, + _: bool, + ) { + rect.height -= 1; + let statusbar_rect = + Rect { y: rect.height - 1, height: 1, x: 2, width: rect.width - 3 }; - // Size the individual components of the screen - let chunks = Layout::default() - .direction(Direction::Horizontal) - .margin(1) - .constraints( - [Constraint::Length(20), Constraint::Max(1000)].as_ref(), - ) - .split(rect); + // Size the individual components of the screen + let chunks = Layout::default() + .direction(Direction::Horizontal) + .margin(1) + .constraints( + [Constraint::Length(20), Constraint::Max(1000)].as_ref(), + ) + .split(rect); - // Draw all the components, starting with the background - let background = Block::default().style(style::background()); - frame.render_widget(background, frame.size()); - self.sidebar.draw(state, frame, chunks[0], self.sidebar.active); - self.draw_pane(state, frame, chunks[1]); - self.draw_statusbar(state, frame, statusbar_rect); - })?; - Ok(()) + // Draw all the components, starting with the background + let background = Block::default().style(style::background()); + frame.render_widget(background, frame.size()); + self.sidebar.draw(state, frame, chunks[0], self.sidebar.active); + self.draw_pane(state, frame, chunks[1]); + self.draw_statusbar(state, frame, statusbar_rect); } /// Compute the layout of the [`Sidebar`] and pane /// // A draw is issued after every resize, so no need to return an Action - pub fn resize(&mut self, state: &mut State, width: u16, height: u16) { - self.rect = Rect { x: 0, y: 0, width, height }; + fn resize(&mut self, state: &mut State, rect: Rect) { + self.rect = rect; // We have a 1 row status bar at the bottom when we draw. Subtract it // from the height; @@ -119,35 +122,26 @@ impl MainScreen { let pane_rect = self.pane_rect; self.current_pane().resize(state, pane_rect); } +} - /// Handle a [`Cmd`] to update state and output any necessary actions for the - /// system to take. - pub fn on(&mut self, state: &mut State, cmd: Cmd) -> Option { - match cmd { - // There's just two panes, so next and previous do the same thing - // for now. - Cmd::NextPane | Cmd::PrevPane => { - if self.sidebar.active { - self.sidebar.active = false; - Some(Action::Redraw) - } else { - if self.current_pane().is_modal_active() { - self.current_pane().on(state, cmd) - } else { - self.sidebar.active = true; - Some(Action::Redraw) - } - } - } - Cmd::Enter => { - if self.sidebar.active { - self.sidebar.active = false; - Some(Action::Redraw) - } else { - self.current_pane().on(state, cmd) - } - } - _ => self.dispatch_cmd(state, cmd), +impl MainScreen { + pub fn new(log: &Logger) -> MainScreen { + // We want the sidebar ordered in this specific manner + let sidebar_ordered_panes = vec![ + ("overview", Box::new(OverviewPane::new()) as Box), + ("update", Box::new(UpdatePane::new(log))), + ("rack setup", Box::::default()), + ]; + let sidebar_keys: Vec<_> = + sidebar_ordered_panes.iter().map(|&(title, _)| title).collect(); + let log = log.new(o!("component" => "MainScreen")); + MainScreen { + log, + sidebar: Sidebar::new(sidebar_keys), + panes: BTreeMap::from_iter(sidebar_ordered_panes), + rect: Rect::default(), + sidebar_rect: Rect::default(), + pane_rect: Rect::default(), } } @@ -158,7 +152,7 @@ impl MainScreen { if self.sidebar.selected_pane() != current_pane { // We need to inform the new pane, which may not have // ever been drawn what its Rect is. - self.resize(state, self.rect.width, self.rect.height); + self.resize(state, self.rect); Some(Action::Redraw) } else { None diff --git a/wicket/src/ui/mod.rs b/wicket/src/ui/mod.rs index 1430a67659..b8e86d9974 100644 --- a/wicket/src/ui/mod.rs +++ b/wicket/src/ui/mod.rs @@ -11,6 +11,7 @@ mod widgets; mod wrap; use crate::{Action, Cmd, State, Term}; +use ratatui::prelude::Rect; use ratatui::widgets::ListState; use slog::{o, Logger}; @@ -25,47 +26,35 @@ pub use panes::UpdatePane; /// The primary display representation. It's sole purpose is to dispatch /// [`Cmd`]s to the underlying splash and main screens. /// -// Note: It would be nice to use an enum here, but swapping between enum -// variants requires taking the screen by value or having a wrapper struct with -// an option so we can `take` the inner value. This is unergomic, so we just go -// with the simple solution. +/// We move controls out of their owning field's option and put them +/// in current as necessary. We start out in `splash` and never +/// transition back to it. pub struct Screen { #[allow(unused)] log: slog::Logger, - splash: Option, - main: MainScreen, + main: Option>, + current: Box, } impl Screen { pub fn new(log: &Logger) -> Screen { let log = log.new(o!("component" => "Screen")); - Screen { - splash: Some(SplashScreen::new()), - main: MainScreen::new(&log), - log, - } + let main = Some(Box::new(MainScreen::new(&log)) as Box); + let current = Box::new(SplashScreen::new()) as Box; + Screen { log, main, current } } - /// Compute the layout of the `MainScreen` + /// Compute the layout of the current screen /// // A draw is issued after every resize, so no need to return an Action pub fn resize(&mut self, state: &mut State, width: u16, height: u16) { state.screen_width = width; state.screen_height = height; - - // Size the main screen - self.main.resize(state, width, height); + self._resize(state); } pub fn on(&mut self, state: &mut State, cmd: Cmd) -> Option { - if let Some(splash) = &mut self.splash { - if splash.on(cmd) { - self.splash = None; - } - Some(Action::Redraw) - } else { - self.main.on(state, cmd) - } + self.current.on(state, cmd) } pub fn draw( @@ -73,13 +62,29 @@ impl Screen { state: &State, terminal: &mut Term, ) -> anyhow::Result<()> { - if let Some(splash) = &self.splash { - splash.draw(terminal)?; - } else { - self.main.draw(state, terminal)?; - } + terminal.draw(|f| { + let active = true; + self.current.draw(state, f, f.size(), active); + })?; Ok(()) } + + pub fn switch(&mut self, state: &mut State) { + if self.main.is_some() { + self.current = self.main.take().unwrap(); + self._resize(state); + } + } + + fn _resize(&mut self, state: &mut State) { + let rect = Rect { + x: 0, + y: 0, + width: state.screen_width, + height: state.screen_height, + }; + self.current.resize(state, rect); + } } // Helper type to wrap a list diff --git a/wicket/src/ui/splash.rs b/wicket/src/ui/splash.rs index cc8ab0bff8..cf61266fc9 100644 --- a/wicket/src/ui/splash.rs +++ b/wicket/src/ui/splash.rs @@ -10,7 +10,7 @@ use super::defaults::colors::*; use super::defaults::dimensions::RectExt; use super::defaults::style; use super::widgets::{Logo, LogoState, LOGO_HEIGHT, LOGO_WIDTH}; -use crate::{Cmd, Frame, Term}; +use crate::{Action, Cmd, Control, Frame}; use ratatui::style::Style; use ratatui::widgets::Block; @@ -56,25 +56,30 @@ impl SplashScreen { } } -impl SplashScreen { - pub fn draw(&self, terminal: &mut Term) -> anyhow::Result<()> { - terminal.draw(|f| { - self.draw_background(f); - self.animate_logo(f); - })?; - Ok(()) +impl Control for SplashScreen { + fn draw( + &mut self, + _: &crate::State, + frame: &mut Frame<'_>, + _: ratatui::prelude::Rect, + _: bool, + ) { + self.draw_background(frame); + self.animate_logo(frame); } - /// Return true if the splash screen should transition to the main screen, false - /// if it should keep animating. - pub fn on(&mut self, cmd: Cmd) -> bool { + fn on(&mut self, _: &mut crate::State, cmd: Cmd) -> Option { match cmd { Cmd::Tick => { self.state.frame += 1; - self.state.frame >= TOTAL_FRAMES + if self.state.frame >= TOTAL_FRAMES { + Some(Action::SwitchScreen) + } else { + Some(Action::Redraw) + } } // Allow the user to skip the splash screen with any key press - _ => true, + _ => Some(Action::SwitchScreen), } } }