use std::cmp::min;
use std::ops::Deref as _;
use std::rc::Rc;
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 crate::tags::Tag;
use crate::tasks::Task;
use crate::tasks::Tasks;
use crate::text::EditableText;
use crate::view::View;
use super::event::Event;
use super::event::Key;
use super::in_out::InOut;
use super::input::InputText;
use super::message::Message;
use super::message::MessageExt;
use super::selectable::Selectable;
use super::tab_bar::SearchState;
use super::tab_bar::TabState;
#[derive(Debug)]
enum State {
Add,
Edit { task: Rc<Task>, edited: Task },
}
pub struct TaskListBoxData {
tasks: Rc<Tasks>,
view: View,
toggle_tag: Option<Tag>,
selection: isize,
state: Option<State>,
}
impl TaskListBoxData {
pub fn new(tasks: Rc<Tasks>, view: View, toggle_tag: Option<Tag>) -> Self {
Self {
tasks,
view,
toggle_tag,
selection: 0,
state: None,
}
}
fn selected_task(&self) -> Option<Rc<Task>> {
let selection = self.selection(0);
self.view.iter(|mut iter| iter.nth(selection).cloned())
}
}
impl Selectable for TaskListBoxData {
fn selection_index(&self) -> isize {
self.selection
}
fn set_selection_index(&mut self, selection: isize) {
self.selection = selection
}
fn count(&self) -> usize {
self.view.iter(|iter| iter.count())
}
}
#[derive(Debug, Widget)]
#[gui(Event = Event, Message = Message)]
pub struct TaskListBox {
id: Id,
tab_bar: Id,
detail_dialog: Id,
tag_dialog: Id,
in_out: Id,
}
impl TaskListBox {
pub fn new(
id: Id,
cap: &mut dyn MutCap<Event, Message>,
tab_bar: Id,
detail_dialog: Id,
tag_dialog: Id,
in_out: Id,
selected: Option<usize>,
) -> Self {
let task_list_box = Self {
id,
tab_bar,
detail_dialog,
tag_dialog,
in_out,
};
let data = task_list_box.data_mut::<TaskListBoxData>(cap);
let selected = selected.map(|x| min(x, isize::MAX as usize)).unwrap_or(0) as isize;
data.selection = selected;
task_list_box
}
async fn handle_select_task(
&self,
cap: &mut dyn MutCap<Event, Message>,
task: Rc<Task>,
done: Option<&mut bool>,
) -> Option<Message> {
let data = self.data_mut::<TaskListBoxData>(cap);
let idx = data
.view
.iter(|mut iter| iter.position(|_task| Rc::ptr_eq(_task, &task)));
if let Some(idx) = idx {
if let Some(done) = done {
*done = true
}
data.select(idx as isize).then(|| Message::updated(self.id))
} else {
if done.is_none() {
let message = Message::SelectTask(task, false);
cap.send(self.tab_bar, message).await
} else {
None
}
}
}
async fn select_task(
&self,
cap: &mut dyn MutCap<Event, Message>,
task: Rc<Task>,
) -> Option<Message> {
self.handle_select_task(cap, task, None).await
}
fn search_task_index(
&self,
cap: &dyn Cap,
string: &str,
search_state: &SearchState,
reverse: bool,
exact: bool,
) -> Option<usize> {
let data = self.data::<TaskListBoxData>(cap);
let count = data.count();
let start_idx = match search_state {
SearchState::Current | SearchState::AfterCurrent => {
let offset = matches!(search_state, SearchState::AfterCurrent) as isize;
if reverse {
count.checked_sub(1)?.checked_sub(data.selection(-offset))?
} else {
data.selection(offset)
}
},
SearchState::First => 0,
SearchState::Done => unreachable!(),
};
let cmp_exact = |task: &Rc<Task>| task.summary() == string;
let cmp_vague = |task: &Rc<Task>| task.summary().to_lowercase().contains(string);
let check = if exact {
&cmp_exact as &dyn Fn(&Rc<Task>) -> bool
} else {
&cmp_vague as &dyn Fn(&Rc<Task>) -> bool
};
if reverse {
data.view.iter(|iter| {
iter
.rev()
.skip(start_idx)
.position(check)
.map(|idx| count.saturating_sub(start_idx + idx + 1))
})
} else {
data.view.iter(|iter| {
iter
.skip(start_idx)
.position(check)
.map(|idx| start_idx + idx)
})
}
}
async fn handle_search_task(
&self,
cap: &mut dyn MutCap<Event, Message>,
string: &str,
search_state: &mut SearchState,
reverse: bool,
exact: bool,
) -> Option<Message> {
let idx = self.search_task_index(cap, string, search_state, reverse, exact);
if let Some(idx) = idx {
*search_state = SearchState::Done;
let data = self.data_mut::<TaskListBoxData>(cap);
data.select(idx as isize).then(|| Message::updated(self.id))
} else {
None
}
}
pub fn view(&self, cap: &dyn Cap) -> View {
let data = self.data::<TaskListBoxData>(cap);
data.view.clone()
}
pub fn toggle_tag(&self, cap: &dyn Cap) -> Option<Tag> {
let data = self.data::<TaskListBoxData>(cap);
data.toggle_tag.clone()
}
pub fn selection(&self, cap: &dyn Cap) -> usize {
let data = self.data::<TaskListBoxData>(cap);
data.selection(0)
}
}
#[async_trait(?Send)]
impl Handleable<Event, Message> for TaskListBox {
async fn handle(&self, cap: &mut dyn MutCap<Event, Message>, event: Event) -> Option<Event> {
let data = self.data_mut::<TaskListBoxData>(cap);
match event {
Event::Key(key, _) => match key {
Key::Char(' ') => {
if let Some(task) = data.selected_task() {
if let Some(toggle_tag) = &data.toggle_tag {
let mut updated = task.deref().clone();
if !updated.unset_tag(toggle_tag) {
updated.set_tag(toggle_tag.clone());
}
cap
.send(self.id, Message::UpdateTask(task, updated))
.await
.into_event()
} else {
None
}
} else {
None
}
},
Key::Char('a') => {
data.state = Some(State::Add);
let message = Message::SetInOut(InOut::Input(InputText::default()));
cap.send(self.in_out, message).await.into_event()
},
Key::Char('d') => {
if let Some(task) = data.selected_task() {
let () = data.tasks.remove(task);
Some(Event::updated(self.id))
} else {
None
}
},
Key::Char('e') => {
if let Some(task) = data.selected_task() {
let edited = Task::clone(task.deref());
let string = edited.summary();
data.state = Some(State::Edit { task, edited });
let mut text = EditableText::from_string(string);
let () = text.move_end();
let message = Message::SetInOut(InOut::Input(InputText::new(text)));
cap.send(self.in_out, message).await.into_event()
} else {
None
}
},
Key::Char('t') => {
if let Some(task) = data.selected_task() {
let edited = Task::clone(task.deref());
let message = Message::EditTags(task, edited);
cap.send(self.tag_dialog, message).await.into_event()
} else {
None
}
},
Key::Char('y') => {
if let Some(task) = data.selected_task() {
let copied = Task::clone(task.deref());
let message = Message::CopyTask(copied);
cap.send(self.tab_bar, message).await.into_event()
} else {
None
}
},
Key::Char('p') => {
let mut message = Message::GetCopiedTask(None);
let result1 = cap.call(self.tab_bar, &mut message).await;
if let Message::GetCopiedTask(task) = message {
if let Some(task) = task {
let builder = Task::builder()
.set_summary(task.summary())
.set_tags(task.tags(|tags| tags.cloned().collect::<Vec<_>>()))
.set_details(task.details());
let data = self.data_mut::<TaskListBoxData>(cap);
let task = data.tasks.add(builder, data.selected_task());
let result2 = self.select_task(cap, task).await;
result1
.maybe_update(result2)
.maybe_update(Some(Message::updated(self.id)))
.into_event()
} else {
result1.into_event()
}
} else {
debug_assert!(false, "received unexpected return message: {message:?}");
result1.into_event()
}
},
Key::Char('\n') => {
if let Some(task) = data.selected_task() {
let edited = Task::clone(task.deref());
let message = Message::EditDetails(task, edited);
cap.send(self.detail_dialog, message).await.into_event()
} else {
None
}
},
Key::Char('J') => {
if let Some(to_move) = data.selected_task() {
let other = data
.view
.iter(|mut iter| iter.nth(data.selection(1)).cloned());
if let Some(other) = other {
let () = data.tasks.move_after(to_move, other);
data.change_selection(1).then(|| Event::updated(self.id))
} else {
None
}
} else {
None
}
},
Key::Char('K') => {
if let Some(to_move) = data.selected_task() {
if data.selection(0) > 0 {
let other = data
.view
.iter(|mut iter| iter.nth(data.selection(-1)).cloned());
if let Some(other) = other {
let () = data.tasks.move_before(to_move, other);
data.change_selection(-1).then(|| Event::updated(self.id))
} else {
None
}
} else {
None
}
} else {
None
}
},
Key::Char('g') => data.select(0).then(|| Event::updated(self.id)),
Key::Char('G') => data.select(isize::MAX).then(|| Event::updated(self.id)),
Key::Char('j') => data.change_selection(1).then(|| Event::updated(self.id)),
Key::Char('k') => data.change_selection(-1).then(|| Event::updated(self.id)),
Key::Char('*') => {
if let Some(selected) = data.selected_task() {
let message = Message::StartTaskSearch(selected.summary());
cap.send(self.tab_bar, message).await.into_event()
} else {
None
}
},
_ => Some(event),
},
_ => Some(event),
}
}
async fn react(&self, message: Message, cap: &mut dyn MutCap<Event, Message>) -> Option<Message> {
let data = self.data_mut::<TaskListBoxData>(cap);
match message {
Message::EnteredText(ref text) => {
if let Some(state) = data.state.take() {
match state {
State::Add => {
if !text.is_empty() {
let tags = if let Some(task) = data.selected_task() {
task.tags(|iter| {
iter
.filter(|tag| Some(*tag) != data.toggle_tag.as_ref())
.cloned()
.collect()
})
} else {
data.view.positive_tag_iter().cloned().collect()
};
let after = data.selected_task();
let builder = Task::builder().set_summary(text).set_tags(tags);
let task = data.tasks.add(builder, after);
self.select_task(cap, task).await
} else {
None
}
},
State::Edit { task, mut edited } => {
if !text.is_empty() {
edited.set_summary(text.clone());
data.tasks.update(Rc::clone(&task), edited);
self
.select_task(cap, task)
.await
.maybe_update(Some(Message::updated(self.id)))
} else {
data.tasks.remove(task);
Some(Message::updated(self.id))
}
},
}
} else {
cap.send(self.tab_bar, message).await
}
},
Message::UpdateTask(task, updated) => {
data.tasks.update(Rc::clone(&task), updated);
self
.select_task(cap, task)
.await
.maybe_update(Some(Message::updated(self.id)))
},
#[cfg(not(feature = "readline"))]
Message::InputCanceled => {
if data.state.take().is_some() {
Some(Message::updated(self.id))
} else {
None
}
},
#[cfg(feature = "readline")]
Message::InputCanceled => None,
message => panic!("Received unexpected message: {message:?}"),
}
}
async fn respond(
&self,
message: &mut Message,
cap: &mut dyn MutCap<Event, Message>,
) -> Option<Message> {
match message {
Message::SelectTask(task, done) => {
self
.handle_select_task(cap, Rc::clone(task), Some(done))
.await
},
Message::SearchTask(string, search_state, reverse, exact) => {
self
.handle_search_task(cap, string, search_state, *reverse, *exact)
.await
},
Message::GetTabState(ref mut tab_state) => {
let TabState { ref mut views, .. } = tab_state;
let data = self.data::<TaskListBoxData>(cap);
let selected = Some(data.selection(0));
views.push((self.view(cap), selected));
None
},
message => panic!("Received unexpected message: {message:?}"),
}
}
}