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,38 +80,61 @@ 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 {
task.already_exists = task.value == value.to_string(); task.already_exists = task.value == value.to_string();
} }
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();
}
let result = db::insert_task(&self.conn, TaskColumn { if self.duplicate_exists(None) {
id: 0, return Task::none();
checked: false, }
value: self.new_task.to_string(),
}); let result = db::insert_task(
&self.conn,
TaskColumn {
id: 0,
checked: false,
value: self.new_task.to_string(),
},
);
match result { match result {
Ok(t) => { Ok(t) => {
@ -110,65 +148,86 @@ 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;
self.updated_task = String::new(); self.updated_task = String::new();
for item in &mut self.tasks { for item in &mut self.tasks {
if item.already_exists { if item.already_exists {
item.already_exists = false; item.already_exists = false;
@ -187,14 +246,14 @@ impl Todo {
if let Err(e) = db::delete_tasks(&self.conn, None) { if let Err(e) = db::delete_tasks(&self.conn, None) {
eprintln!("[ERROR] Failed to delete all tasks:\n{e}") eprintln!("[ERROR] Failed to delete all tasks:\n{e}")
} }
self.new_task = String::from(""); self.new_task = String::from("");
self.updated_task = String::from(""); self.updated_task = String::from("");
self.tasks.clear(); self.tasks.clear();
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;
@ -235,53 +292,56 @@ impl Todo {
here we only call for storage change not implement the whole system here we only call for storage change not implement the whole system
as the system should be running since the program startup as the system should be running since the program startup
*/ */
// 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 {
task.checked = toggle; task.checked = toggle;
} }
if toggle { if toggle {
self.completed_tasks = self.tasks.len(); self.completed_tasks = self.tasks.len();
} else { } else {
self.completed_tasks = 0; self.completed_tasks = 0;
} }
if let Err(e) = db::update_unselect_tasks(&self.conn, toggle) { if let Err(e) = db::update_unselect_tasks(&self.conn, toggle) {
eprintln!("[ERROR] Failed to update un/select all operation in database:\n{e}"); eprintln!("[ERROR] Failed to update un/select all operation in database:\n{e}");
} }
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)
} }
@ -365,20 +457,21 @@ impl Todo {
} }
} }
} }
fn duplicate_exists(&self, task_name: Option<&String>) -> bool { fn duplicate_exists(&self, task_name: Option<&String>) -> bool {
for task in &self.tasks { for task in &self.tasks {
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,
} }
} }
} }
false false
} }
} }