combobox initial implementation

This commit is contained in:
Martin Vrhovšek 2025-02-05 00:02:28 +01:00
parent 33207c2620
commit 68411dc657

View File

@ -1,35 +1,41 @@
use iced::{event, keyboard, widget, Center, Element, Event, Length, Subscription, Task, Theme};
use iced::keyboard::key;
use iced::widget::{button, center, checkbox, column, row, scrollable, text_input, Space, Text};
use iced::widget::text::danger;
use rusqlite::Connection;
use crate::db; use crate::db;
use iced::keyboard::key;
use iced::widget::text::danger;
use iced::widget::{
button, center, checkbox, column, combo_box, row, scrollable, text_input, Text,
};
use iced::{event, keyboard, widget, Center, Element, Event, Length, Subscription, Task, Theme};
use rusqlite::Connection;
#[derive(Debug)] #[derive(Debug)]
pub struct TaskColumn { pub struct TaskColumn {
pub(crate) id: usize, pub(crate) id: usize,
pub(crate) checked: bool, pub(crate) checked: bool,
pub(crate) value: String, pub(crate) value: String,
} }
pub struct TaskData { pub struct TaskData {
db_id: usize, db_id: usize,
checked: bool, checked: bool,
value: String, value: String,
edit: bool, edit: bool,
can_update: bool, can_update: bool,
can_delete: bool, can_delete: bool,
already_exists: bool, already_exists: bool,
} }
pub(crate) struct Todo { pub(crate) struct Todo {
new_task: String, new_task: String,
updated_task: String, updated_task: String,
tasks: Vec<TaskData>, tasks: Vec<TaskData>,
completed_tasks: usize, completed_tasks: usize,
local_storage: bool, local_storage: bool,
conn: Connection, conn: Connection,
select_all: bool, select_all: bool,
task_list_input: String,
task_list_state: combo_box::State<String>,
task_list: Vec<String>,
current_task_group: Option<String>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -39,11 +45,13 @@ pub enum Message {
EditTask(usize), EditTask(usize),
DeleteTask(usize), DeleteTask(usize),
DeleteAll, DeleteAll,
ContentUpdated(bool, String), ContentUpdated(ContentType, String),
Event(Event), Event(Event),
TaskPush(Option<usize>), TaskPush(Option<usize>),
StorageToggle(bool), StorageToggle(bool),
ToggleUnselect(bool), ToggleUnselect(bool),
SelectedTaskList(String),
AddTaskList,
} }
pub enum ChangeType { pub enum ChangeType {
@ -51,6 +59,13 @@ pub enum ChangeType {
Value, Value,
} }
#[derive(Clone, Debug)]
enum ContentType {
NewTask,
ExistingTask,
NewList,
}
impl Default for Todo { impl Default for Todo {
fn default() -> Self { fn default() -> Self {
Todo::new() Todo::new()
@ -65,21 +80,37 @@ impl Todo {
tasks: Vec::new(), tasks: Vec::new(),
completed_tasks: 0, completed_tasks: 0,
local_storage: true, // todo eventually get this info from db - if no db set it to true local_storage: true, // todo eventually get this info from db - if no db set it to true
conn: db::startup("./todolist-db.db3".as_ref()).expect("[ERROR] Failed to access the local storage"), conn: db::startup("./todolist-db.db3".as_ref())
.expect("[ERROR] Failed to access the local storage"),
select_all: false, select_all: false,
task_list_input: String::from(""),
task_list_state: Default::default(),
task_list: vec![String::from("general")],
current_task_group: Some(String::from("")),
}; };
Self::load_data_from_db(&mut init); Self::load_data_from_db(&mut init);
init.task_list_state = combo_box::State::new(init.task_list.clone());
init.current_task_group = Some(init.task_list[0].clone());
init init
} }
pub(crate) fn update(&mut self, message: Message) -> Task<Message> { pub(crate) fn update(&mut self, message: Message) -> Task<Message> {
match message { match message {
Message::ContentUpdated(new, value) => { Message::ContentUpdated(new, value) => {
if new { /*if new {
self.new_task = String::from(&value); self.new_task = String::from(&value);
} else { } else {
self.updated_task = String::from(&value) self.updated_task = String::from(&value)
}*/
match new {
ContentType::NewTask => self.new_task = String::from(&value),
ContentType::ExistingTask => self.updated_task = String::from(&value),
ContentType::NewList => {
self.task_list_input = String::from(&value);
return Task::none();
}
} }
for task in &mut self.tasks { for task in &mut self.tasks {
@ -89,14 +120,21 @@ impl Todo {
Task::none() Task::none()
} }
Message::AddTask => { Message::AddTask => {
if self.new_task.is_empty() { return Task::none(); } if self.new_task.is_empty() {
if self.duplicate_exists(None) { return Task::none(); } return Task::none();
}
if self.duplicate_exists(None) {
return Task::none();
}
let result = db::insert_task(&self.conn, TaskColumn { let result = db::insert_task(
id: 0, &self.conn,
checked: false, TaskColumn {
value: self.new_task.to_string(), id: 0,
}); checked: false,
value: self.new_task.to_string(),
},
);
match result { match result {
Ok(t) => { Ok(t) => {
@ -110,60 +148,81 @@ impl Todo {
already_exists: false, already_exists: false,
}; };
self.tasks.push(data); self.tasks.push(data);
}, }
Err(e) => eprintln!("[ERROR] Failed to insert new task into DB:\n{e}"), Err(e) => eprintln!("[ERROR] Failed to insert new task into DB:\n{e}"),
} }
self.new_task = String::new(); self.new_task = String::new();
Task::none() Task::none()
}, }
Message::DeleteTask(index) => { Message::DeleteTask(index) => {
if self.tasks[index].checked { if self.tasks[index].checked {
self.completed_tasks -= 1; self.completed_tasks -= 1;
} }
if let Err(e) = db::delete_tasks(&self.conn, Some(self.tasks[index].db_id)) { if let Err(e) = db::delete_tasks(&self.conn, Some(self.tasks[index].db_id)) {
eprintln!("[ERROR] Failed to delete task '{}':\n{e}", self.tasks[index].value); eprintln!(
"[ERROR] Failed to delete task '{}':\n{e}",
self.tasks[index].value
);
} }
self.tasks.remove(index); self.tasks.remove(index);
Task::none() Task::none()
} }
Message::CheckTask(choice, id) => { Message::CheckTask(choice, id) => {
self.completed_tasks = if choice { self.completed_tasks + 1 } else { self.completed_tasks - 1 }; self.completed_tasks = if choice {
self.completed_tasks + 1
} else {
self.completed_tasks - 1
};
self.tasks[id].checked = choice; self.tasks[id].checked = choice;
let result = db::update_task(&self.conn, TaskColumn { let result = db::update_task(
id: self.tasks[id].db_id, &self.conn,
checked: self.tasks[id].checked, TaskColumn {
value: self.tasks[id].value.to_string(), id: self.tasks[id].db_id,
}, ChangeType::Checked); checked: self.tasks[id].checked,
value: self.tasks[id].value.to_string(),
},
ChangeType::Checked,
);
if let Err(e) = result { if let Err(e) = result {
eprintln!("[ERROR] Failed to update checkbox in local storage:\n{e}"); eprintln!("[ERROR] Failed to update checkbox in local storage:\n{e}");
} }
Task::none() Task::none()
}, }
Message::EditTask(index) => { Message::EditTask(index) => {
let set_update; let set_update;
if self.tasks[index].edit { if self.tasks[index].edit {
if self.updated_task.is_empty() { return Task::none(); } if self.updated_task.is_empty() {
if self.duplicate_exists(Some(&self.tasks[index].value)) { return Task::none(); } return Task::none();
}
if self.duplicate_exists(Some(&self.tasks[index].value)) {
return Task::none();
}
self.tasks[index].value = self.updated_task.clone(); self.tasks[index].value = self.updated_task.clone();
let result = db::update_task(&self.conn, TaskColumn { let result = db::update_task(
id: self.tasks[index].db_id, &self.conn,
checked: self.tasks[index].checked, TaskColumn {
value: self.tasks[index].value.to_string(), id: self.tasks[index].db_id,
}, ChangeType::Value); checked: self.tasks[index].checked,
value: self.tasks[index].value.to_string(),
},
ChangeType::Value,
);
if let Err(e) = result { if let Err(e) = result {
eprintln!("[ERROR] Failed to update task '{}' in local storage:\n{e}", self.tasks[index].value); eprintln!(
"[ERROR] Failed to update task '{}' in local storage:\n{e}",
self.tasks[index].value
);
} }
set_update = true; set_update = true;
@ -194,7 +253,7 @@ impl Todo {
self.completed_tasks = 0; self.completed_tasks = 0;
Task::none() Task::none()
}, }
Message::Event(event) => match event { Message::Event(event) => match event {
Event::Keyboard(keyboard::Event::KeyPressed { Event::Keyboard(keyboard::Event::KeyPressed {
key: keyboard::Key::Named(key::Named::Tab), key: keyboard::Key::Named(key::Named::Tab),
@ -211,22 +270,20 @@ impl Todo {
key: keyboard::Key::Named(key::Named::Delete), key: keyboard::Key::Named(key::Named::Delete),
modifiers, modifiers,
.. ..
}) => { }) => {
if modifiers.control() { if modifiers.control() {
Task::perform(async move { Message::DeleteAll }, |result| result) Task::perform(async move { Message::DeleteAll }, |result| result)
} else { } else {
Task::none() Task::none()
} }
} }
_ => Task::none() _ => Task::none(),
}, },
Message::TaskPush(option_id) => { Message::TaskPush(option_id) => match option_id {
match option_id { Some(i) => Task::perform(async move { Message::EditTask(i) }, |result| result),
Some(i) => Task::perform(async move { Message::EditTask(i) }, |result| result), None => Task::perform(async { Message::AddTask }, |result| result),
None => Task::perform(async { Message::AddTask }, |result| result),
}
}, },
Message::StorageToggle(toggle) => { Message::StorageToggle(_toggle) => {
// todo must be enabled eventually // todo must be enabled eventually
//self.local_storage = toggle; //self.local_storage = toggle;
@ -239,7 +296,7 @@ impl Todo {
// also how do we even implement that // also how do we even implement that
Task::none() Task::none()
}, }
Message::ToggleUnselect(toggle) => { Message::ToggleUnselect(toggle) => {
self.select_all = toggle; self.select_all = toggle;
for task in &mut self.tasks { for task in &mut self.tasks {
@ -257,31 +314,34 @@ impl Todo {
} }
Task::none() Task::none()
}, }
Message::SelectedTaskList(task) => {
self.current_task_group = Some(task);
Task::none()
}
Message::AddTaskList => {
if self.task_list_input.is_empty() { return Task::none(); }
self.task_list.push(self.task_list_input.clone());
self.task_list_state = combo_box::State::new(self.task_list.clone());
self.task_list_input = String::from("");
Task::none()
}
} }
} }
pub(crate) fn view(&self) -> Element<Message> { pub(crate) fn view(&self) -> Element<Message> {
let input = text_input("Enter new task", &self.new_task) let input = text_input("Enter new task", &self.new_task)
.width(300) .width(300)
.size(25) .size(25)
.on_input(|str| Message::ContentUpdated(true, str)) .on_input(|str| Message::ContentUpdated(ContentType::NewTask, str))
.on_submit(Message::TaskPush(None)); .on_submit(Message::TaskPush(None));
let add_btn = button( let add_btn = button(Text::new("Add").size(19).center())
Text::new("Add")
.size(19)
.center()
)
.width(Length::Shrink) .width(Length::Shrink)
.padding(10) .padding(10)
.on_press(Message::AddTask); .on_press(Message::AddTask);
let clear_btn = button( let clear_btn = button(Text::new("Clear").size(19).center())
Text::new("Clear")
.size(19)
.center()
)
.width(Length::Shrink) .width(Length::Shrink)
.padding(10) .padding(10)
.style(button::danger) .style(button::danger)
@ -291,14 +351,16 @@ impl Todo {
let mut saved_tasks = column![]; let mut saved_tasks = column![];
for (i, task) in self.tasks.iter().enumerate() { for (i, task) in self.tasks.iter().enumerate() {
let chk = checkbox("", task.checked).size(25).on_toggle(move |b| Message::CheckTask(b, i)); let chk = checkbox("", task.checked)
.size(25)
.on_toggle(move |b| Message::CheckTask(b, i));
let mut label = Text::new(&task.value).width(200); let mut label = Text::new(&task.value).width(200);
if task.already_exists { if task.already_exists {
label = label.style(danger); label = label.style(danger);
} }
let input = text_input("Enter new name", &self.updated_task) let input = text_input("Enter new name", &self.updated_task)
.width(200) .width(200)
.on_input(|str| Message::ContentUpdated(false, str)) .on_input(|str| Message::ContentUpdated(ContentType::ExistingTask, str))
.on_submit(Message::TaskPush(Some(i))); .on_submit(Message::TaskPush(Some(i)));
let mut edit = button(if task.edit { "save" } else { "edit" }).width(Length::Shrink); let mut edit = button(if task.edit { "save" } else { "edit" }).width(Length::Shrink);
if task.can_update { if task.can_update {
@ -309,29 +371,59 @@ impl Todo {
delete = delete.on_press(Message::DeleteTask(i)); delete = delete.on_press(Message::DeleteTask(i));
} }
let mut task_line = row![chk]; let mut task_line = row![chk];
task_line = if task.edit { task_line.push(input) } else { task_line.push(label) }; task_line = if task.edit {
task_line = task_line.push(edit).push(delete).spacing(10).padding([5, 10]); task_line.push(input)
} else {
task_line.push(label)
};
task_line = task_line
.push(edit)
.push(delete)
.spacing(10)
.padding([5, 10]);
saved_tasks = saved_tasks.push(task_line); saved_tasks = saved_tasks.push(task_line);
} }
let status = Text::new(format!("{} / {}", self.completed_tasks, self.tasks.len())); let status = Text::new(format!("{} / {}", self.completed_tasks, self.tasks.len()));
let unselect = checkbox("Un/select all", self.select_all).on_toggle(Message::ToggleUnselect); let tasklist = combo_box(
let storage = checkbox("Local storage", self.local_storage).on_toggle(Message::StorageToggle); &self.task_list_state,
let footer = row![status, Space::with_width(Length::Fill), unselect, storage].padding(10).spacing(10); "Test...",
self.current_task_group.as_ref(),
Message::SelectedTaskList,
)
.on_input(|str| Message::ContentUpdated(ContentType::NewList, str));
let add_tasklist = button("+").on_press(Message::AddTaskList);
let tasklist_group = row![tasklist.width(Length::Fill), add_tasklist].spacing(5);
let unselect =
checkbox("Select all", self.select_all).on_toggle(Message::ToggleUnselect);
let storage =
checkbox("Local storage", self.local_storage).on_toggle(Message::StorageToggle);
let footer = row![
status,
tasklist_group,
unselect,
storage
]
.padding(10)
.spacing(10);
let mut output = column![new_task.padding(10)]; let mut output = column![new_task.padding(10)];
output = if self.tasks.is_empty() { output.push(saved_tasks.height(Length::Fill)) } else { output.push(scrollable(saved_tasks).height(Length::Fill).spacing(10)) }; output = if self.tasks.is_empty() {
output.push(saved_tasks.height(Length::Fill))
} else {
output.push(scrollable(saved_tasks).height(Length::Fill).spacing(10))
};
output = output.align_x(Center).push(footer); output = output.align_x(Center).push(footer);
center(output).into() center(output).into()
} }
pub(crate) fn theme(&self) -> Theme { pub(crate) fn theme(&self) -> Theme {
Theme::Dark Theme::Dark
} }
pub(crate) fn subscription(&self) -> Subscription<Message> { pub(crate) fn subscription(&self) -> Subscription<Message> {
event::listen().map(Message::Event) event::listen().map(Message::Event)
} }
@ -371,11 +463,12 @@ impl Todo {
if task.already_exists { if task.already_exists {
match task_name { match task_name {
Some(t) => { Some(t) => {
if task.value != *t { return true; } if task.value != *t {
}, return true;
}
}
None => return true, None => return true,
} }
} }
} }