diff options
Diffstat (limited to 'schist_desktop_gui/src/gui/screens')
5 files changed, 147 insertions, 165 deletions
diff --git a/schist_desktop_gui/src/gui/screens/files_screen.rs b/schist_desktop_gui/src/gui/screens/files_screen.rs index 68a9556..373b54f 100644 --- a/schist_desktop_gui/src/gui/screens/files_screen.rs +++ b/schist_desktop_gui/src/gui/screens/files_screen.rs @@ -3,20 +3,28 @@ mod view_files_screen; use std::{fmt::Debug, path::PathBuf}; -use crate::DatabaseConnection; +use crate::{ + gui::components::{navigation, Navigation, Panel}, + DatabaseConnection, +}; pub struct FilesScreen { - new_file_err: Option<(PathBuf, String)>, - file_paths: Vec<PathBuf>, - open_file_err: Option<(PathBuf, String)>, + options: Navigation<Panel<OptionId>>, } -#[derive(Clone, Debug)] -pub enum Message { - ImportFromActualbudget, +#[derive(Clone, Debug, PartialEq)] +pub enum OptionId { NewFile, - OpenFile(std::path::PathBuf), PickFile, + ImportFromActualbudget, + OpenFile(std::path::PathBuf), +} + +#[derive(Clone, Debug, PartialEq)] +pub enum Message { + NavigationMessage(navigation::Message<Panel<OptionId>>), + NextOption, + PrevOption, } #[derive(Debug)] @@ -27,10 +35,38 @@ pub enum Action { impl<'a> FilesScreen { pub fn new(file_paths: &'a [PathBuf]) -> Self { + let mut options = vec![ + Panel::new(OptionId::NewFile, "New file", None, None), + Panel::new(OptionId::PickFile, "Open file...", None, None), + Panel::new( + OptionId::ImportFromActualbudget, + "Import from Actualbudget", + None, + None, + ), + ]; + options.extend( + file_paths + .iter() + .map(|file_path| file_panel(file_path, None)), + ); + Self { - file_paths: file_paths.to_vec(), - new_file_err: None, - open_file_err: None, + options: Navigation::new(None, options), } } } + +fn file_panel<'a>(file_path: &'a PathBuf, err: Option<String>) -> Panel<OptionId> { + let file_name = file_path + .file_name() + .map(|os_str| os_str.to_str().unwrap_or("[invalid Unicode]")) + .unwrap_or("[invalid filename]"); + + Panel::new( + OptionId::OpenFile(file_path.clone()), + file_name, + file_path.to_str().map(String::from), + err.map(|err| format!("Failed to open new file {:#?}: {:#?}", file_path, err)), + ) +} diff --git a/schist_desktop_gui/src/gui/screens/files_screen/update_files_screen.rs b/schist_desktop_gui/src/gui/screens/files_screen/update_files_screen.rs index 9c45d6e..82ca7b6 100644 --- a/schist_desktop_gui/src/gui/screens/files_screen/update_files_screen.rs +++ b/schist_desktop_gui/src/gui/screens/files_screen/update_files_screen.rs @@ -4,34 +4,20 @@ use itertools::Itertools; use crate::{ database_connection::DatabaseConnectionResult, dialog::{import_from_actualbudget_dialog, new_file_dialog, pick_file_dialog}, + gui::components::{navigation, Panel}, paths::get_schist_database_directory, traits::Component, DatabaseConnection, }; -use super::{Action, FilesScreen, Message}; +use super::{Action, FilesScreen, Message, OptionId}; impl<'a> Component<'a, Message, Action> for FilesScreen { fn update(&mut self, message: Message) -> Action { match message { - Message::NewFile => match self.new_file() { - Some(Ok(file)) => Action::OpenedFile(file), - Some(Err(_)) | None => Action::None, - }, - - Message::OpenFile(file_path) => self - .open_file(file_path) - .map_or(Action::None, Action::OpenedFile), - - Message::PickFile => match self.pick_file() { - Some(Ok(file)) => Action::OpenedFile(file), - Some(Err(_)) | None => Action::None, - }, - - Message::ImportFromActualbudget => match self.import_from_actualbudget() { - Some(Ok(file)) => Action::OpenedFile(file), - Some(Err(_)) | None => Action::None, - }, + Message::NavigationMessage(message) => self.update_options(message), + Message::NextOption => self.update_options(navigation::Message::SelectNext), + Message::PrevOption => self.update_options(navigation::Message::SelectPrev), } } } @@ -41,13 +27,12 @@ impl FilesScreen { &mut self, ) -> Option<Result<DatabaseConnection, (std::path::PathBuf, anyhow::Error)>> { pick_file_dialog(get_schist_database_directory().ok()).map(|db_connection_result| { - db_connection_result.ok().inspect_err(|(path, err)| { - self.open_file_err = Some((path.clone(), err.to_string())); - rfd::MessageDialog::new() - .set_title("Failed to import from Actualbudget") - .set_level(rfd::MessageLevel::Error) - .set_description(format!("{}", err.chain().join("\n | "))) - .show(); + db_connection_result.ok().inspect_err(|(_path, err)| { + self.report_err( + super::OptionId::PickFile, + format!("{}", err.chain().join("\n | ")).as_str(), + "Failed to open file", + ); }) }) } @@ -59,12 +44,11 @@ impl FilesScreen { DatabaseConnection::establish(file_path.clone()) .ok() .inspect_err(|(_path, err)| { - self.open_file_err = Some((file_path, err.to_string())); - rfd::MessageDialog::new() - .set_title("Failed to import from Actualbudget") - .set_level(rfd::MessageLevel::Error) - .set_description(format!("{}", err.chain().join("\n | "))) - .show(); + self.report_err( + OptionId::OpenFile(file_path), + format!("{}", err.chain().join("\n | ")).as_str(), + "Failed to open file", + ); }) } @@ -72,13 +56,12 @@ impl FilesScreen { &mut self, ) -> Option<Result<DatabaseConnection, (std::path::PathBuf, anyhow::Error)>> { new_file_dialog(get_schist_database_directory().ok()).map(|result| { - result.ok().inspect_err(|(path, err)| { - self.new_file_err = Some((path.clone(), err.to_string())); - rfd::MessageDialog::new() - .set_title("Failed to import from Actualbudget") - .set_level(rfd::MessageLevel::Error) - .set_description(format!("{}", err.chain().join("\n | "))) - .show(); + result.ok().inspect_err(|(_path, err)| { + self.report_err( + OptionId::NewFile, + "Failed to create new file", + format!("{}", err.chain().join("\n | ")).as_str(), + ); }) }) } @@ -86,13 +69,12 @@ impl FilesScreen { fn import_from_actualbudget(&mut self) -> Option<anyhow::Result<DatabaseConnection>> { match import_from_actualbudget_dialog() { Some(Ok(state)) => self.new_file_with_state(&state), - Some(Err((path, err))) => Some({ - self.open_file_err = Some((path, err.to_string())); - rfd::MessageDialog::new() - .set_title("Failed to import from Actualbudget") - .set_level(rfd::MessageLevel::Error) - .set_description(format!("{}", err.chain().join("\n | "))) - .show(); + Some(Err((_path, err))) => Some({ + self.report_err( + OptionId::ImportFromActualbudget, + "Failed to import from Actualbudget", + format!("{}", err.chain().join("\n | ")).as_str(), + ); Err(err) }), None => None, @@ -110,16 +92,71 @@ impl FilesScreen { export_schist_state(state, &mut file.connection).ok()?; Ok(file) }), - Some(Err((path, err))) => Some({ - self.new_file_err = Some((path, err.to_string())); - rfd::MessageDialog::new() - .set_title("Failed to import from Actualbudget") - .set_level(rfd::MessageLevel::Error) - .set_description(format!("{}", err.chain().join("\n | "))) - .show(); + Some(Err((_path, err))) => Some({ + self.report_err( + OptionId::NewFile, + format!("{}", err.chain().join("\n | ")).as_str(), + "Failed to create new file", + ); Err(err) }), None => None, } } + + fn report_err(&mut self, id: OptionId, err: &str, dialog_title: &str) { + let old_panel = self.options.find(|o| o.id == id); + let file_panel_with_err = Panel::new( + id.clone(), + old_panel.as_ref().map(|p| p.name.as_str()).unwrap_or(""), + old_panel.as_ref().map(|p| p.small.clone()).flatten(), + Some(err.to_string()), + ); + self.options + .replace_first(|o| o.id == id, &vec![file_panel_with_err]); + + rfd::MessageDialog::new() + .set_title(dialog_title) + .set_level(rfd::MessageLevel::Error) + .set_description(err) + .show(); + } + + fn update_options(&mut self, message: navigation::Message<Panel<OptionId>>) -> Action { + match self.options.update(message) { + navigation::Action::ActivateOption(option) => match option { + Panel { + id: OptionId::NewFile, + .. + } => match self.new_file() { + Some(Ok(file)) => Action::OpenedFile(file), + Some(Err(_)) | None => Action::None, + }, + + Panel { + id: OptionId::OpenFile(file_path), + .. + } => self + .open_file(file_path.clone()) + .map_or(Action::None, Action::OpenedFile), + + Panel { + id: OptionId::PickFile, + .. + } => match self.pick_file() { + Some(Ok(file)) => Action::OpenedFile(file), + Some(Err(_)) | None => Action::None, + }, + + Panel { + id: OptionId::ImportFromActualbudget, + .. + } => match self.import_from_actualbudget() { + Some(Ok(file)) => Action::OpenedFile(file), + Some(Err(_)) | None => Action::None, + }, + }, + navigation::Action::SelectOption(_) | navigation::Action::None => Action::None, + } + } } diff --git a/schist_desktop_gui/src/gui/screens/files_screen/view_files_screen.rs b/schist_desktop_gui/src/gui/screens/files_screen/view_files_screen.rs index 3cbd939..8ff9f63 100644 --- a/schist_desktop_gui/src/gui/screens/files_screen/view_files_screen.rs +++ b/schist_desktop_gui/src/gui/screens/files_screen/view_files_screen.rs @@ -1,106 +1,9 @@ -use std::path::PathBuf; - -use iced::widget::column; - -use crate::{ - gui::components::{panel_button, Text}, - style::*, - traits::Viewable, -}; +use crate::traits::Viewable; use super::{FilesScreen, Message}; impl<'a> Viewable<'a, Message> for FilesScreen { fn view(&'a self) -> iced::Element<'a, Message> { - let mut cols = Vec::new(); - if let Some(col) = self.view_new_file_err() { - cols.push(col); - }; - if let Some(col) = self.view_open_file_err() { - cols.push(col); - }; - cols.push(self.view_new_file_button()); - cols.push(self.view_open_file_button()); - cols.push(self.view_import_from_actualbudget_button()); - cols.append(&mut self.view_files()); - column(cols).padding(SPACING_LG).spacing(SPACING_MD).into() - } -} - -impl<'a> FilesScreen { - fn view_new_file_err(&'a self) -> Option<iced::Element<'a, Message>> { - self.new_file_err.as_ref().map(|(path, err)| { - let msg = format!("Failed to open new file {:#?}: {:#?}", path, err); - Text::new(&msg).danger().into() - }) - } - - fn view_open_file_err(&'a self) -> Option<iced::Element<'a, Message>> { - self.open_file_err - .as_ref() - .filter(|(file_path, _err)| !self.file_paths.iter().any(|fp| *fp == *file_path)) - .map(|(file_path, err)| { - let msg = format!("Failed to open new file {:#?}: {:#?}", file_path, err); - Text::new(&msg).danger().into() - }) - } - - fn view_files(&'a self) -> Vec<iced::Element<'a, Message>> { - self.file_paths - .iter() - .map(|f| { - view_file( - f, - self.open_file_err - .clone() - .filter(|(file, _err)| *file == *f) - .map(|(_file, err)| err), - ) - }) - .collect() - } - - fn view_new_file_button(&'a self) -> iced::Element<'a, Message> { - panel_button::<Text, Message>(Text::default("New file").into(), Message::NewFile).into() - } - - fn view_open_file_button(&'a self) -> iced::Element<'a, Message> { - panel_button::<Text, Message>(Text::default("Open file").into(), Message::PickFile).into() - } - - fn view_import_from_actualbudget_button(&'a self) -> iced::Element<'a, Message> { - panel_button::<Text, Message>( - Text::default("Import from Actualbudget").into(), - Message::ImportFromActualbudget, - ) - .into() + self.options.view().map(Message::NavigationMessage) } } - -fn view_file<'a>(file_path: &'a PathBuf, err: Option<String>) -> iced::Element<'a, Message> { - let mut cols: Vec<iced::Element<'a, Message>> = err - .map(|err| { - let msg = format!("Failed to open new file {:#?}: {:#?}", file_path, err); - vec![Text::new(&msg).danger().small().into()] - }) - .unwrap_or_else(Vec::new); - - cols.push( - Text::default(file_path.to_str().unwrap_or("")) - .small() - .weak() - .into(), - ); - - cols.push( - Text::default( - file_path - .file_name() - .map(|os_str| os_str.to_str().unwrap_or("[invalid Unicode]")) - .unwrap_or("[invalid filename]"), - ) - .into(), - ); - - panel_button(column(cols), Message::OpenFile(file_path.clone())).into() -} diff --git a/schist_desktop_gui/src/gui/screens/main_screen/main_screen.rs b/schist_desktop_gui/src/gui/screens/main_screen/main_screen.rs index cddec32..5ed4b30 100644 --- a/schist_desktop_gui/src/gui/screens/main_screen/main_screen.rs +++ b/schist_desktop_gui/src/gui/screens/main_screen/main_screen.rs @@ -95,7 +95,8 @@ impl<'a> Component<'a, Message, Action> for MainScreen { } Message::ViewNavigationMessage(message) => match self.view_navigation.update(message) { - navigation::Action::SelectOption(view) => { + navigation::Action::ActivateOption(view) + | navigation::Action::SelectOption(view) => { self.active_view = view; Action::None } diff --git a/schist_desktop_gui/src/gui/screens/main_screen/view.rs b/schist_desktop_gui/src/gui/screens/main_screen/view.rs index 5f2df4e..2ff5753 100644 --- a/schist_desktop_gui/src/gui/screens/main_screen/view.rs +++ b/schist_desktop_gui/src/gui/screens/main_screen/view.rs @@ -1,6 +1,6 @@ use iced::{widget::Text, Element}; -use crate::gui::components::navigation; +use crate::{gui::components::navigation, traits::Focusable}; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum View { @@ -46,3 +46,8 @@ impl<'a> From<&'a View> for Element<'a, navigation::Message<View>> { Text::new(val.name()).into() } } + +impl Focusable for View { + fn focus(&mut self) {} + fn unfocus(&mut self) {} +} |
