Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace Option<Component<T>> with Component<Option<T>> #382

Merged
merged 1 commit into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion crates/tui/src/view/component/internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,17 +215,45 @@ impl<T> Component<T> {
) where
T: Draw<Props>,
{
self.draw_inner(frame, props, area, has_focus, &self.inner);
}

fn draw_inner<D: Draw<Props>, Props>(
&self,
frame: &mut Frame,
props: Props,
area: Rect,
has_focus: bool,
inner: &D,
) {
let guard = DrawGuard::new(self.id);

// Update internal state for event handling
let metadata = DrawMetadata::new_dangerous(area, has_focus);
self.metadata.set(metadata);

self.inner.draw(frame, props, metadata);
inner.draw(frame, props, metadata);
drop(guard); // Make sure guard stays alive until here
}
}

impl<T> Component<Option<T>> {
/// For components with optional data, draw the contents if present
pub fn draw_opt<Props>(
&self,
frame: &mut Frame,
props: Props,
area: Rect,
has_focus: bool,
) where
T: Draw<Props>,
{
if let Some(inner) = &self.inner {
self.draw_inner(frame, props, area, has_focus, inner);
}
}
}

// Derive impl doesn't work because the constructor gets the correct name
impl<T: Default> Default for Component<T> {
fn default() -> Self {
Expand Down
27 changes: 13 additions & 14 deletions crates/tui/src/view/component/recipe_pane.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ use strum::{EnumCount, EnumIter};
pub struct RecipePane {
/// All UI state derived from the recipe is stored together, and reset when
/// the recipe or profile changes
recipe_state: StateCell<RecipeStateKey, Option<Component<RecipeDisplay>>>,
recipe_state: StateCell<RecipeStateKey, Component<Option<RecipeDisplay>>>,
}

#[derive(Clone)]
Expand All @@ -56,7 +56,7 @@ impl RecipePane {
let recipe_id = state_key.recipe_id.clone()?;
let profile_id = state_key.selected_profile_id.clone();
let recipe_state = self.recipe_state.get()?;
let options = recipe_state.as_ref()?.data().build_options();
let options = recipe_state.data().as_ref()?.build_options();
Some(RequestConfig {
recipe_id,
profile_id,
Expand All @@ -80,8 +80,8 @@ impl EventHandler for RecipePane {
RecipeMenuAction::disabled_actions(
state.is_some(),
state
.and_then(Option::as_mut)
.is_some_and(|state| state.data().has_body()),
.and_then(|state| state.data().as_ref())
.is_some_and(|state| state.has_body()),
),
))
}
Expand All @@ -96,7 +96,7 @@ impl EventHandler for RecipePane {
fn children(&mut self) -> Vec<Component<Child<'_>>> {
self.recipe_state
.get_mut()
.and_then(|state| Some(state.as_mut()?.to_child_mut()))
.map(|state| state.to_child_mut())
.into_iter()
.collect()
}
Expand Down Expand Up @@ -138,11 +138,14 @@ impl<'a> Draw<RecipePaneProps<'a>> for RecipePane {
.map(RecipeNode::id)
.cloned(),
},
|| match props.selected_recipe_node {
Some(RecipeNode::Recipe(recipe)) => {
Some(RecipeDisplay::new(recipe).into())
|| {
match props.selected_recipe_node {
Some(RecipeNode::Recipe(recipe)) => {
Some(RecipeDisplay::new(recipe))
}
Some(RecipeNode::Folder(_)) | None => None,
}
Some(RecipeNode::Folder(_)) | None => None,
.into()
},
);

Expand All @@ -158,11 +161,7 @@ impl<'a> Draw<RecipePaneProps<'a>> for RecipePane {
frame.render_widget(folder.generate(), inner_area);
}
Some(RecipeNode::Recipe(_)) => {
// Unwrap is safe because we just initialized state above
recipe_state
.as_ref()
.unwrap()
.draw(frame, (), inner_area, true)
recipe_state.draw_opt(frame, (), inner_area, true)
}
};
}
Expand Down
59 changes: 28 additions & 31 deletions crates/tui/src/view/component/recipe_pane/recipe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ pub struct RecipeDisplay {
method: Method,
query: Component<RecipeFieldTable<QueryRowKey, QueryRowToggleKey>>,
headers: Component<RecipeFieldTable<HeaderRowKey, HeaderRowToggleKey>>,
body: Option<Component<RecipeBodyDisplay>>,
authentication: Option<Component<AuthenticationDisplay>>,
body: Component<Option<RecipeBodyDisplay>>,
authentication: Component<Option<AuthenticationDisplay>>,
}

impl RecipeDisplay {
Expand Down Expand Up @@ -85,32 +85,37 @@ impl RecipeDisplay {
),
)
.into(),
body: recipe.body.as_ref().map(|body| {
RecipeBodyDisplay::new(body, recipe.id.clone()).into()
}),
body: recipe
.body
.as_ref()
.map(|body| RecipeBodyDisplay::new(body, recipe.id.clone()))
.into(),
// Map authentication type
authentication: recipe.authentication.as_ref().map(
|authentication| {
authentication: recipe
.authentication
.as_ref()
.map(|authentication| {
AuthenticationDisplay::new(
recipe.id.clone(),
authentication.clone(),
)
.into()
},
),
})
.into(),
}
}

/// Generate a [BuildOptions] instance based on current UI state
pub fn build_options(&self) -> BuildOptions {
let authentication = self
.authentication
.data()
.as_ref()
.and_then(|authentication| authentication.data().override_value());
.and_then(|authentication| authentication.override_value());
let form_fields = self
.body
.data()
.as_ref()
.and_then(|body| match body.data() {
.and_then(|body| match body {
RecipeBodyDisplay::Raw(_) => None,
RecipeBodyDisplay::Form(form) => {
Some(form.data().to_build_overrides())
Expand All @@ -119,8 +124,9 @@ impl RecipeDisplay {
.unwrap_or_default();
let body = self
.body
.data()
.as_ref()
.and_then(|body| body.data().override_value());
.and_then(|body| body.override_value());

BuildOptions {
authentication,
Expand All @@ -133,22 +139,19 @@ impl RecipeDisplay {

/// Does the recipe have a body defined?
pub fn has_body(&self) -> bool {
self.body.is_some()
self.body.data().is_some()
}
}

impl EventHandler for RecipeDisplay {
fn children(&mut self) -> Vec<Component<Child<'_>>> {
[
Some(self.tabs.to_child_mut()),
self.body.as_mut().map(Component::to_child_mut),
Some(self.query.to_child_mut()),
Some(self.headers.to_child_mut()),
self.authentication.as_mut().map(Component::to_child_mut),
vec![
self.tabs.to_child_mut(),
self.body.to_child_mut(),
self.query.to_child_mut(),
self.headers.to_child_mut(),
self.authentication.to_child_mut(),
]
.into_iter()
.flatten()
.collect()
}
}

Expand Down Expand Up @@ -196,11 +199,7 @@ impl Draw for RecipeDisplay {

// Recipe content
match self.tabs.data().selected() {
Tab::Body => {
if let Some(body) = &self.body {
body.draw(frame, (), content_area, true);
}
}
Tab::Body => self.body.draw_opt(frame, (), content_area, true),
Tab::Query => self.query.draw(
frame,
RecipeFieldTableProps {
Expand All @@ -220,9 +219,7 @@ impl Draw for RecipeDisplay {
true,
),
Tab::Authentication => {
if let Some(authentication) = &self.authentication {
authentication.draw(frame, (), content_area, true)
}
self.authentication.draw_opt(frame, (), content_area, true)
}
}
}
Expand Down
11 changes: 5 additions & 6 deletions crates/tui/src/view/component/root.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ pub struct Root {
// ==== Children =====
primary_view: Component<PrimaryView>,
modal_queue: Component<ModalQueue>,
notification_text: Option<Component<NotificationText>>,
notification_text: Component<Option<NotificationText>>,
}

impl Root {
Expand All @@ -54,7 +54,7 @@ impl Root {
// Children
primary_view: primary_view.into(),
modal_queue: Component::default(),
notification_text: None,
notification_text: Component::default(),
}
}

Expand Down Expand Up @@ -138,7 +138,7 @@ impl EventHandler for Root {

Event::Notify(notification) => {
self.notification_text =
Some(NotificationText::new(notification).into())
Some(NotificationText::new(notification)).into()
}

Event::Input {
Expand Down Expand Up @@ -218,9 +218,8 @@ impl<'a> Draw<RootProps<'a>> for Root {
Constraint::Length(footer.width() as u16),
])
.areas(footer_area);
if let Some(notification_text) = &self.notification_text {
notification_text.draw(frame, (), notification_area, false);
}
self.notification_text
.draw_opt(frame, (), notification_area, false);
frame.render_widget(footer, help_area);

// Render modals last so they go on top
Expand Down
11 changes: 11 additions & 0 deletions crates/tui/src/view/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,17 @@ pub trait EventHandler {
}
}

/// Enable `Component<Option<T>>` with an empty event handler
impl<T: EventHandler> EventHandler for Option<T> {
fn update(&mut self, _: &mut UpdateContext, event: Event) -> Update {
Update::Propagate(event)
}

fn children(&mut self) -> Vec<Component<Child<'_>>> {
Vec::new()
}
}

// We can't do a blanket impl of EventHandler based on DerefMut because of the
// PersistedLazy's custom ToChild impl, which interferes with the blanket
// ToChild impl
Expand Down
Loading