From e5cd71450b5cc70988d72fd5c70ac1ebe4273ca6 Mon Sep 17 00:00:00 2001 From: Erik Kundt Date: Fri, 31 May 2024 13:35:44 +0200 Subject: [PATCH] bin: Adjust to new view props --- bin/commands/inbox/select/ui.rs | 150 ++++++++++++--------------- bin/commands/issue/select/ui.rs | 140 ++++++++++++------------- bin/commands/patch/select.rs | 6 +- bin/commands/patch/select/ui.rs | 175 +++++++++++++++----------------- 4 files changed, 217 insertions(+), 254 deletions(-) diff --git a/bin/commands/inbox/select/ui.rs b/bin/commands/inbox/select/ui.rs index 9ac6533c..c92001b6 100644 --- a/bin/commands/inbox/select/ui.rs +++ b/bin/commands/inbox/select/ui.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use std::str::FromStr; +use ratatui::Frame; use tokio::sync::mpsc::UnboundedSender; use termion::event::Key; @@ -108,38 +109,33 @@ impl<'a> From<&State> for BrowserProps<'a> { } } -pub struct Browser<'a> { - /// Internal props - props: BrowserProps<'a>, +pub struct Browser { /// Notification widget notifications: Widget, /// Search widget search: Widget, } -impl<'a: 'static> Browser<'a> { +impl Browser { fn new(tx: UnboundedSender) -> Self { - let props = BrowserProps::default(); - Self { - props: props.clone(), notifications: Container::default() .header( Header::default() - .columns( - [ - Column::new("", Constraint::Length(0)), - Column::new(Text::from(props.header), Constraint::Fill(1)), - ] - .to_vec(), - ) - .cutoff(props.cutoff, props.cutoff_after) + // .columns( + // [ + // Column::new("", Constraint::Length(0)), + // Column::new(Text::from(props.header), Constraint::Fill(1)), + // ] + // .to_vec(), + // ) + // .cutoff(props.cutoff, props.cutoff_after) .to_widget(tx.clone()), ) .content( Table::::default() .to_widget(tx.clone()) - .on_event(|s, _| { + .on_event(|_, s, _| { Some(Message::Select { selected: s.and_then(|s| s.unwrap_usize()), }) @@ -178,23 +174,27 @@ impl<'a: 'static> Browser<'a> { } } -impl<'a: 'static> View for Browser<'a> { +impl View for Browser { type Message = Message; type State = State; - fn handle_event(&mut self, key: Key) -> Option { - if self.props.show_search { + fn handle_event(&mut self, props: Option<&ViewProps>, key: Key) -> Option { + let default = BrowserProps::default(); + let props = props + .and_then(|props| props.inner_ref::()) + .unwrap_or(&default); + + if props.show_search { self.search.handle_event(key); None } else { match key { Key::Char('/') => Some(Message::OpenSearch), - Key::Char('\n') => self - .props + Key::Char('\n') => props .selected - .and_then(|selected| self.props.notifications.get(selected)) + .and_then(|selected| props.notifications.get(selected)) .map(|notif| { - let selection = match self.props.mode.selection() { + let selection = match props.mode.selection() { SelectionMode::Operation => Selection::default() .with_operation(InboxOperation::Show.to_string()) .with_id(notif.id), @@ -205,10 +205,9 @@ impl<'a: 'static> View for Browser<'a> { selection: Some(selection), } }), - Key::Char('c') => self - .props + Key::Char('c') => props .selected - .and_then(|selected| self.props.notifications.get(selected)) + .and_then(|selected| props.notifications.get(selected)) .map(|notif| Message::Exit { selection: Some( Selection::default() @@ -224,28 +223,27 @@ impl<'a: 'static> View for Browser<'a> { } } - fn update(&mut self, state: &Self::State, props: Option) { - if let Some(props) = props.and_then(|props| props.inner::()) { - self.props = props; - } else { - self.props = BrowserProps::from(state); - } - + fn update(&mut self, _props: Option<&ViewProps>, state: &Self::State) { self.notifications.update(state); self.search.update(state); } - fn render(&self, frame: &mut ratatui::Frame, props: RenderProps) { - if self.props.show_search { + fn render(&self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) { + let default = BrowserProps::default(); + let props = props + .and_then(|props| props.inner_ref::()) + .unwrap_or(&default); + + if props.show_search { let [table_area, search_area] = - Layout::vertical([Constraint::Min(1), Constraint::Length(2)]).areas(props.area); + Layout::vertical([Constraint::Min(1), Constraint::Length(2)]).areas(render.area); self.notifications - .render(frame, RenderProps::from(table_area)); + .render(RenderProps::from(table_area), frame); self.search - .render(frame, RenderProps::from(search_area).focus(props.focus)); + .render(RenderProps::from(search_area).focus(render.focus), frame); } else { - self.notifications.render(frame, props); + self.notifications.render(render, frame); } } } @@ -321,7 +319,7 @@ impl<'a: 'static> View for BrowserPage<'a> { type Message = Message; type State = State; - fn handle_event(&mut self, key: Key) -> Option { + fn handle_event(&mut self, _props: Option<&ViewProps>, key: Key) -> Option { self.sections.handle_event(key); if self.props.handle_keys { @@ -335,31 +333,25 @@ impl<'a: 'static> View for BrowserPage<'a> { None } - fn update(&mut self, state: &Self::State, props: Option) { - if let Some(props) = props.and_then(|props| props.inner::()) { - self.props = props; - } else { - self.props = BrowserPageProps::from(state); - } - + fn update(&mut self, _props: Option<&ViewProps>, state: &Self::State) { self.sections.update(state); self.shortcuts.update(state); } - fn render(&self, frame: &mut ratatui::Frame, props: RenderProps) { - let page_size = props.area.height.saturating_sub(6) as usize; + fn render(&self, _props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) { + let page_size = render.area.height.saturating_sub(6) as usize; let [content_area, shortcuts_area] = - Layout::vertical([Constraint::Min(1), Constraint::Length(1)]).areas(props.area); + Layout::vertical([Constraint::Min(1), Constraint::Length(1)]).areas(render.area); self.sections.render( - frame, RenderProps::from(content_area) .layout(Layout::horizontal([Constraint::Min(1)])) .focus(true), + frame, ); self.shortcuts - .render(frame, RenderProps::from(shortcuts_area)); + .render(RenderProps::from(shortcuts_area), frame); // TODO: Find better solution if page_size != self.props.page_size { @@ -387,7 +379,7 @@ impl Search { props: SearchProps {}, input: TextField::default() .to_widget(tx.clone()) - .on_event(|s, _| { + .on_event(|_, s, _| { Some(Message::UpdateSearch { value: s.and_then(|i| i.unwrap_string()).unwrap_or_default(), }) @@ -408,7 +400,7 @@ impl View for Search { type Message = Message; type State = State; - fn handle_event(&mut self, key: termion::event::Key) -> Option { + fn handle_event(&mut self, _props: Option<&ViewProps>, key: Key) -> Option { match key { Key::Esc => Some(Message::CloseSearch), Key::Char('\n') => Some(Message::ApplySearch), @@ -419,20 +411,16 @@ impl View for Search { } } - fn update(&mut self, state: &Self::State, props: Option) { - if let Some(props) = props.and_then(|props| props.inner::()) { - self.props = props; - } - + fn update(&mut self, _props: Option<&ViewProps>, state: &Self::State) { self.input.update(state); } - fn render(&self, frame: &mut ratatui::Frame, props: RenderProps) { + fn render(&self, _props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) { let layout = Layout::horizontal(Constraint::from_mins([0])) .horizontal_margin(1) - .split(props.area); + .split(render.area); - self.input.render(frame, RenderProps::from(layout[0])); + self.input.render(RenderProps::from(layout[0]), frame); } } @@ -456,22 +444,19 @@ impl<'a> From<&State> for HelpPageProps<'a> { } } -pub struct HelpPage<'a> { - /// Internal props - props: HelpPageProps<'a>, +pub struct HelpPage { /// Content widget content: Widget, /// Shortcut widget shortcuts: Widget, } -impl<'a: 'static> HelpPage<'a> { +impl HelpPage { pub fn new(tx: UnboundedSender) -> Self where Self: Sized, { Self { - props: HelpPageProps::default(), content: Container::default() .header(Header::default().to_widget(tx.clone()).on_update(|_| { HeaderProps::default() @@ -482,7 +467,7 @@ impl<'a: 'static> HelpPage<'a> { .content( Paragraph::default() .to_widget(tx.clone()) - .on_event(|s, _| { + .on_event(|_, s, _| { Some(Message::ScrollHelp { progress: s.and_then(|p| p.unwrap_usize()).unwrap_or_default(), }) @@ -527,11 +512,11 @@ impl<'a: 'static> HelpPage<'a> { } } -impl<'a: 'static> View for HelpPage<'a> { +impl View for HelpPage { type Message = Message; type State = State; - fn handle_event(&mut self, key: termion::event::Key) -> Option { + fn handle_event(&mut self, _props: Option<&ViewProps>, key: Key) -> Option { match key { Key::Esc | Key::Ctrl('c') => Some(Message::Exit { selection: None }), Key::Char('?') => Some(Message::LeavePage), @@ -542,30 +527,29 @@ impl<'a: 'static> View for HelpPage<'a> { } } - fn update(&mut self, state: &Self::State, props: Option) { - if let Some(props) = props.and_then(|props| props.inner::()) { - self.props = props; - } else { - self.props = HelpPageProps::from(state); - } - + fn update(&mut self, _props: Option<&ViewProps>, state: &Self::State) { self.content.update(state); self.shortcuts.update(state); } - fn render(&self, frame: &mut ratatui::Frame, props: RenderProps) { - let page_size = props.area.height.saturating_sub(6) as usize; + fn render(&self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) { + let default = HelpPageProps::default(); + let props = props + .and_then(|props| props.inner_ref::()) + .unwrap_or(&default); + + let page_size = render.area.height.saturating_sub(6) as usize; let [content_area, shortcuts_area] = - Layout::vertical([Constraint::Min(1), Constraint::Length(1)]).areas(props.area); + Layout::vertical([Constraint::Min(1), Constraint::Length(1)]).areas(render.area); self.content - .render(frame, RenderProps::from(content_area).focus(true)); + .render(RenderProps::from(content_area).focus(true), frame); self.shortcuts - .render(frame, RenderProps::from(shortcuts_area)); + .render(RenderProps::from(content_area).focus(true), frame); // TODO: Find better solution - if page_size != self.props.page_size { + if page_size != props.page_size { self.content.send(Message::HelpPageSize(page_size)); } } diff --git a/bin/commands/issue/select/ui.rs b/bin/commands/issue/select/ui.rs index 650c4376..8c8a1dad 100644 --- a/bin/commands/issue/select/ui.rs +++ b/bin/commands/issue/select/ui.rs @@ -3,6 +3,7 @@ use std::str::FromStr; use std::vec; use radicle::issue::{self, CloseReason}; +use ratatui::Frame; use tokio::sync::mpsc::UnboundedSender; use termion::event::Key; @@ -128,33 +129,29 @@ impl<'a> From<&State> for BrowserProps<'a> { } } -pub struct Browser<'a> { - /// Internal props - props: BrowserProps<'a>, +pub struct Browser { /// Notifications widget issues: Widget, /// Search widget search: Widget, } -impl<'a: 'static> Browser<'a> { +impl Browser { fn new(tx: UnboundedSender) -> Self { - let props = BrowserProps::default(); Self { - props: props.clone(), issues: Container::default() .header( Header::default() - .columns(props.header.clone()) - .cutoff(props.cutoff, props.cutoff_after) + // .columns(props.header.clone()) + // .cutoff(props.cutoff, props.cutoff_after) .to_widget(tx.clone()), ) .content( Table::::default() .to_widget(tx.clone()) - .on_event(|state, _| { + .on_event(|_, s, _| { Some(Message::Select { - selected: state.and_then(|s| s.unwrap_usize()), + selected: s.and_then(|s| s.unwrap_usize()), }) }) .on_update(|state| { @@ -191,26 +188,31 @@ impl<'a: 'static> Browser<'a> { } } -impl<'a: 'static> View for Browser<'a> { +impl View for Browser { type Message = Message; type State = State; - fn handle_event(&mut self, key: Key) -> Option { - if self.props.show_search { + fn handle_event(&mut self, props: Option<&ViewProps>, key: Key) -> Option { + let default = BrowserProps::default(); + let props = props + .and_then(|props| props.inner_ref::()) + .unwrap_or(&default); + + if props.show_search { self.search.handle_event(key); None } else { match key { Key::Char('/') => Some(Message::OpenSearch), Key::Char('\n') => { - let operation = match self.props.mode { + let operation = match props.mode { Mode::Operation => Some(IssueOperation::Show.to_string()), Mode::Id => None, }; - self.props + props .selected - .and_then(|selected| self.props.issues.get(selected)) + .and_then(|selected| props.issues.get(selected)) .map(|issue| Message::Exit { selection: Some(Selection { operation, @@ -219,10 +221,9 @@ impl<'a: 'static> View for Browser<'a> { }), }) } - Key::Char('e') => self - .props + Key::Char('e') => props .selected - .and_then(|selected| self.props.issues.get(selected)) + .and_then(|selected| props.issues.get(selected)) .map(|issue| Message::Exit { selection: Some(Selection { operation: Some(IssueOperation::Edit.to_string()), @@ -238,27 +239,26 @@ impl<'a: 'static> View for Browser<'a> { } } - fn update(&mut self, state: &Self::State, props: Option) { - if let Some(props) = props.and_then(|props| props.inner::()) { - self.props = props; - } else { - self.props = BrowserProps::from(state); - } - + fn update(&mut self, _props: Option<&ViewProps>, state: &Self::State) { self.issues.update(state); self.search.update(state); } - fn render(&self, frame: &mut ratatui::Frame, props: RenderProps) { - if self.props.show_search { - let [table_area, search_area] = - Layout::vertical([Constraint::Min(1), Constraint::Length(2)]).areas(props.area); + fn render(&self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) { + let default = BrowserProps::default(); + let props = props + .and_then(|props| props.inner_ref::()) + .unwrap_or(&default); - self.issues.render(frame, RenderProps::from(table_area)); + if props.show_search { + let [table_area, search_area] = + Layout::vertical([Constraint::Min(1), Constraint::Length(2)]).areas(render.area); + + self.issues.render(RenderProps::from(table_area), frame); self.search - .render(frame, RenderProps::from(search_area).focus(props.focus)); + .render(RenderProps::from(search_area).focus(render.focus), frame); } else { - self.issues.render(frame, props); + self.issues.render(render, frame); } } } @@ -334,7 +334,7 @@ impl<'a: 'static> View for BrowserPage<'a> { type Message = Message; type State = State; - fn handle_event(&mut self, key: Key) -> Option { + fn handle_event(&mut self, _props: Option<&ViewProps>, key: Key) -> Option { self.sections.handle_event(key); if self.props.handle_keys { @@ -348,31 +348,25 @@ impl<'a: 'static> View for BrowserPage<'a> { None } - fn update(&mut self, state: &Self::State, props: Option) { - if let Some(props) = props.and_then(|props| props.inner::()) { - self.props = props; - } else { - self.props = BrowserPageProps::from(state); - } - + fn update(&mut self, _props: Option<&ViewProps>, state: &Self::State) { self.sections.update(state); self.shortcuts.update(state); } - fn render(&self, frame: &mut ratatui::Frame, props: RenderProps) { - let page_size = props.area.height.saturating_sub(6) as usize; + fn render(&self, _props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) { + let page_size = render.area.height.saturating_sub(6) as usize; let [content_area, shortcuts_area] = - Layout::vertical([Constraint::Min(1), Constraint::Length(1)]).areas(props.area); + Layout::vertical([Constraint::Min(1), Constraint::Length(1)]).areas(render.area); self.sections.render( - frame, RenderProps::from(content_area) .layout(Layout::horizontal([Constraint::Min(1)])) .focus(true), + frame, ); self.shortcuts - .render(frame, RenderProps::from(shortcuts_area)); + .render(RenderProps::from(shortcuts_area), frame); // TODO: Find better solution if page_size != self.props.page_size { @@ -400,7 +394,7 @@ impl Search { props: SearchProps {}, input: TextField::default() .to_widget(tx.clone()) - .on_event(|s, _| { + .on_event(|_, s, _| { Some(Message::UpdateSearch { value: s.and_then(|i| i.unwrap_string()).unwrap_or_default(), }) @@ -421,7 +415,7 @@ impl View for Search { type Message = Message; type State = State; - fn handle_event(&mut self, key: termion::event::Key) -> Option { + fn handle_event(&mut self, _props: Option<&ViewProps>, key: Key) -> Option { match key { Key::Esc => Some(Message::CloseSearch), Key::Char('\n') => Some(Message::ApplySearch), @@ -432,20 +426,16 @@ impl View for Search { } } - fn update(&mut self, state: &Self::State, props: Option) { - if let Some(props) = props.and_then(|props| props.inner::()) { - self.props = props; - } - + fn update(&mut self, _props: Option<&ViewProps>, state: &Self::State) { self.input.update(state); } - fn render(&self, frame: &mut ratatui::Frame, props: RenderProps) { + fn render(&self, _props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) { let layout = Layout::horizontal(Constraint::from_mins([0])) .horizontal_margin(1) - .split(props.area); + .split(render.area); - self.input.render(frame, RenderProps::from(layout[0])); + self.input.render(RenderProps::from(layout[0]), frame); } } @@ -469,22 +459,19 @@ impl<'a> From<&State> for HelpPageProps<'a> { } } -pub struct HelpPage<'a> { - /// Internal props - props: HelpPageProps<'a>, +pub struct HelpPage { /// Content widget content: Widget, /// Shortcut widget shortcuts: Widget, } -impl<'a: 'static> HelpPage<'a> { +impl HelpPage { pub fn new(tx: UnboundedSender) -> Self where Self: Sized, { Self { - props: HelpPageProps::default(), content: Container::default() .header(Header::default().to_widget(tx.clone()).on_update(|_| { HeaderProps::default() @@ -495,7 +482,7 @@ impl<'a: 'static> HelpPage<'a> { .content( Paragraph::default() .to_widget(tx.clone()) - .on_event(|s, _| { + .on_event(|_, s, _| { Some(Message::ScrollHelp { progress: s.and_then(|p| p.unwrap_usize()).unwrap_or_default(), }) @@ -540,11 +527,11 @@ impl<'a: 'static> HelpPage<'a> { } } -impl<'a: 'static> View for HelpPage<'a> { +impl View for HelpPage { type Message = Message; type State = State; - fn handle_event(&mut self, key: termion::event::Key) -> Option { + fn handle_event(&mut self, _props: Option<&ViewProps>, key: Key) -> Option { match key { Key::Esc | Key::Ctrl('c') => Some(Message::Exit { selection: None }), Key::Char('?') => Some(Message::LeavePage), @@ -555,30 +542,29 @@ impl<'a: 'static> View for HelpPage<'a> { } } - fn update(&mut self, state: &Self::State, props: Option) { - if let Some(props) = props.and_then(|props| props.inner::()) { - self.props = props; - } else { - self.props = HelpPageProps::from(state); - } - + fn update(&mut self, _props: Option<&ViewProps>, state: &Self::State) { self.content.update(state); self.shortcuts.update(state); } - fn render(&self, frame: &mut ratatui::Frame, props: RenderProps) { - let page_size = props.area.height.saturating_sub(6) as usize; + fn render(&self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) { + let default = HelpPageProps::default(); + let props = props + .and_then(|props| props.inner_ref::()) + .unwrap_or(&default); + + let page_size = render.area.height.saturating_sub(6) as usize; let [content_area, shortcuts_area] = - Layout::vertical([Constraint::Min(1), Constraint::Length(1)]).areas(props.area); + Layout::vertical([Constraint::Min(1), Constraint::Length(1)]).areas(render.area); self.content - .render(frame, RenderProps::from(content_area).focus(true)); + .render(RenderProps::from(content_area).focus(true), frame); self.shortcuts - .render(frame, RenderProps::from(shortcuts_area)); + .render(RenderProps::from(content_area).focus(true), frame); // TODO: Find better solution - if page_size != self.props.page_size { + if page_size != props.page_size { self.content.send(Message::HelpPageSize(page_size)); } } diff --git a/bin/commands/patch/select.rs b/bin/commands/patch/select.rs index 0cbfad28..799b39a4 100644 --- a/bin/commands/patch/select.rs +++ b/bin/commands/patch/select.rs @@ -19,8 +19,8 @@ use tui::ui::widget::ToWidget; use tui::{BoxedAny, Channel, Exit, PageStack}; -use self::ui::BrowserPage; use self::ui::HelpPage; +use self::ui::{BrowserPage, BrowserPageProps}; use super::common::Mode; @@ -206,7 +206,9 @@ impl App { let window = Window::default() .page( Page::Browse, - BrowserPage::new(tx.clone()).to_widget(tx.clone()), + BrowserPage::new(tx.clone()) + .to_widget(tx.clone()) + .on_update(|state| BrowserPageProps::from(state).to_boxed_any().into()), ) .page(Page::Help, HelpPage::new(tx.clone()).to_widget(tx.clone())) .to_widget(tx.clone()) diff --git a/bin/commands/patch/select/ui.rs b/bin/commands/patch/select/ui.rs index a70a210f..a9c3a54d 100644 --- a/bin/commands/patch/select/ui.rs +++ b/bin/commands/patch/select/ui.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use std::str::FromStr; use std::vec; +use ratatui::Frame; use tokio::sync::mpsc::UnboundedSender; use termion::event::Key; @@ -10,12 +11,14 @@ use ratatui::layout::{Constraint, Layout}; use ratatui::style::Stylize; use ratatui::text::{Line, Span, Text}; -use radicle::patch::{self, Status}; +use radicle::patch; +use radicle::patch::Status; use radicle_tui as tui; use tui::ui::items::{PatchItem, PatchItemFilter}; use tui::ui::span; +use tui::ui::widget; use tui::ui::widget::container::{ Column, Container, ContainerProps, Footer, FooterProps, Header, HeaderProps, SectionGroup, SectionGroupProps, @@ -24,7 +27,7 @@ use tui::ui::widget::input::{TextField, TextFieldProps}; use tui::ui::widget::list::{Table, TableProps, TableUtils}; use tui::ui::widget::text::{Paragraph, ParagraphProps}; use tui::ui::widget::window::{Shortcuts, ShortcutsProps}; -use tui::ui::widget::{self, ViewProps}; +use tui::ui::widget::ViewProps; use tui::ui::widget::{RenderProps, ToWidget, View}; use tui::{BoxedAny, Selection}; @@ -128,31 +131,27 @@ impl<'a> From<&State> for BrowserProps<'a> { } } -pub struct Browser<'a> { - /// Internal props - props: BrowserProps<'a>, +pub struct Browser { /// Patches widget patches: Widget, /// Search widget search: Widget, } -impl<'a: 'static> Browser<'a> { +impl Browser { fn new(tx: UnboundedSender) -> Self { - let props = BrowserProps::default(); Self { - props: props.clone(), patches: Container::default() .header( Header::default() - .columns(props.header.clone()) - .cutoff(props.cutoff, props.cutoff_after) + // .columns(props.header.clone()) + // .cutoff(props.cutoff, props.cutoff_after) .to_widget(tx.clone()), ) .content( Table::::default() .to_widget(tx.clone()) - .on_event(|s, _| { + .on_event(|_, s, _| { Some(Message::Select { selected: s.and_then(|s| s.unwrap_usize()), }) @@ -191,12 +190,17 @@ impl<'a: 'static> Browser<'a> { } } -impl<'a: 'static> View for Browser<'a> { +impl View for Browser { type Message = Message; type State = State; - fn handle_event(&mut self, key: Key) -> Option { - if self.props.show_search { + fn handle_event(&mut self, props: Option<&ViewProps>, key: Key) -> Option { + let default = BrowserProps::default(); + let props = props + .and_then(|props| props.inner_ref::()) + .unwrap_or(&default); + + if props.show_search { self.search.handle_event(key); None } else { @@ -205,14 +209,14 @@ impl<'a: 'static> View for Browser<'a> { Key::Char('?') => Some(Message::OpenHelp), Key::Char('/') => Some(Message::OpenSearch), Key::Char('\n') => { - let operation = match self.props.mode { + let operation = match props.mode { Mode::Operation => Some(PatchOperation::Show.to_string()), Mode::Id => None, }; - self.props + props .selected - .and_then(|selected| self.props.patches.get(selected)) + .and_then(|selected| props.patches.get(selected)) .map(|patch| Message::Exit { selection: Some(Selection { operation, @@ -221,10 +225,9 @@ impl<'a: 'static> View for Browser<'a> { }), }) } - Key::Char('c') => self - .props + Key::Char('c') => props .selected - .and_then(|selected| self.props.patches.get(selected)) + .and_then(|selected| props.patches.get(selected)) .map(|patch| Message::Exit { selection: Some(Selection { operation: Some(PatchOperation::Checkout.to_string()), @@ -232,10 +235,9 @@ impl<'a: 'static> View for Browser<'a> { args: vec![], }), }), - Key::Char('d') => self - .props + Key::Char('d') => props .selected - .and_then(|selected| self.props.patches.get(selected)) + .and_then(|selected| props.patches.get(selected)) .map(|patch| Message::Exit { selection: Some(Selection { operation: Some(PatchOperation::Diff.to_string()), @@ -251,33 +253,32 @@ impl<'a: 'static> View for Browser<'a> { } } - fn update(&mut self, state: &Self::State, props: Option) { - if let Some(props) = props.and_then(|props| props.inner::()) { - self.props = props; - } else { - self.props = BrowserProps::from(state); - } - + fn update(&mut self, _props: Option<&ViewProps>, state: &Self::State) { self.patches.update(state); self.search.update(state); } - fn render(&self, frame: &mut ratatui::Frame, props: RenderProps) { - if self.props.show_search { - let [table_area, search_area] = - Layout::vertical([Constraint::Min(1), Constraint::Length(2)]).areas(props.area); + fn render(&self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) { + let default = BrowserProps::default(); + let props = props + .and_then(|props| props.inner_ref::()) + .unwrap_or(&default); - self.patches.render(frame, RenderProps::from(table_area)); + if props.show_search { + let [table_area, search_area] = + Layout::vertical([Constraint::Min(1), Constraint::Length(2)]).areas(render.area); + + self.patches.render(RenderProps::from(table_area), frame); self.search - .render(frame, RenderProps::from(search_area).focus(props.focus)); + .render(RenderProps::from(search_area).focus(render.focus), frame); } else { - self.patches.render(frame, props); + self.patches.render(render, frame); } } } #[derive(Clone, Default)] -struct BrowserPageProps<'a> { +pub struct BrowserPageProps<'a> { /// Current page size (height of table content). page_size: usize, /// If this pages' keys should be handled (`false` if search is shown). @@ -309,19 +310,16 @@ impl<'a> From<&State> for BrowserPageProps<'a> { } } -pub struct BrowserPage<'a> { - /// Internal props - props: BrowserPageProps<'a>, +pub struct BrowserPage { /// Sections widget sections: Widget, /// Shortcut widget shortcuts: Widget, } -impl<'a: 'static> BrowserPage<'a> { +impl BrowserPage { pub fn new(tx: UnboundedSender) -> Self { Self { - props: BrowserPageProps::default(), sections: SectionGroup::default() .section(Browser::new(tx.clone()).to_widget(tx.clone())) .to_widget(tx.clone()) @@ -344,14 +342,19 @@ impl<'a: 'static> BrowserPage<'a> { } } -impl<'a: 'static> View for BrowserPage<'a> { +impl View for BrowserPage { type Message = Message; type State = State; - fn handle_event(&mut self, key: Key) -> Option { + fn handle_event(&mut self, props: Option<&ViewProps>, key: Key) -> Option { + let default = BrowserPageProps::default(); + let props = props + .and_then(|props| props.inner_ref::()) + .unwrap_or(&default); + self.sections.handle_event(key); - if self.props.handle_keys { + if props.handle_keys { return match key { Key::Esc | Key::Ctrl('c') => Some(Message::Exit { selection: None }), Key::Char('?') => Some(Message::OpenHelp), @@ -362,34 +365,33 @@ impl<'a: 'static> View for BrowserPage<'a> { None } - fn update(&mut self, state: &Self::State, props: Option) { - if let Some(props) = props.and_then(|props| props.inner::()) { - self.props = props; - } else { - self.props = BrowserPageProps::from(state); - } - + fn update(&mut self, _props: Option<&ViewProps>, state: &Self::State) { self.sections.update(state); self.shortcuts.update(state); } - fn render(&self, frame: &mut ratatui::Frame, props: RenderProps) { - let page_size = props.area.height.saturating_sub(6) as usize; + fn render(&self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) { + let default = BrowserPageProps::default(); + let props = props + .and_then(|props| props.inner_ref::()) + .unwrap_or(&default); + + let page_size = render.area.height.saturating_sub(6) as usize; let [content_area, shortcuts_area] = - Layout::vertical([Constraint::Min(1), Constraint::Length(1)]).areas(props.area); + Layout::vertical([Constraint::Min(1), Constraint::Length(1)]).areas(render.area); self.sections.render( - frame, RenderProps::from(content_area) .layout(Layout::horizontal([Constraint::Min(1)])) .focus(true), + frame, ); self.shortcuts - .render(frame, RenderProps::from(shortcuts_area)); + .render(RenderProps::from(shortcuts_area), frame); // TODO: Find better solution - if page_size != self.props.page_size { + if page_size != props.page_size { self.sections.send(Message::BrowserPageSize(page_size)); } } @@ -399,8 +401,6 @@ impl<'a: 'static> View for BrowserPage<'a> { pub struct SearchProps {} pub struct Search { - /// Internal props - props: SearchProps, /// Search input field input: Widget, } @@ -411,7 +411,6 @@ impl Search { Self: Sized, { Self { - props: SearchProps {}, input: TextField::default() .to_widget(tx.clone()) .on_event(|s, _| { @@ -435,7 +434,7 @@ impl View for Search { type Message = Message; type State = State; - fn handle_event(&mut self, key: termion::event::Key) -> Option { + fn handle_event(&mut self, _props: Option<&ViewProps>, key: Key) -> Option { match key { Key::Esc => Some(Message::CloseSearch), Key::Char('\n') => Some(Message::ApplySearch), @@ -446,20 +445,16 @@ impl View for Search { } } - fn update(&mut self, state: &Self::State, props: Option) { - if let Some(props) = props.and_then(|props| props.inner::()) { - self.props = props; - } - + fn update(&mut self, _props: Option<&ViewProps>, state: &Self::State) { self.input.update(state); } - fn render(&self, frame: &mut ratatui::Frame, props: RenderProps) { + fn render(&self, _props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) { let layout = Layout::horizontal(Constraint::from_mins([0])) .horizontal_margin(1) - .split(props.area); + .split(render.area); - self.input.render(frame, RenderProps::from(layout[0])); + self.input.render(RenderProps::from(layout[0]), frame); } } @@ -483,22 +478,19 @@ impl<'a> From<&State> for HelpPageProps<'a> { } } -pub struct HelpPage<'a> { - /// Internal props - props: HelpPageProps<'a>, +pub struct HelpPage { /// Content widget content: Widget, /// Shortcut widget shortcuts: Widget, } -impl<'a: 'static> HelpPage<'a> { +impl HelpPage { pub fn new(tx: UnboundedSender) -> Self where Self: Sized, { Self { - props: HelpPageProps::default(), content: Container::default() .header(Header::default().to_widget(tx.clone()).on_update(|_| { HeaderProps::default() @@ -509,7 +501,7 @@ impl<'a: 'static> HelpPage<'a> { .content( Paragraph::default() .to_widget(tx.clone()) - .on_event(|s, _| { + .on_event(|_, s, _| { Some(Message::ScrollHelp { progress: s.and_then(|p| p.unwrap_usize()).unwrap_or_default(), }) @@ -554,11 +546,11 @@ impl<'a: 'static> HelpPage<'a> { } } -impl<'a: 'static> View for HelpPage<'a> { +impl View for HelpPage { type Message = Message; type State = State; - fn handle_event(&mut self, key: termion::event::Key) -> Option { + fn handle_event(&mut self, _props: Option<&ViewProps>, key: Key) -> Option { match key { Key::Esc | Key::Ctrl('c') => Some(Message::Exit { selection: None }), Key::Char('?') => Some(Message::LeavePage), @@ -569,30 +561,29 @@ impl<'a: 'static> View for HelpPage<'a> { } } - fn update(&mut self, state: &Self::State, props: Option) { - if let Some(props) = props.and_then(|props| props.inner::()) { - self.props = props; - } else { - self.props = HelpPageProps::from(state); - } - + fn update(&mut self, _props: Option<&ViewProps>, state: &Self::State) { self.content.update(state); self.shortcuts.update(state); } - fn render(&self, frame: &mut ratatui::Frame, props: RenderProps) { - let page_size = props.area.height.saturating_sub(6) as usize; + fn render(&self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) { + let default = HelpPageProps::default(); + let props = props + .and_then(|props| props.inner_ref::()) + .unwrap_or(&default); + + let page_size = render.area.height.saturating_sub(6) as usize; let [content_area, shortcuts_area] = - Layout::vertical([Constraint::Min(1), Constraint::Length(1)]).areas(props.area); + Layout::vertical([Constraint::Min(1), Constraint::Length(1)]).areas(render.area); self.content - .render(frame, RenderProps::from(content_area).focus(true)); + .render(RenderProps::from(content_area).focus(true), frame); self.shortcuts - .render(frame, RenderProps::from(shortcuts_area)); + .render(RenderProps::from(content_area).focus(true), frame); // TODO: Find better solution - if page_size != self.props.page_size { + if page_size != props.page_size { self.content.send(Message::HelpPageSize(page_size)); } }