mirror of
https://github.com/ratatui-org/ratatui
synced 2024-11-24 21:53:21 +00:00
fix: Make StatefulWidget and Ref work with unsized State (#1505)
StatefulWidget::State and StatefulWidgetRef::State are now ?Sized. This allows implementations of the traits to use unsized types for the State associated type. This is turn is useful when doing things like boxing different stateful widget types with State which implements `Any`, are slices or any other dynamically sized type. Co-authored-by: Josh McKinney <joshka@users.noreply.github.com>
This commit is contained in:
parent
e4e95bcecf
commit
4d7704fba5
3 changed files with 147 additions and 3 deletions
|
@ -121,7 +121,7 @@ pub trait StatefulWidget {
|
|||
/// If you don't need this then you probably want to implement [`Widget`] instead.
|
||||
///
|
||||
/// [`Widget`]: super::Widget
|
||||
type State;
|
||||
type State: ?Sized;
|
||||
/// Draws the current state of the widget in the given buffer. That is the only method required
|
||||
/// to implement a custom stateful widget.
|
||||
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State);
|
||||
|
@ -159,4 +159,23 @@ mod tests {
|
|||
widget.render(buf.area, &mut buf, &mut state);
|
||||
assert_eq!(buf, Buffer::with_lines(["Hello world "]));
|
||||
}
|
||||
|
||||
struct Bytes;
|
||||
|
||||
/// A widget with an unsized state type.
|
||||
impl StatefulWidget for Bytes {
|
||||
type State = [u8];
|
||||
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
let slice = std::str::from_utf8(state).unwrap();
|
||||
Line::from(format!("Bytes: {slice}")).render(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn render_unsized_state_type(mut buf: Buffer) {
|
||||
let widget = Bytes;
|
||||
let state = b"hello";
|
||||
widget.render(buf.area, &mut buf, &mut state.clone());
|
||||
assert_eq!(buf, Buffer::with_lines(["Bytes: hello "]));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@ pub trait StatefulWidgetRef {
|
|||
/// If you don't need this then you probably want to implement [`WidgetRef`] instead.
|
||||
///
|
||||
/// [`WidgetRef`]: super::WidgetRef
|
||||
type State;
|
||||
type State: ?Sized;
|
||||
/// Draws the current state of the widget in the given buffer. That is the only method required
|
||||
/// to implement a custom stateful widget.
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State);
|
||||
|
@ -70,7 +70,7 @@ pub trait StatefulWidgetRef {
|
|||
/// Blanket implementation of `StatefulWidgetRef` for `&W` where `W` implements `StatefulWidget`.
|
||||
///
|
||||
/// This allows you to render a stateful widget by reference.
|
||||
impl<W, State> StatefulWidgetRef for &W
|
||||
impl<W, State: ?Sized> StatefulWidgetRef for &W
|
||||
where
|
||||
for<'a> &'a W: StatefulWidget<State = State>,
|
||||
{
|
||||
|
@ -119,4 +119,38 @@ mod tests {
|
|||
widget.render_ref(buf.area, &mut buf, &mut state);
|
||||
assert_eq!(buf, Buffer::with_lines(["Hello world "]));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn render_stateful_widget_ref_with_unsized_state(mut buf: Buffer) {
|
||||
struct Bytes;
|
||||
|
||||
impl StatefulWidgetRef for Bytes {
|
||||
type State = [u8];
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
let slice = std::str::from_utf8(state).unwrap();
|
||||
Line::from(format!("Bytes: {slice}")).render(area, buf);
|
||||
}
|
||||
}
|
||||
let widget = Bytes;
|
||||
let state = b"hello";
|
||||
widget.render_ref(buf.area, &mut buf, &mut state.clone());
|
||||
assert_eq!(buf, Buffer::with_lines(["Bytes: hello "]));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn render_stateful_widget_with_unsized_state(mut buf: Buffer) {
|
||||
struct Bytes;
|
||||
impl StatefulWidget for &Bytes {
|
||||
type State = [u8];
|
||||
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
let slice = std::str::from_utf8(state).unwrap();
|
||||
Line::from(format!("Bytes: {slice}")).render(area, buf);
|
||||
}
|
||||
}
|
||||
let widget = &Bytes;
|
||||
let mut state = b"hello".to_owned();
|
||||
let state = state.as_mut_slice();
|
||||
widget.render_ref(buf.area, &mut buf, state);
|
||||
assert_eq!(buf, Buffer::with_lines(["Bytes: hello "]));
|
||||
}
|
||||
}
|
||||
|
|
91
ratatui/tests/stateful_widget_ref_dyn.rs
Normal file
91
ratatui/tests/stateful_widget_ref_dyn.rs
Normal file
|
@ -0,0 +1,91 @@
|
|||
#![cfg(feature = "unstable-widget-ref")]
|
||||
|
||||
use std::{
|
||||
any::{type_name, Any},
|
||||
cell::RefCell,
|
||||
};
|
||||
|
||||
use pretty_assertions::assert_eq;
|
||||
use ratatui::widgets::StatefulWidgetRef;
|
||||
use ratatui_core::{buffer::Buffer, layout::Rect, text::Line, widgets::Widget};
|
||||
|
||||
trait AnyWindow: StatefulWidgetRef<State = dyn Any> {
|
||||
fn title(&self) -> &str {
|
||||
type_name::<Self>()
|
||||
}
|
||||
}
|
||||
|
||||
struct Window1;
|
||||
|
||||
struct Window1State {
|
||||
pub value: u32,
|
||||
}
|
||||
|
||||
impl AnyWindow for Window1 {}
|
||||
|
||||
impl StatefulWidgetRef for Window1 {
|
||||
type State = dyn Any;
|
||||
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
let state = state.downcast_mut::<Window1State>().expect("window1 state");
|
||||
Line::from(format!("{}, u32: {}", self.title(), state.value)).render(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
struct Window2;
|
||||
|
||||
struct Window2State {
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
impl AnyWindow for Window2 {}
|
||||
|
||||
impl StatefulWidgetRef for Window2 {
|
||||
type State = dyn Any;
|
||||
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
let state = state.downcast_mut::<Window2State>().expect("window2 state");
|
||||
Line::from(format!("{}, String: {}", self.title(), state.value)).render(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
type BoxedWindow = Box<dyn AnyWindow>;
|
||||
type BoxedState = Box<RefCell<dyn Any>>;
|
||||
|
||||
#[test]
|
||||
fn render_dyn_widgets() {
|
||||
let windows: Vec<(BoxedWindow, BoxedState)> = vec![
|
||||
(
|
||||
Box::new(Window1),
|
||||
Box::new(RefCell::new(Window1State { value: 32 })),
|
||||
),
|
||||
(
|
||||
Box::new(Window2),
|
||||
Box::new(RefCell::new(Window2State {
|
||||
value: "Some".to_string(),
|
||||
})),
|
||||
),
|
||||
(
|
||||
Box::new(Window1),
|
||||
Box::new(RefCell::new(Window1State { value: 42 })),
|
||||
),
|
||||
];
|
||||
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 50, 3));
|
||||
|
||||
let mut area = Rect::new(0, 0, 50, 1);
|
||||
for (w, s) in &windows {
|
||||
let mut s = s.borrow_mut();
|
||||
w.render_ref(area, &mut buf, &mut *s);
|
||||
area.y += 1;
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
buf,
|
||||
Buffer::with_lines([
|
||||
"stateful_widget_ref_dyn::Window1, u32: 32 ",
|
||||
"stateful_widget_ref_dyn::Window2, String: Some ",
|
||||
"stateful_widget_ref_dyn::Window1, u32: 42 ",
|
||||
])
|
||||
);
|
||||
}
|
Loading…
Reference in a new issue