Skip to content

Commit 27b2e01

Browse files
authored
Merge pull request #66 from ynqa/render-on-resize
fix: render on resize
2 parents be89d35 + a5dc0ae commit 27b2e01

File tree

19 files changed

+320
-255
lines changed

19 files changed

+320
-255
lines changed

promkit-core/src/render.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ impl<K: Ord + Send + 'static> Renderer<K> {
5656
self
5757
}
5858

59+
// TODO: Implement diff rendering
5960
pub async fn render(&self) -> anyhow::Result<()> {
6061
let panes: Vec<Pane> = self
6162
.panes

promkit/src/lib.rs

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,11 @@ use futures::StreamExt;
1414
use scopeguard::defer;
1515
use tokio::sync::Mutex;
1616

17-
use core::{
18-
crossterm::{
19-
cursor,
20-
event::{self, Event, EventStream},
21-
execute,
22-
terminal::{disable_raw_mode, enable_raw_mode},
23-
},
24-
render::SharedRenderer,
17+
use core::crossterm::{
18+
cursor,
19+
event::{self, Event, EventStream},
20+
execute,
21+
terminal::{disable_raw_mode, enable_raw_mode},
2522
};
2623

2724
/// Singleton for EventStream. If a new EventStream is created for each Prompt::run,
@@ -49,12 +46,6 @@ pub enum Signal {
4946
/// are evaluated, and how the final result is produced.
5047
#[async_trait::async_trait]
5148
pub trait Prompt {
52-
/// The type of index used to identify different components in the prompt.
53-
type Index: Ord + Send + Sync + 'static;
54-
55-
/// Returns a shared renderer for the prompt.
56-
fn renderer(&self) -> SharedRenderer<Self::Index>;
57-
5849
/// Initializes the handler, preparing it for use.
5950
/// This method is called before the prompt starts running.
6051
///

promkit/src/preset.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,14 @@ pub mod form;
3737
#[cfg(feature = "text")]
3838
#[cfg_attr(docsrs, doc(cfg(feature = "text")))]
3939
pub mod text;
40+
41+
use std::{future::Future, pin::Pin};
42+
43+
use crate::{core::crossterm::event::Event, Signal};
44+
45+
/// Type alias for the evaluator function used in the prompt.
46+
pub type Evaluator<T> =
47+
for<'a> fn(
48+
event: &'a Event,
49+
ctx: &'a mut T,
50+
) -> Pin<Box<dyn Future<Output = Result<Signal, anyhow::Error>> + Send + 'a>>;

promkit/src/preset/checkbox.rs

Lines changed: 31 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use crate::{
1212
render::{Renderer, SharedRenderer},
1313
PaneFactory,
1414
},
15+
preset::Evaluator,
1516
widgets::{
1617
checkbox,
1718
text::{self, Text},
@@ -28,16 +29,13 @@ pub enum Index {
2829
Checkbox = 1,
2930
}
3031

31-
/// Type alias for the evaluator function used in the checkbox preset.
32-
pub type Evaluator = fn(event: &Event, ctx: &mut Checkbox) -> anyhow::Result<Signal>;
33-
3432
/// Represents a checkbox component for creating
3533
/// and managing a list of selectable options.
3634
pub struct Checkbox {
3735
/// Shared renderer for the prompt, allowing for rendering of UI components.
3836
pub renderer: Option<SharedRenderer<Index>>,
3937
/// Function to evaluate the input events and update the state of the prompt.
40-
pub evaluator_fn: Evaluator,
38+
pub evaluator: Evaluator<Self>,
4139
/// State for the title displayed above the checkbox list.
4240
pub title: text::State,
4341
/// State for the checkbox list itself.
@@ -46,12 +44,6 @@ pub struct Checkbox {
4644

4745
#[async_trait::async_trait]
4846
impl crate::Prompt for Checkbox {
49-
type Index = Index;
50-
51-
fn renderer(&self) -> SharedRenderer<Self::Index> {
52-
self.renderer.clone().unwrap()
53-
}
54-
5547
async fn initialize(&mut self) -> anyhow::Result<()> {
5648
let size = crossterm::terminal::size()?;
5749
self.renderer = Some(SharedRenderer::new(
@@ -68,17 +60,9 @@ impl crate::Prompt for Checkbox {
6860
}
6961

7062
async fn evaluate(&mut self, event: &Event) -> anyhow::Result<Signal> {
71-
let ret = (self.evaluator_fn)(event, self);
63+
let ret = (self.evaluator)(event, self).await;
7264
let size = crossterm::terminal::size()?;
73-
self.renderer
74-
.as_ref()
75-
.unwrap()
76-
.update([
77-
(Index::Title, self.title.create_pane(size.0, size.1)),
78-
(Index::Checkbox, self.checkbox.create_pane(size.0, size.1)),
79-
])
80-
.render()
81-
.await?;
65+
self.render(size.0, size.1).await?;
8266
ret
8367
}
8468

@@ -96,11 +80,10 @@ impl crate::Prompt for Checkbox {
9680
}
9781

9882
impl Checkbox {
99-
/// Creates a new `Checkbox` instance with the provided items.
100-
pub fn new<T: Display, I: IntoIterator<Item = T>>(items: I) -> Self {
83+
fn new_with_checkbox(checkbox: checkbox::Checkbox) -> Self {
10184
Self {
10285
renderer: None,
103-
evaluator_fn: evaluate::default,
86+
evaluator: |event, ctx| Box::pin(evaluate::default(event, ctx)),
10487
title: text::State {
10588
style: ContentStyle {
10689
attributes: Attributes::from(Attribute::Bold),
@@ -109,7 +92,7 @@ impl Checkbox {
10992
..Default::default()
11093
},
11194
checkbox: checkbox::State {
112-
checkbox: checkbox::Checkbox::from_displayable(items),
95+
checkbox,
11396
cursor: String::from("❯ "),
11497
active_mark: '☒',
11598
inactive_mark: '☐',
@@ -123,31 +106,14 @@ impl Checkbox {
123106
}
124107
}
125108

109+
/// Creates a new `Checkbox` instance with the provided items.
110+
pub fn new<T: Display, I: IntoIterator<Item = T>>(items: I) -> Self {
111+
Self::new_with_checkbox(checkbox::Checkbox::from_displayable(items))
112+
}
113+
126114
/// Creates a new `Checkbox` instance with the provided items and their checked states.
127115
pub fn new_with_checked<T: Display, I: IntoIterator<Item = (T, bool)>>(items: I) -> Self {
128-
Self {
129-
renderer: None,
130-
evaluator_fn: evaluate::default,
131-
title: text::State {
132-
style: ContentStyle {
133-
attributes: Attributes::from(Attribute::Bold),
134-
..Default::default()
135-
},
136-
..Default::default()
137-
},
138-
checkbox: checkbox::State {
139-
checkbox: checkbox::Checkbox::new_with_checked(items),
140-
cursor: String::from("❯ "),
141-
active_mark: '☒',
142-
inactive_mark: '☐',
143-
active_item_style: ContentStyle {
144-
foreground_color: Some(Color::DarkCyan),
145-
..Default::default()
146-
},
147-
inactive_item_style: ContentStyle::default(),
148-
lines: Default::default(),
149-
},
150-
}
116+
Self::new_with_checkbox(checkbox::Checkbox::new_with_checked(items))
151117
}
152118

153119
/// Sets the title text displayed above the checkbox list.
@@ -193,8 +159,24 @@ impl Checkbox {
193159
}
194160

195161
/// Sets the evaluator function for handling input events.
196-
pub fn evaluator(mut self, evaluator: Evaluator) -> Self {
197-
self.evaluator_fn = evaluator;
162+
pub fn evaluator(mut self, evaluator: Evaluator<Self>) -> Self {
163+
self.evaluator = evaluator;
198164
self
199165
}
166+
167+
/// Render the prompt with the specified width and height.
168+
async fn render(&mut self, width: u16, height: u16) -> anyhow::Result<()> {
169+
match self.renderer.as_ref() {
170+
Some(renderer) => {
171+
renderer
172+
.update([
173+
(Index::Title, self.title.create_pane(width, height)),
174+
(Index::Checkbox, self.checkbox.create_pane(width, height)),
175+
])
176+
.render()
177+
.await
178+
}
179+
None => Err(anyhow::anyhow!("Renderer not initialized")),
180+
}
181+
}
200182
}

promkit/src/preset/checkbox/evaluate.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,14 @@ use crate::{
1616
/// | <kbd>↑</kbd> | Move the selection up
1717
/// | <kbd>↓</kbd> | Move the selection down
1818
/// | <kbd>Space</kbd> | Toggle the checkbox state for the current item
19-
pub fn default(event: &Event, ctx: &mut Checkbox) -> anyhow::Result<Signal> {
19+
pub async fn default(event: &Event, ctx: &mut Checkbox) -> anyhow::Result<Signal> {
2020
match event {
21+
// Render for refreshing prompt on resize.
22+
Event::Resize(width, height) => {
23+
ctx.render(*width, *height).await?;
24+
}
25+
26+
// Quit
2127
Event::Key(KeyEvent {
2228
code: KeyCode::Enter,
2329
modifiers: KeyModifiers::NONE,

promkit/src/preset/form.rs

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,13 @@ use crate::{
1010
render::{Renderer, SharedRenderer},
1111
PaneFactory,
1212
},
13+
preset::Evaluator,
1314
widgets::{cursor::Cursor, text_editor},
1415
Signal,
1516
};
1617

1718
mod evaluate;
1819

19-
/// Represents the indices of various components in the form preset.
20-
pub type Evaluator = fn(event: &Event, ctx: &mut Form) -> anyhow::Result<Signal>;
21-
2220
/// Represents the visual styles for different states of text editor components.
2321
pub struct Style {
2422
/// Style for the prefix of the text editor.
@@ -34,7 +32,7 @@ pub struct Form {
3432
/// Shared renderer for the prompt, allowing for rendering of UI components.
3533
pub renderer: Option<SharedRenderer<usize>>,
3634
/// Function to evaluate the input events and update the state of the prompt.
37-
pub evaluator_fn: Evaluator,
35+
pub evaluator: Evaluator<Self>,
3836
/// State for the multiple text editor components.
3937
pub readlines: Cursor<Vec<text_editor::State>>,
4038
/// Default styles applied to text editors.
@@ -45,12 +43,6 @@ pub struct Form {
4543

4644
#[async_trait::async_trait]
4745
impl crate::Prompt for Form {
48-
type Index = usize;
49-
50-
fn renderer(&self) -> SharedRenderer<Self::Index> {
51-
self.renderer.clone().unwrap()
52-
}
53-
5446
async fn initialize(&mut self) -> anyhow::Result<()> {
5547
// Update styles based on the current position.
5648
self.overwrite_styles();
@@ -71,24 +63,13 @@ impl crate::Prompt for Form {
7163
}
7264

7365
async fn evaluate(&mut self, event: &Event) -> anyhow::Result<Signal> {
74-
let ret = (self.evaluator_fn)(event, self);
66+
let ret = (self.evaluator)(event, self).await;
7567

7668
// Update the styles based on the current position.
7769
self.overwrite_styles();
7870

7971
let size = crossterm::terminal::size()?;
80-
self.renderer
81-
.as_ref()
82-
.unwrap()
83-
.update(
84-
self.readlines
85-
.contents()
86-
.iter()
87-
.enumerate()
88-
.map(|(i, state)| (i, state.create_pane(size.0, size.1))),
89-
)
90-
.render()
91-
.await?;
72+
self.render(size.0, size.1).await?;
9273
ret
9374
}
9475

@@ -142,13 +123,32 @@ impl Form {
142123

143124
Self {
144125
renderer: None,
145-
evaluator_fn: evaluate::default,
126+
evaluator: |event, ctx| Box::pin(evaluate::default(event, ctx)),
146127
readlines: Cursor::new(readlines, 0, false),
147128
focus_styles,
148129
unfocus_styles,
149130
}
150131
}
151132

133+
/// Render the prompt with the specified width and height.
134+
async fn render(&mut self, width: u16, height: u16) -> anyhow::Result<()> {
135+
match self.renderer.as_ref() {
136+
Some(renderer) => {
137+
renderer
138+
.update(
139+
self.readlines
140+
.contents()
141+
.iter()
142+
.enumerate()
143+
.map(|(i, state)| (i, state.create_pane(width, height))),
144+
)
145+
.render()
146+
.await
147+
}
148+
None => Err(anyhow::anyhow!("Renderer not initialized")),
149+
}
150+
}
151+
152152
/// Updates the styles of text editor states based on their active or inactive status.
153153
fn overwrite_styles(&mut self) {
154154
let current_position = self.readlines.position();

promkit/src/preset/form/evaluate.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,17 @@ use crate::{
66
Signal,
77
};
88

9-
pub fn default(event: &Event, ctx: &mut Form) -> anyhow::Result<Signal> {
9+
/// Default event handler for the `Form` prompt.
10+
pub async fn default(event: &Event, ctx: &mut Form) -> anyhow::Result<Signal> {
1011
let current_position = ctx.readlines.position();
1112

1213
match event {
14+
// Render for refreshing prompt on resize.
15+
Event::Resize(width, height) => {
16+
ctx.render(*width, *height).await?;
17+
}
18+
19+
// Quit
1320
Event::Key(KeyEvent {
1421
code: KeyCode::Enter,
1522
modifiers: KeyModifiers::NONE,

0 commit comments

Comments
 (0)