Skip to content

Commit

Permalink
[wicket] Make screens impl Control
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewjstone committed Sep 15, 2023
1 parent ca311de commit 183ce6e
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 122 deletions.
4 changes: 3 additions & 1 deletion wicket/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ pub enum Action {
Ignition(ComponentId, IgnitionCommand),
StartRackSetup,
StartRackReset,
SwitchScreen,
}

impl Action {
Expand All @@ -96,7 +97,8 @@ impl Action {
| Action::ClearUpdateState(_)
| Action::Ignition(_, _)
| Action::StartRackSetup
| Action::StartRackReset => true,
| Action::StartRackReset
| Action::SwitchScreen => true,
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions wicket/src/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}
Expand Down
154 changes: 74 additions & 80 deletions wicket/src/ui/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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<dyn Control>),
("update", Box::new(UpdatePane::new(log))),
("rack setup", Box::<RackSetupPane>::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<Action> {
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;
Expand All @@ -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<Action> {
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<dyn Control>),
("update", Box::new(UpdatePane::new(log))),
("rack setup", Box::<RackSetupPane>::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(),
}
}

Expand All @@ -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
Expand Down
61 changes: 33 additions & 28 deletions wicket/src/ui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand All @@ -25,61 +26,65 @@ 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<SplashScreen>,
main: MainScreen,
main: Option<Box<dyn Control>>,
current: Box<dyn Control>,
}

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<dyn Control>);
let current = Box::new(SplashScreen::new()) as Box<dyn Control>;
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<Action> {
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(
&mut self,
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
Expand Down
31 changes: 18 additions & 13 deletions wicket/src/ui/splash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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<crate::Action> {
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),
}
}
}

0 comments on commit 183ce6e

Please sign in to comment.