diff --git a/src/tui/input.rs b/src/tui/input.rs index 4abe2f32..e6a291bb 100644 --- a/src/tui/input.rs +++ b/src/tui/input.rs @@ -31,7 +31,7 @@ impl InputEngine { }), }, InputBinding::new(KeyCode::Char('r'), Action::ReloadCollection), - InputBinding::new(KeyCode::Char(' '), Action::Fullscreen), + InputBinding::new(KeyCode::F(11), Action::Fullscreen), InputBinding::new(KeyCode::BackTab, Action::FocusPrevious), InputBinding::new(KeyCode::Tab, Action::FocusNext), InputBinding::new(KeyCode::Up, Action::Up), @@ -174,6 +174,7 @@ impl Display for KeyCombination { KeyCode::Right => write!(f, "→"), KeyCode::Esc => write!(f, ""), KeyCode::Enter => write!(f, ""), + KeyCode::F(num) => write!(f, "F{}", num), KeyCode::Char(' ') => write!(f, ""), KeyCode::Char(c) => write!(f, "<{c}>"), // Punting on everything else until we need it diff --git a/src/tui/view/component/misc.rs b/src/tui/view/component/misc.rs index 17c36ae7..f663bd56 100644 --- a/src/tui/view/component/misc.rs +++ b/src/tui/view/component/misc.rs @@ -43,16 +43,16 @@ impl Component for ErrorModal { fn update( &mut self, _context: &mut UpdateContext, - message: Event, + event: Event, ) -> UpdateOutcome { - match message { + match event { // Extra close action Event::Input { action: Some(Action::Interact), .. } => UpdateOutcome::Propagate(Event::CloseModal), - _ => UpdateOutcome::Propagate(message), + _ => UpdateOutcome::Propagate(event), } } } @@ -149,9 +149,9 @@ impl Component for PromptModal { fn update( &mut self, _context: &mut UpdateContext, - message: Event, + event: Event, ) -> UpdateOutcome { - match message { + match event { // Submit Event::Input { action: Some(Action::Interact), @@ -163,15 +163,19 @@ impl Component for PromptModal { UpdateOutcome::Propagate(Event::CloseModal) } - // All other input gets forwarded to the text editor (except cancel) - Event::Input { event, action } - if action != Some(Action::Cancel) => - { + // Make sure cancel gets propagated to close the modal + event @ Event::Input { + action: Some(Action::Cancel), + .. + } => UpdateOutcome::Propagate(event), + + // All other input gets forwarded to the text editor + Event::Input { event, .. } => { self.text_area.input(event); UpdateOutcome::Consumed } - _ => UpdateOutcome::Propagate(message), + _ => UpdateOutcome::Propagate(event), } } } diff --git a/src/tui/view/component/mod.rs b/src/tui/view/component/mod.rs index 15d62310..3a80c85e 100644 --- a/src/tui/view/component/mod.rs +++ b/src/tui/view/component/mod.rs @@ -16,7 +16,7 @@ use crate::{ input::{Action, InputEngine}, message::{Message, MessageSender}, view::{ - component::root::RootMode, + component::root::FullscreenMode, state::{Notification, RequestState}, theme::Theme, Frame, @@ -46,10 +46,10 @@ pub trait Component: Debug + Display { fn update( &mut self, _context: &mut UpdateContext, - message: Event, + event: Event, ) -> UpdateOutcome { // By default just forward to our parent - UpdateOutcome::Propagate(message) + UpdateOutcome::Propagate(event) } /// Which, if any, of this component's children currently has focus? The @@ -139,8 +139,8 @@ pub enum Event { state: RequestState, }, - /// Change the root view mode - OpenView(RootMode), + /// Enter a particular fullscreen mode. If we're already in that mode, exit + ToggleFullscreen(FullscreenMode), /// Show a modal to the user OpenModal { diff --git a/src/tui/view/component/modal.rs b/src/tui/view/component/modal.rs index 551485f1..6d2f60eb 100644 --- a/src/tui/view/component/modal.rs +++ b/src/tui/view/component/modal.rs @@ -94,9 +94,9 @@ impl Component for ModalQueue { fn update( &mut self, _context: &mut UpdateContext, - message: Event, + event: Event, ) -> UpdateOutcome { - match message { + match event { // Close the active modal. If there's no modal open, we'll propagate // the event down Event::Input { @@ -111,7 +111,7 @@ impl Component for ModalQueue { UpdateOutcome::Consumed } // Modal wasn't open, so don't consume the event - None => UpdateOutcome::Propagate(message), + None => UpdateOutcome::Propagate(event), } } @@ -121,7 +121,7 @@ impl Component for ModalQueue { UpdateOutcome::Consumed } - _ => UpdateOutcome::Propagate(message), + _ => UpdateOutcome::Propagate(event), } } diff --git a/src/tui/view/component/primary.rs b/src/tui/view/component/primary.rs index 7bf6cd47..31b5051f 100644 --- a/src/tui/view/component/primary.rs +++ b/src/tui/view/component/primary.rs @@ -82,6 +82,16 @@ impl PrimaryView { self.profile_list_pane.profiles.selected() } + /// Expose request pane, for fullscreening + pub fn request_pane(&self) -> &RequestPane { + &self.request_pane + } + + /// Expose request pane, for fullscreening + pub fn request_pane_mut(&mut self) -> &mut RequestPane { + &mut self.request_pane + } + /// Expose response pane, for fullscreening pub fn response_pane(&self) -> &ResponsePane { &self.response_pane @@ -97,9 +107,9 @@ impl Component for PrimaryView { fn update( &mut self, context: &mut UpdateContext, - message: Event, + event: Event, ) -> UpdateOutcome { - match message { + match event { // Send HTTP request (bubbled up from child) Event::HttpSendRequest => { if let Some(recipe) = self.selected_recipe() { @@ -130,7 +140,7 @@ impl Component for PrimaryView { UpdateOutcome::Consumed } - _ => UpdateOutcome::Propagate(message), + _ => UpdateOutcome::Propagate(event), } } @@ -236,9 +246,9 @@ impl Component for ProfileListPane { fn update( &mut self, _context: &mut UpdateContext, - message: Event, + event: Event, ) -> UpdateOutcome { - match message { + match event { Event::Input { action: Some(Action::Up), .. @@ -253,7 +263,7 @@ impl Component for ProfileListPane { self.profiles.next(); UpdateOutcome::Consumed } - _ => UpdateOutcome::Propagate(message), + _ => UpdateOutcome::Propagate(event), } } } @@ -299,7 +309,7 @@ impl Component for RecipeListPane { fn update( &mut self, context: &mut UpdateContext, - message: Event, + event: Event, ) -> UpdateOutcome { let mut load_from_repo = |pane: &RecipeListPane| -> UpdateOutcome { if let Some(recipe) = pane.recipes.selected() { @@ -310,7 +320,7 @@ impl Component for RecipeListPane { UpdateOutcome::Consumed }; - match message { + match event { Event::Input { action: Some(Action::Interact), .. @@ -333,7 +343,7 @@ impl Component for RecipeListPane { self.recipes.next(); load_from_repo(self) } - _ => UpdateOutcome::Propagate(message), + _ => UpdateOutcome::Propagate(event), } } } diff --git a/src/tui/view/component/request.rs b/src/tui/view/component/request.rs index ebd03909..b856731e 100644 --- a/src/tui/view/component/request.rs +++ b/src/tui/view/component/request.rs @@ -4,8 +4,8 @@ use crate::{ input::Action, view::{ component::{ - primary::PrimaryPane, Component, Draw, Event, UpdateContext, - UpdateOutcome, + primary::PrimaryPane, root::FullscreenMode, Component, Draw, + Event, UpdateContext, UpdateOutcome, }, state::{FixedSelect, StatefulSelect}, util::{layout, BlockBrick, TabBrick, ToTui}, @@ -45,9 +45,9 @@ impl Component for RequestPane { fn update( &mut self, _context: &mut UpdateContext, - message: Event, + event: Event, ) -> UpdateOutcome { - match message { + match event { Event::Input { action: Some(action), .. @@ -60,9 +60,15 @@ impl Component for RequestPane { self.tabs.next(); UpdateOutcome::Consumed } - _ => UpdateOutcome::Propagate(message), + + // Enter fullscreen + Action::Fullscreen => UpdateOutcome::Propagate( + Event::ToggleFullscreen(FullscreenMode::Request), + ), + + _ => UpdateOutcome::Propagate(event), }, - _ => UpdateOutcome::Propagate(message), + _ => UpdateOutcome::Propagate(event), } } } diff --git a/src/tui/view/component/response.rs b/src/tui/view/component/response.rs index 2d9d4a62..2b986c58 100644 --- a/src/tui/view/component/response.rs +++ b/src/tui/view/component/response.rs @@ -2,7 +2,7 @@ use crate::tui::{ input::Action, view::{ component::{ - primary::PrimaryPane, root::RootMode, Component, Draw, Event, + primary::PrimaryPane, root::FullscreenMode, Component, Draw, Event, UpdateContext, UpdateOutcome, }, state::{FixedSelect, RequestState, StatefulSelect}, @@ -43,9 +43,9 @@ impl Component for ResponsePane { fn update( &mut self, _context: &mut UpdateContext, - message: Event, + event: Event, ) -> UpdateOutcome { - match message { + match event { Event::Input { action: Some(action), .. @@ -62,16 +62,12 @@ impl Component for ResponsePane { // Enter fullscreen Action::Fullscreen => UpdateOutcome::Propagate( - Event::OpenView(RootMode::Response), + Event::ToggleFullscreen(FullscreenMode::Response), ), - // Exit fullscreen - Action::Cancel => { - UpdateOutcome::Propagate(Event::OpenView(RootMode::Primary)) - } - _ => UpdateOutcome::Propagate(message), + _ => UpdateOutcome::Propagate(event), }, - _ => UpdateOutcome::Propagate(message), + _ => UpdateOutcome::Propagate(event), } } } @@ -172,9 +168,7 @@ impl<'a> Draw> for ResponsePane { // Main content for the response match self.tabs.selected() { ResponseTab::Body => { - // Render the pretty body if it's available, - // otherwise fall back to the regular one - let body: &str = pretty_body + let body = pretty_body .as_deref() .unwrap_or(response.body.text()); frame.render_widget( @@ -194,7 +188,7 @@ impl<'a> Draw> for ResponsePane { } // Sadge - RequestState::RequestError { error, .. } => { + RequestState::RequestError { error } => { frame.render_widget( Paragraph::new(error.to_tui(context)) .wrap(Wrap::default()), diff --git a/src/tui/view/component/root.rs b/src/tui/view/component/root.rs index 694f2dfa..ac7550bc 100644 --- a/src/tui/view/component/root.rs +++ b/src/tui/view/component/root.rs @@ -8,6 +8,7 @@ use crate::{ misc::{HelpText, NotificationText}, modal::ModalQueue, primary::{PrimaryView, PrimaryViewProps}, + request::RequestPaneProps, response::ResponsePaneProps, Component, Draw, Event, UpdateContext, UpdateOutcome, }, @@ -32,25 +33,25 @@ pub struct Root { /// - It has beed focused by the user during this process /// This will be populated on-demand when a user selects a recipe in the /// list. - #[display(fmt = "")] active_requests: HashMap, - /// What is we lookin at? - mode: RootMode, + fullscreen_mode: Option, // ==== Children ===== /// We hold onto the primary view even when it's not visible, because we /// don't want the state to reset when changing views primary_view: PrimaryView, + // fullscreen_view: Option, modal_queue: ModalQueue, notification_text: Option, } -/// View mode of the root component -#[derive(Copy, Clone, Debug, Default)] -pub enum RootMode { - /// Show the normal pane view - #[default] - Primary, +/// The various things that can be requested (haha get it, requested) to be +/// shown in fullscreen. If one of these is requested while not available, we +/// simply won't show it. +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum FullscreenMode { + /// Fullscreen the active request recipe + Request, /// Fullscreen the active response Response, } @@ -60,7 +61,7 @@ impl Root { Self { // State active_requests: HashMap::new(), - mode: RootMode::default(), + fullscreen_mode: None, // Children primary_view: PrimaryView::new(collection), @@ -106,9 +107,9 @@ impl Component for Root { fn update( &mut self, context: &mut UpdateContext, - message: Event, + event: Event, ) -> UpdateOutcome { - match message { + match event { Event::Init => { // Load the initial state for the selected recipe if let Some(recipe) = self.primary_view.selected_recipe() { @@ -124,7 +125,14 @@ impl Component for Root { } // Other state messages - Event::OpenView(mode) => self.mode = mode, + Event::ToggleFullscreen(mode) => { + // If we're already in the given mode, exit + self.fullscreen_mode = if Some(mode) == self.fullscreen_mode { + None + } else { + Some(mode) + }; + } Event::Notify(notification) => { self.notification_text = Some(NotificationText::new(notification)) @@ -143,7 +151,7 @@ impl Component for Root { Event::Input { .. } => {} // There shouldn't be anything left unhandled. Bubble up to log it - _ => return UpdateOutcome::Propagate(message), + _ => return UpdateOutcome::Propagate(event), } UpdateOutcome::Consumed } @@ -151,9 +159,14 @@ impl Component for Root { fn children(&mut self) -> Vec<&mut dyn Component> { vec![ &mut self.modal_queue, - match self.mode { - RootMode::Primary => &mut self.primary_view, - RootMode::Response => self.primary_view.response_pane_mut(), + match self.fullscreen_mode { + None => &mut self.primary_view, + Some(FullscreenMode::Request) => { + self.primary_view.request_pane_mut() + } + Some(FullscreenMode::Response) => { + self.primary_view.response_pane_mut() + } }, ] } @@ -175,8 +188,8 @@ impl Draw for Root { ); // Main content - match self.mode { - RootMode::Primary => self.primary_view.draw( + match self.fullscreen_mode { + None => self.primary_view.draw( context, PrimaryViewProps { active_request: self.active_request(), @@ -184,15 +197,28 @@ impl Draw for Root { frame, main_chunk, ), - RootMode::Response => self.primary_view.response_pane().draw( - context, - ResponsePaneProps { - active_request: self.active_request(), - is_selected: false, - }, - frame, - main_chunk, - ), + Some(FullscreenMode::Request) => { + self.primary_view.request_pane().draw( + context, + RequestPaneProps { + is_selected: false, + selected_recipe: self.primary_view.selected_recipe(), + }, + frame, + main_chunk, + ); + } + Some(FullscreenMode::Response) => { + self.primary_view.response_pane().draw( + context, + ResponsePaneProps { + is_selected: false, + active_request: self.active_request(), + }, + frame, + main_chunk, + ); + } } // Footer diff --git a/src/tui/view/mod.rs b/src/tui/view/mod.rs index f6e5920b..c5f83b2e 100644 --- a/src/tui/view/mod.rs +++ b/src/tui/view/mod.rs @@ -50,7 +50,7 @@ impl View { root: Root::new(collection), }; // Tell the components to wake up - view.handle_message(Event::Init); + view.handle_event(Event::Init); view } @@ -76,7 +76,7 @@ impl View { recipe_id: RequestRecipeId, state: RequestState, ) { - self.handle_message(Event::HttpSetState { recipe_id, state }); + self.handle_event(Event::HttpSetState { recipe_id, state }); } /// Open a new modal. The input can be anything that converts to modal @@ -86,7 +86,7 @@ impl View { modal: impl IntoModal + 'static, priority: ModalPriority, ) { - self.handle_message(Event::OpenModal { + self.handle_event(Event::OpenModal { modal: Box::new(modal.into_modal()), priority, }); @@ -95,7 +95,7 @@ impl View { /// Send an informational notification to the user pub fn notify(&mut self, message: String) { let notification = Notification::new(message); - self.handle_message(Event::Notify(notification)); + self.handle_event(Event::Notify(notification)); } /// Update the view according to an input event from the user. If possible, @@ -106,22 +106,22 @@ impl View { event: crossterm::event::Event, action: Option, ) { - self.handle_message(Event::Input { event, action }) + self.handle_event(Event::Input { event, action }) } - /// Process a view message by passing it to the root component and letting + /// Process a view event by passing it to the root component and letting /// it pass it down the tree - fn handle_message(&mut self, message: Event) { - let span = trace_span!("View message", ?message); + fn handle_event(&mut self, event: Event) { + let span = trace_span!("View event", ?event); span.in_scope(|| { let mut context = self.update_context(); - match Self::update_all(&mut self.root, &mut context, message) { + match Self::update_all(&mut self.root, &mut context, event) { UpdateOutcome::Consumed => { - trace!("View message consumed") + trace!("View event consumed") } - // Consumer didn't eat the message - huh? + // Consumer didn't eat the event - huh? UpdateOutcome::Propagate(_) => { - error!("View message was unhandled"); + error!("View event was unhandled"); } } }); @@ -134,31 +134,31 @@ impl View { /// Update the state of a component *and* its children, starting at the /// lowest descendant. Recursively walk up the tree until a component - /// consumes the message. + /// consumes the event. fn update_all( component: &mut dyn Component, context: &mut UpdateContext, - mut message: Event, + mut event: Event, ) -> UpdateOutcome { - // If we have a child, send them the message. If not, eat it ourselves + // If we have a child, send them the event. If not, eat it ourselves for child in component.children() { - let outcome = Self::update_all(child, context, message); // RECURSION + let outcome = Self::update_all(child, context, event); // RECURSION if let UpdateOutcome::Propagate(returned) = outcome { // Keep going to the next child. It's possible the child - // returned something other than the original message, which + // returned something other than the original event, which // we'll just pass along anyway. - message = returned; + event = returned; } else { - trace!(%child, "View message consumed"); + trace!(%child, "View event consumed"); return outcome; } } // None of our children handled it, we'll take it ourselves. // Message is already traced in the parent span, so don't dupe it. - let span = trace_span!("Component handling message", %component); + let span = trace_span!("Component handling", %component); span.in_scope(|| { - let outcome = component.update(context, message); + let outcome = component.update(context, event); trace!(?outcome); outcome })