use std::future::Future;
use std::ops::Deref as _;
use std::pin::Pin;
use async_trait::async_trait;
use gui::derive::Widget;
use gui::Cap;
use gui::Handleable;
use gui::Id;
use gui::MutCap;
use gui::Widget;
use super::event::Event;
use super::input::InputResult;
use super::input::InputText;
use super::message::Message;
use super::message::MessageExt;
use super::modal::Modal;
#[derive(Debug)]
pub enum InOut {
Saved,
Search(String),
Error(String),
Input(InputText),
Clear,
}
impl PartialEq for InOut {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(InOut::Saved, InOut::Saved) => true,
(InOut::Search(x), InOut::Search(y)) => x == y,
(InOut::Error(x), InOut::Error(y)) => x == y,
(InOut::Input(x), InOut::Input(y)) => x.deref() == y.deref(),
(InOut::Clear, InOut::Clear) => true,
_ => false,
}
}
}
impl Eq for InOut {}
#[derive(Debug)]
struct InOutState {
in_out: InOut,
r#gen: usize,
}
impl InOutState {
fn get(&self) -> &InOut {
&self.in_out
}
fn get_mut(&mut self) -> &mut InOut {
&mut self.in_out
}
fn set(&mut self, in_out: InOut) {
self.in_out = in_out;
self.bump()
}
fn bump(&mut self) {
self.r#gen += 1;
}
}
impl Default for InOutState {
fn default() -> Self {
Self {
in_out: InOut::Clear,
r#gen: 0,
}
}
}
pub struct InOutAreaData {
prev_focused: Option<Id>,
clear_gen: Option<usize>,
in_out: InOutState,
}
impl InOutAreaData {
pub fn new() -> Self {
Self {
prev_focused: None,
clear_gen: None,
in_out: Default::default(),
}
}
fn change_state(&mut self, parent: Id, in_out: Option<InOut>) -> Option<Message> {
self.in_out.bump();
match in_out {
Some(in_out) if in_out != *self.in_out.get() => {
self.in_out.set(in_out);
Some(Message::updated(parent))
},
Some(..) => None,
None => Some(Message::updated(parent)),
}
}
}
#[derive(Debug, Widget)]
#[gui(Event = Event, Message = Message)]
pub struct InOutArea {
id: Id,
}
impl InOutArea {
pub fn new(id: Id, cap: &mut dyn MutCap<Event, Message>) -> Self {
cap.hook_events(id, Some(&InOutArea::handle_hooked_event));
Self { id }
}
fn handle_hooked_event<'f>(
widget: &'f dyn Widget<Event, Message>,
cap: &'f mut dyn MutCap<Event, Message>,
event: Option<&'f Event>,
) -> Pin<Box<dyn Future<Output = Option<Event>> + 'f>> {
Box::pin(async move {
let parent = cap.parent_id(widget.id()).unwrap();
let data = cap
.data_mut(widget.id())
.downcast_mut::<InOutAreaData>()
.unwrap();
if let Some(event) = event {
match event {
Event::Key(..) => {
data.clear_gen = Some(data.in_out.r#gen);
None
},
Event::Updated(..) | Event::Quit => None,
}
} else {
if data.clear_gen.take() == Some(data.in_out.r#gen) {
match data.in_out.get() {
InOut::Saved | InOut::Search(_) | InOut::Error(_) => {
data.change_state(parent, Some(InOut::Clear)).into_event()
},
InOut::Input(..) | InOut::Clear => None,
}
} else {
None
}
}
})
}
async fn finish_input(
&self,
cap: &mut dyn MutCap<Event, Message>,
string: Option<String>,
) -> Option<Message> {
let parent = cap.parent_id(self.id).unwrap();
let data = self.data_mut::<InOutAreaData>(cap);
let result1 = data.change_state(parent, Some(InOut::Clear));
let widget = self.restore_focus(cap);
let message = if let Some(s) = string {
Message::EnteredText(s)
} else {
Message::InputCanceled
};
let result2 = cap.send(widget, message).await;
result1.maybe_update(result2)
}
pub fn state<'slf>(&'slf self, cap: &'slf dyn Cap) -> &'slf InOut {
let data = self.data::<InOutAreaData>(cap);
data.in_out.get()
}
}
impl Modal for InOutArea {
fn prev_focused(&self, cap: &dyn Cap) -> Option<Id> {
self.data::<InOutAreaData>(cap).prev_focused
}
fn set_prev_focused(&self, cap: &mut dyn MutCap<Event, Message>, focused: Option<Id>) {
let data = self.data_mut::<InOutAreaData>(cap);
data.prev_focused = focused;
}
}
#[async_trait(?Send)]
impl Handleable<Event, Message> for InOutArea {
async fn handle(&self, cap: &mut dyn MutCap<Event, Message>, event: Event) -> Option<Event> {
match event {
Event::Key(key, raw) => {
let parent = cap.parent_id(self.id).unwrap();
let data = self.data_mut::<InOutAreaData>(cap);
let text = if let InOut::Input(text) = data.in_out.get_mut() {
text
} else {
panic!("In/out area not used for input.");
};
let message = match text.handle_key(key, &raw) {
InputResult::Completed(text) => self.finish_input(cap, Some(text)).await,
InputResult::Canceled => self.finish_input(cap, None).await,
InputResult::Updated => data.change_state(parent, None),
InputResult::Unchanged => {
data.in_out.bump();
None
},
};
message.into_event()
},
_ => Some(event),
}
}
async fn react(&self, message: Message, cap: &mut dyn MutCap<Event, Message>) -> Option<Message> {
match message {
Message::SetInOut(in_out) => {
if matches!(in_out, InOut::Input(..)) {
self.make_focused(cap);
};
let parent = cap.parent_id(self.id).unwrap();
let data = self.data_mut::<InOutAreaData>(cap);
data.change_state(parent, Some(in_out))
},
#[cfg(all(test, not(feature = "readline")))]
Message::GetInOut => {
use crate::text::EditableText;
let data = self.data::<InOutAreaData>(cap);
let in_out = match data.in_out.get() {
InOut::Saved => InOut::Saved,
InOut::Search(x) => InOut::Search(x.clone()),
InOut::Error(x) => InOut::Error(x.clone()),
InOut::Input(x) => InOut::Input(InputText::new(EditableText::clone(x.deref()))),
InOut::Clear => InOut::Clear,
};
Some(Message::GotInOut(in_out))
},
message => panic!("Received unexpected message: {message:?}"),
}
}
}