summaryrefslogtreecommitdiff
path: root/schist_desktop_gui
diff options
context:
space:
mode:
authorJoe Carstairs <me@joeac.net>2026-02-07 11:52:08 +0000
committerJoe Carstairs <me@joeac.net>2026-02-07 12:02:27 +0000
commit1664e741ad35ce52a01788c34029f0b2ea477e32 (patch)
treedd6bd7c83a7e834c75815a786d67b5188cdd1f48 /schist_desktop_gui
parent179878d12ba10ac242da2044f4c6dfe4d4829e59 (diff)
task-001: can navigate file screen options with keyboard
Diffstat (limited to 'schist_desktop_gui')
-rw-r--r--schist_desktop_gui/src/gui.rs18
-rw-r--r--schist_desktop_gui/src/gui/components.rs4
-rw-r--r--schist_desktop_gui/src/gui/components/balances_view/update_balances_view.rs9
-rw-r--r--schist_desktop_gui/src/gui/components/bucket_navigation_entry.rs18
-rw-r--r--schist_desktop_gui/src/gui/components/bucket_navigation_entry/bucket_group_name.rs9
-rw-r--r--schist_desktop_gui/src/gui/components/bucket_navigation_entry/bucket_name_and_balance.rs9
-rw-r--r--schist_desktop_gui/src/gui/components/buckets_view.rs88
-rw-r--r--schist_desktop_gui/src/gui/components/button.rs59
-rw-r--r--schist_desktop_gui/src/gui/components/navigation.rs5
-rw-r--r--schist_desktop_gui/src/gui/components/navigation/dummy_navigation_option.rs9
-rw-r--r--schist_desktop_gui/src/gui/components/navigation/navigate_navigation.rs31
-rw-r--r--schist_desktop_gui/src/gui/components/navigation/navigation_find.rs16
-rw-r--r--schist_desktop_gui/src/gui/components/navigation/navigation_replace_options.rs5
-rw-r--r--schist_desktop_gui/src/gui/components/navigation/navigation_select_option.rs24
-rw-r--r--schist_desktop_gui/src/gui/components/navigation/navigation_set_options.rs9
-rw-r--r--schist_desktop_gui/src/gui/components/navigation/update_navigation.rs11
-rw-r--r--schist_desktop_gui/src/gui/components/navigation/view_navigation_button.rs13
-rw-r--r--schist_desktop_gui/src/gui/components/panel.rs101
-rw-r--r--schist_desktop_gui/src/gui/components/text.rs25
-rw-r--r--schist_desktop_gui/src/gui/components/transactions_view.rs12
-rw-r--r--schist_desktop_gui/src/gui/screens/files_screen.rs58
-rw-r--r--schist_desktop_gui/src/gui/screens/files_screen/update_files_screen.rs143
-rw-r--r--schist_desktop_gui/src/gui/screens/files_screen/view_files_screen.rs101
-rw-r--r--schist_desktop_gui/src/gui/screens/main_screen/main_screen.rs3
-rw-r--r--schist_desktop_gui/src/gui/screens/main_screen/view.rs7
-rw-r--r--schist_desktop_gui/src/traits.rs2
-rw-r--r--schist_desktop_gui/src/traits/focusable.rs18
27 files changed, 537 insertions, 270 deletions
diff --git a/schist_desktop_gui/src/gui.rs b/schist_desktop_gui/src/gui.rs
index c11a609..ae8e034 100644
--- a/schist_desktop_gui/src/gui.rs
+++ b/schist_desktop_gui/src/gui.rs
@@ -102,11 +102,25 @@ impl Gui {
iced::Task::none()
}
crate::shortcut::Command::GotoNextItem => {
- self.main_screen.update(main_screen::Message::NextItem);
+ match self.active_screen {
+ Screen::FilesScreen => {
+ self.files_screen.update(files_screen::Message::NextOption);
+ }
+ Screen::MainScreen => {
+ self.main_screen.update(main_screen::Message::NextItem);
+ }
+ };
iced::Task::none()
}
crate::shortcut::Command::GotoPrevItem => {
- self.main_screen.update(main_screen::Message::PrevItem);
+ match self.active_screen {
+ Screen::FilesScreen => {
+ self.files_screen.update(files_screen::Message::PrevOption);
+ }
+ Screen::MainScreen => {
+ self.main_screen.update(main_screen::Message::PrevItem);
+ }
+ };
iced::Task::none()
}
crate::shortcut::Command::ExpandItem => {
diff --git a/schist_desktop_gui/src/gui/components.rs b/schist_desktop_gui/src/gui/components.rs
index b0f8faf..bb5590f 100644
--- a/schist_desktop_gui/src/gui/components.rs
+++ b/schist_desktop_gui/src/gui/components.rs
@@ -3,13 +3,15 @@ pub mod bucket_navigation_entry;
pub mod buckets_view;
pub mod button;
pub mod navigation;
+pub mod panel;
pub mod text;
pub mod transactions_view;
pub use balances_view::BalancesView;
pub use bucket_navigation_entry::BucketNavigationEntry;
pub use buckets_view::BucketsView;
-pub use button::{active_button, inactive_button, panel_button};
+pub use button::{button, panel_button};
pub use navigation::Navigation;
+pub use panel::Panel;
pub use text::Text;
pub use transactions_view::TransactionsView;
diff --git a/schist_desktop_gui/src/gui/components/balances_view/update_balances_view.rs b/schist_desktop_gui/src/gui/components/balances_view/update_balances_view.rs
index 1dd5c6f..5661954 100644
--- a/schist_desktop_gui/src/gui/components/balances_view/update_balances_view.rs
+++ b/schist_desktop_gui/src/gui/components/balances_view/update_balances_view.rs
@@ -38,7 +38,7 @@ fn select_prev_bucket(balances_view: &mut BalancesView) -> () {
.navigation
.update(navigation::Message::SelectPrev)
{
- navigation::Action::SelectOption(option) => {
+ navigation::Action::ActivateOption(option) | navigation::Action::SelectOption(option) => {
balances_view.greeting = make_balances_view_greeting(Some(option));
}
navigation::Action::None => {}
@@ -50,7 +50,7 @@ fn select_next_bucket(balances_view: &mut BalancesView) -> () {
.navigation
.update(navigation::Message::SelectNext)
{
- navigation::Action::SelectOption(option) => {
+ navigation::Action::ActivateOption(option) | navigation::Action::SelectOption(option) => {
balances_view.greeting = make_balances_view_greeting(Some(option));
}
navigation::Action::None => {}
@@ -63,7 +63,7 @@ fn set_buckets(balances_view: &mut BalancesView, buckets: Vec<schist_models::Buc
.navigation
.update(navigation::Message::SetOptions(view_group_names(buckets)))
{
- navigation::Action::SelectOption(option) => {
+ navigation::Action::ActivateOption(option) | navigation::Action::SelectOption(option) => {
balances_view.greeting = make_balances_view_greeting(Some(option));
}
navigation::Action::None => {}
@@ -72,7 +72,8 @@ fn set_buckets(balances_view: &mut BalancesView, buckets: Vec<schist_models::Buc
fn update_navigation(balances_view: &mut BalancesView, message: navigation::Message<Text>) -> () {
match balances_view.navigation.update(message) {
- navigation::Action::SelectOption(group_name) => {
+ navigation::Action::ActivateOption(group_name)
+ | navigation::Action::SelectOption(group_name) => {
balances_view.greeting = make_balances_view_greeting(Some(group_name));
}
navigation::Action::None => {}
diff --git a/schist_desktop_gui/src/gui/components/bucket_navigation_entry.rs b/schist_desktop_gui/src/gui/components/bucket_navigation_entry.rs
index be11178..69665a3 100644
--- a/schist_desktop_gui/src/gui/components/bucket_navigation_entry.rs
+++ b/schist_desktop_gui/src/gui/components/bucket_navigation_entry.rs
@@ -4,7 +4,7 @@ use iced::Element;
use itertools::Either;
use schist_models::Bucket;
-use crate::gui::components::navigation;
+use crate::{gui::components::navigation, traits::Focusable};
mod bucket_group_name;
mod bucket_name_and_balance;
@@ -66,3 +66,19 @@ impl<'a> From<&'a BucketNavigationEntry>
bucket_navigation_entry.0.as_ref().either_into()
}
}
+
+impl Focusable for BucketNavigationEntry {
+ fn focus(&mut self) {
+ match self.0.as_mut() {
+ Either::Left(group) => group.focus(),
+ Either::Right(bucket) => bucket.focus(),
+ };
+ }
+
+ fn unfocus(&mut self) {
+ match self.0.as_mut() {
+ Either::Left(group) => group.unfocus(),
+ Either::Right(bucket) => bucket.unfocus(),
+ };
+ }
+}
diff --git a/schist_desktop_gui/src/gui/components/bucket_navigation_entry/bucket_group_name.rs b/schist_desktop_gui/src/gui/components/bucket_navigation_entry/bucket_group_name.rs
index cbc9521..98902ee 100644
--- a/schist_desktop_gui/src/gui/components/bucket_navigation_entry/bucket_group_name.rs
+++ b/schist_desktop_gui/src/gui/components/bucket_navigation_entry/bucket_group_name.rs
@@ -3,12 +3,16 @@ use iced::{
Element, Length,
};
-use crate::gui::components::{navigation, BucketNavigationEntry};
+use crate::{
+ gui::components::{navigation, BucketNavigationEntry},
+ impl_focusable,
+};
#[derive(Clone, Debug, PartialEq)]
pub struct BucketGroupName {
pub group: String,
pub is_open: bool,
+ pub is_focused: bool,
}
impl BucketGroupName {
@@ -16,6 +20,7 @@ impl BucketGroupName {
Self {
group: group.to_string(),
is_open,
+ is_focused: false,
}
}
}
@@ -34,3 +39,5 @@ impl<'a> From<&'a BucketGroupName> for Element<'a, navigation::Message<BucketNav
row![text(text_content).width(Length::Fill),].into()
}
}
+
+impl_focusable!(BucketGroupName);
diff --git a/schist_desktop_gui/src/gui/components/bucket_navigation_entry/bucket_name_and_balance.rs b/schist_desktop_gui/src/gui/components/bucket_navigation_entry/bucket_name_and_balance.rs
index 7abca57..874540f 100644
--- a/schist_desktop_gui/src/gui/components/bucket_navigation_entry/bucket_name_and_balance.rs
+++ b/schist_desktop_gui/src/gui/components/bucket_navigation_entry/bucket_name_and_balance.rs
@@ -4,17 +4,22 @@ use iced::{
};
use schist_models::Bucket;
-use crate::gui::components::{navigation, BucketNavigationEntry};
+use crate::{
+ gui::components::{navigation, BucketNavigationEntry},
+ impl_focusable,
+};
#[derive(Clone, Debug, PartialEq)]
pub struct BucketNameAndBalance {
pub bucket: Bucket,
+ is_focused: bool,
}
impl BucketNameAndBalance {
pub fn new(bucket: &Bucket) -> Self {
Self {
bucket: bucket.clone(),
+ is_focused: false,
}
}
}
@@ -40,3 +45,5 @@ impl<'a> From<&'a BucketNameAndBalance>
.into()
}
}
+
+impl_focusable!(BucketNameAndBalance);
diff --git a/schist_desktop_gui/src/gui/components/buckets_view.rs b/schist_desktop_gui/src/gui/components/buckets_view.rs
index 2b3258a..7f5b218 100644
--- a/schist_desktop_gui/src/gui/components/buckets_view.rs
+++ b/schist_desktop_gui/src/gui/components/buckets_view.rs
@@ -63,6 +63,43 @@ impl BucketsView {
fn update_greeting(&mut self, active_entry: &str) {
self.greeting = format!("Hello, {}!", active_entry);
}
+
+ fn expand_group(&mut self, group: String, buckets: Vec<Bucket>) -> Action {
+ match self.navigation.insert_after_first(
+ |bucket_navigation_entry| {
+ bucket_navigation_entry.is_group_and(|bucket_group_name| {
+ !bucket_group_name.is_open && bucket_group_name.group.eq(group.as_str())
+ })
+ },
+ &buckets
+ .iter()
+ .map(BucketNavigationEntry::from_bucket)
+ .collect(),
+ ) {
+ InsertOptionResult::InsertedOptions => {
+ self.navigation.replace_first(
+ |bucket_navigation_entry| {
+ bucket_navigation_entry.is_group_and(|g| g.group.eq(group.as_str()))
+ },
+ &vec![BucketNavigationEntry::from_group_open(&group)],
+ );
+ Action::None
+ }
+ InsertOptionResult::NoSuchOption => Action::None,
+ }
+ }
+
+ fn collapse_group(&mut self, group: String) -> Action {
+ self.navigation
+ .remove_options_where(|bucket_navigation_entry| {
+ bucket_navigation_entry.is_bucket_and(|bucket| bucket.group.eq(&group))
+ });
+ self.navigation.replace_first(
+ |bucket_navigation_entry| bucket_navigation_entry.is_group_and(|g| g.group.eq(&group)),
+ &vec![BucketNavigationEntry::from_group_closed(&group)],
+ );
+ Action::None
+ }
}
impl<'a> Viewable<'a, Message> for BucketsView {
@@ -97,7 +134,8 @@ impl<'a> Component<'a, Message, Action> for BucketsView {
.navigation
.update(navigation::Message::SetOptions(groups))
{
- navigation::Action::SelectOption(bucket_navigation_entry) => {
+ navigation::Action::ActivateOption(bucket_navigation_entry)
+ | navigation::Action::SelectOption(bucket_navigation_entry) => {
self.update_greeting(&bucket_navigation_entry.to_string());
Action::None
}
@@ -106,7 +144,8 @@ impl<'a> Component<'a, Message, Action> for BucketsView {
}
Message::NavigationMessage(message) => match self.navigation.update(message) {
- navigation::Action::SelectOption(bucket_navigation_entry) => {
+ navigation::Action::ActivateOption(bucket_navigation_entry)
+ | navigation::Action::SelectOption(bucket_navigation_entry) => {
self.update_greeting(&bucket_navigation_entry.to_string());
Action::None
}
@@ -114,7 +153,8 @@ impl<'a> Component<'a, Message, Action> for BucketsView {
},
Message::SelectNext => match self.navigation.update(navigation::Message::SelectNext) {
- navigation::Action::SelectOption(bucket_navigation_entry) => {
+ navigation::Action::ActivateOption(bucket_navigation_entry)
+ | navigation::Action::SelectOption(bucket_navigation_entry) => {
self.update_greeting(&bucket_navigation_entry.to_string());
Action::None
}
@@ -122,51 +162,17 @@ impl<'a> Component<'a, Message, Action> for BucketsView {
},
Message::SelectPrev => match self.navigation.update(navigation::Message::SelectPrev) {
- navigation::Action::SelectOption(bucket_navigation_entry) => {
+ navigation::Action::ActivateOption(bucket_navigation_entry)
+ | navigation::Action::SelectOption(bucket_navigation_entry) => {
self.update_greeting(&bucket_navigation_entry.to_string());
Action::None
}
navigation::Action::None => Action::None,
},
- Message::ExpandGroup(group, buckets) => {
- match self.navigation.insert_after_first(
- |bucket_navigation_entry| {
- bucket_navigation_entry.is_group_and(|bucket_group_name| {
- !bucket_group_name.is_open && bucket_group_name.group.eq(group.as_str())
- })
- },
- &buckets
- .iter()
- .map(BucketNavigationEntry::from_bucket)
- .collect(),
- ) {
- InsertOptionResult::InsertedOptions => {
- self.navigation.replace_first(
- |bucket_navigation_entry| {
- bucket_navigation_entry.is_group_and(|g| g.group.eq(group.as_str()))
- },
- &vec![BucketNavigationEntry::from_group_open(&group)],
- );
- Action::None
- }
- InsertOptionResult::NoSuchOption => Action::None,
- }
- }
+ Message::ExpandGroup(group, buckets) => self.expand_group(group, buckets),
- Message::CollapseGroup(group) => {
- self.navigation
- .remove_options_where(|bucket_navigation_entry| {
- bucket_navigation_entry.is_bucket_and(|bucket| bucket.group.eq(&group))
- });
- self.navigation.replace_first(
- |bucket_navigation_entry| {
- bucket_navigation_entry.is_group_and(|g| g.group.eq(&group))
- },
- &vec![BucketNavigationEntry::from_group_closed(&group)],
- );
- Action::None
- }
+ Message::CollapseGroup(group) => self.collapse_group(group),
}
}
}
diff --git a/schist_desktop_gui/src/gui/components/button.rs b/schist_desktop_gui/src/gui/components/button.rs
index 6882273..8a33e39 100644
--- a/schist_desktop_gui/src/gui/components/button.rs
+++ b/schist_desktop_gui/src/gui/components/button.rs
@@ -1,29 +1,58 @@
-use iced::{widget::Button, Element};
+use iced::{widget::Button, Element, Theme};
use crate::style::*;
-pub fn active_button<'a, Content, Message>(content: &'a Content) -> Button<'a, Message>
+pub fn button<'a, Content, Message>(
+ content: &'a Content,
+ message: Message,
+ focus: bool,
+) -> Button<'a, Message>
where
Element<'a, Message>: From<&'a Content>,
{
iced::widget::button(content)
- .style(|theme: &iced::Theme, _status| iced::widget::button::Style {
- background: Some(iced::Background::Color(
- theme.extended_palette().primary.base.text,
- )),
- text_color: theme.extended_palette().primary.base.color,
- ..Default::default()
+ .on_press(message)
+ .style(move |theme, status| match (status, focus) {
+ (iced::widget::button::Status::Hovered, _) => hovered_button_style(theme),
+ (iced::widget::button::Status::Pressed, _) | (_, true) => focused_button_style(theme),
+ (iced::widget::button::Status::Active, false) => base_button_style(theme),
+ (iced::widget::button::Status::Disabled, false) => todo!(),
})
.width(iced::Length::Fill)
}
-pub fn inactive_button<'a, Content: Into<Element<'a, Message>>, Message>(
- content: Content,
- on_press: Message,
-) -> Button<'a, Message> {
- iced::widget::button(content)
- .on_press(on_press)
- .width(iced::Length::Fill)
+fn hovered_button_style(theme: &Theme) -> iced::widget::button::Style {
+ iced::widget::button::Style {
+ background: Some(iced::Background::Color(
+ theme.extended_palette().background.weak.color,
+ )),
+ text_color: theme.extended_palette().primary.base.text,
+ ..base_button_style(theme)
+ }
+}
+
+fn focused_button_style(theme: &Theme) -> iced::widget::button::Style {
+ iced::widget::button::Style {
+ background: Some(iced::Background::Color(
+ theme.extended_palette().primary.base.text,
+ )),
+ text_color: theme.extended_palette().primary.base.color,
+ ..base_button_style(theme)
+ }
+}
+
+fn base_button_style(theme: &Theme) -> iced::widget::button::Style {
+ iced::widget::button::Style {
+ background: Some(iced::Background::Color(
+ theme.extended_palette().primary.base.color,
+ )),
+ border: iced::Border {
+ color: theme.extended_palette().primary.base.text,
+ ..Default::default()
+ },
+ text_color: theme.extended_palette().primary.base.text,
+ ..Default::default()
+ }
}
pub fn panel_button<'a, Content: Into<Element<'a, Message>>, Message>(
diff --git a/schist_desktop_gui/src/gui/components/navigation.rs b/schist_desktop_gui/src/gui/components/navigation.rs
index 6fe100d..1780f1a 100644
--- a/schist_desktop_gui/src/gui/components/navigation.rs
+++ b/schist_desktop_gui/src/gui/components/navigation.rs
@@ -1,4 +1,5 @@
mod navigate_navigation;
+mod navigation_find;
mod navigation_insert_options;
mod navigation_remove_options;
mod navigation_replace_options;
@@ -22,8 +23,9 @@ pub struct Navigation<TOption> {
options_after_active: Vec<TOption>,
}
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, PartialEq)]
pub enum Message<TOption> {
+ ActivateOption(TOption),
SelectNext,
SelectOption(TOption),
SelectPrev,
@@ -31,6 +33,7 @@ pub enum Message<TOption> {
}
pub enum Action<TOption> {
+ ActivateOption(TOption),
SelectOption(TOption),
None,
}
diff --git a/schist_desktop_gui/src/gui/components/navigation/dummy_navigation_option.rs b/schist_desktop_gui/src/gui/components/navigation/dummy_navigation_option.rs
index ae5799f..6875305 100644
--- a/schist_desktop_gui/src/gui/components/navigation/dummy_navigation_option.rs
+++ b/schist_desktop_gui/src/gui/components/navigation/dummy_navigation_option.rs
@@ -2,17 +2,22 @@ use std::fmt::Display;
use iced::Element;
-use crate::gui::components::{navigation, Text};
+use crate::{
+ gui::components::{navigation, Text},
+ impl_focusable,
+};
#[derive(Clone, Debug, PartialEq)]
pub struct DummyNavigationOption {
pub name: String,
+ is_focused: bool,
}
impl DummyNavigationOption {
pub fn new(name: &str) -> Self {
Self {
name: String::from(name),
+ is_focused: false,
}
}
}
@@ -30,3 +35,5 @@ impl Display for DummyNavigationOption {
f.write_str(&self.name)
}
}
+
+impl_focusable!(DummyNavigationOption);
diff --git a/schist_desktop_gui/src/gui/components/navigation/navigate_navigation.rs b/schist_desktop_gui/src/gui/components/navigation/navigate_navigation.rs
index f399cd0..72bc503 100644
--- a/schist_desktop_gui/src/gui/components/navigation/navigate_navigation.rs
+++ b/schist_desktop_gui/src/gui/components/navigation/navigate_navigation.rs
@@ -1,24 +1,39 @@
use iced::Element;
-use crate::traits::{Navigable, NavigationResult};
+use crate::traits::{Focusable, Navigable, NavigationResult};
use super::{Message, Navigation};
impl<'a, TOption> Navigable<TOption> for Navigation<TOption>
where
- TOption: Clone + PartialEq + 'a,
+ TOption: Clone + Focusable + PartialEq + 'a,
Element<'a, Message<TOption>>: From<&'a TOption>,
{
fn move_next(&mut self) -> NavigationResult<TOption> {
- if let Some(next) = self.options_after_active.clone().first() {
+ if let Some(next) = self.options_after_active.clone().first_mut() {
self.options_before_active = vec![
self.options_before_active.clone(),
- self.active_option.clone().map_or(vec![], |o| vec![o]),
+ self.active_option.clone().map_or_else(Vec::new, |mut o| {
+ o.unfocus();
+ vec![o]
+ }),
]
.concat();
+ next.focus();
self.active_option = Some(next.clone());
- self.options_after_active = self.options_after_active.split_at(1).1.to_vec();
+ self.options_after_active.remove(0);
NavigationResult::Moved(next.clone())
+ } else if let Some(first) = self
+ .options_before_active
+ .clone()
+ .first_mut()
+ .filter(|_| self.active_option.is_none())
+ {
+ self.options_after_active = self.options_before_active.split_at(1).1.to_vec();
+ self.options_before_active = Vec::new();
+ first.focus();
+ self.active_option = Some(first.clone());
+ NavigationResult::Moved(first.clone())
} else {
NavigationResult::LastOption
}
@@ -27,11 +42,15 @@ where
fn move_prev(&mut self) -> NavigationResult<TOption> {
if let Some(prev) = self.options_before_active.clone().last() {
self.options_after_active = vec![
- self.active_option.clone().map_or(vec![], |o| vec![o]),
+ self.active_option.clone().map_or(vec![], |mut o| {
+ o.unfocus();
+ vec![o]
+ }),
self.options_after_active.clone(),
]
.concat();
self.active_option = Some(prev.clone());
+ self.active_option.iter_mut().for_each(TOption::focus);
self.options_before_active = self
.options_before_active
.split_last()
diff --git a/schist_desktop_gui/src/gui/components/navigation/navigation_find.rs b/schist_desktop_gui/src/gui/components/navigation/navigation_find.rs
new file mode 100644
index 0000000..79c8ab0
--- /dev/null
+++ b/schist_desktop_gui/src/gui/components/navigation/navigation_find.rs
@@ -0,0 +1,16 @@
+impl<'a, TOption> super::Navigation<TOption>
+where
+ TOption: Clone + PartialEq + 'a,
+{
+ pub fn find<P>(&self, pred: P) -> Option<TOption>
+ where
+ P: Fn(&TOption) -> bool,
+ {
+ self.options_before_active
+ .iter()
+ .find(|o| pred(o))
+ .cloned()
+ .or_else(|| self.active_option.clone().filter(|o| pred(o)))
+ .or_else(|| self.options_after_active.iter().find(|o| pred(o)).cloned())
+ }
+}
diff --git a/schist_desktop_gui/src/gui/components/navigation/navigation_replace_options.rs b/schist_desktop_gui/src/gui/components/navigation/navigation_replace_options.rs
index e24059d..496250d 100644
--- a/schist_desktop_gui/src/gui/components/navigation/navigation_replace_options.rs
+++ b/schist_desktop_gui/src/gui/components/navigation/navigation_replace_options.rs
@@ -1,6 +1,8 @@
use iced::Element;
use itertools::Itertools;
+use crate::traits::Focusable;
+
use super::{Message, Navigation};
#[derive(PartialEq)]
@@ -11,7 +13,7 @@ pub enum ReplaceOptionResult {
impl<'a, TOption> Navigation<TOption>
where
- TOption: Clone + PartialEq + 'a,
+ TOption: Clone + Focusable + PartialEq + 'a,
Element<'a, Message<TOption>>: From<&'a TOption>,
{
pub fn replace_first<P>(&'a mut self, pred: P, new: &'a Vec<TOption>) -> ReplaceOptionResult
@@ -92,6 +94,7 @@ where
}
self.active_option = new.first().cloned();
+ self.active_option.iter_mut().for_each(|o| o.focus());
self.options_after_active
.splice(0..0, new.split_at(1).1.to_vec());
ReplaceOptionResult::ReplacedOption
diff --git a/schist_desktop_gui/src/gui/components/navigation/navigation_select_option.rs b/schist_desktop_gui/src/gui/components/navigation/navigation_select_option.rs
index 4192059..5aa96cd 100644
--- a/schist_desktop_gui/src/gui/components/navigation/navigation_select_option.rs
+++ b/schist_desktop_gui/src/gui/components/navigation/navigation_select_option.rs
@@ -1,5 +1,7 @@
use iced::Element;
+use crate::traits::Focusable;
+
use super::{Message, Navigation};
pub enum SelectOptionResult<TOption> {
@@ -10,7 +12,7 @@ pub enum SelectOptionResult<TOption> {
impl<'a, TOption> Navigation<TOption>
where
- TOption: Clone + PartialEq + 'a,
+ TOption: Clone + Focusable + PartialEq + 'a,
Element<'a, Message<TOption>>: From<&'a TOption>,
{
pub fn select_option(&mut self, option: TOption) -> SelectOptionResult<TOption>
@@ -30,15 +32,17 @@ where
{
let split = self.options_before_active.split_at(index);
self.options_after_active = vec![
- self.active_option
- .clone()
- .map_or(vec![], |active_option| vec![active_option]),
+ self.active_option.as_mut().map_or(vec![], |active_option| {
+ active_option.unfocus();
+ vec![active_option.clone()]
+ }),
split.1.split_at(1).1.to_vec(),
self.options_after_active.clone(),
]
.concat();
self.options_before_active = split.0.to_vec();
self.active_option = Some(option.clone());
+ self.active_option.iter_mut().for_each(TOption::focus);
SelectOptionResult::SelectedExistingOption(option)
} else if let Some(index) = self
.options_after_active
@@ -49,21 +53,25 @@ where
self.options_before_active = vec![
self.options_before_active.clone(),
split.0.to_vec(),
- self.active_option
- .clone()
- .map_or(vec![], |active_option| vec![active_option]),
+ self.active_option.as_mut().map_or(vec![], |active_option| {
+ active_option.unfocus();
+ vec![active_option.clone()]
+ }),
]
.concat();
self.active_option = Some(option.clone());
+ self.active_option.iter_mut().for_each(TOption::focus);
self.options_after_active = split.1.split_at(1).1.to_vec();
SelectOptionResult::SelectedExistingOption(option)
} else {
- if let Some(active_option) = &self.active_option {
+ if let Some(active_option) = &mut self.active_option {
+ active_option.unfocus();
self.options_before_active.push(active_option.clone());
}
self.options_before_active
.append(&mut self.options_after_active);
self.active_option = Some(option.clone());
+ self.active_option.iter_mut().for_each(TOption::focus);
self.options_after_active = vec![];
SelectOptionResult::SelectedNewOption(option)
}
diff --git a/schist_desktop_gui/src/gui/components/navigation/navigation_set_options.rs b/schist_desktop_gui/src/gui/components/navigation/navigation_set_options.rs
index c2c9952..3eb455c 100644
--- a/schist_desktop_gui/src/gui/components/navigation/navigation_set_options.rs
+++ b/schist_desktop_gui/src/gui/components/navigation/navigation_set_options.rs
@@ -1,5 +1,7 @@
use iced::Element;
+use crate::traits::Focusable;
+
use super::{Message, Navigation};
pub enum SetOptionsResult<TOption> {
@@ -9,10 +11,12 @@ pub enum SetOptionsResult<TOption> {
impl<'a, TOption> Navigation<TOption>
where
- TOption: Clone + PartialEq + 'a,
+ TOption: Clone + Focusable + PartialEq + 'a,
Element<'a, Message<TOption>>: From<&'a TOption>,
{
- pub fn set_options(&mut self, options: Vec<TOption>) -> SetOptionsResult<TOption> {
+ pub fn set_options(&mut self, options: &mut Vec<TOption>) -> SetOptionsResult<TOption> {
+ options.iter_mut().for_each(TOption::unfocus);
+
let active_option_index =
self.active_option
.clone()
@@ -31,6 +35,7 @@ where
.split_last()
.map_or_else(Vec::new, |(_last, rest)| rest.to_vec());
self.active_option = options.last().cloned();
+ self.active_option.iter_mut().for_each(TOption::focus);
options.last().cloned().map_or(
SetOptionsResult::SetOptionsAndKeptSelection,
SetOptionsResult::SetOptionsAndSetSelectedOption,
diff --git a/schist_desktop_gui/src/gui/components/navigation/update_navigation.rs b/schist_desktop_gui/src/gui/components/navigation/update_navigation.rs
index 1d5a734..7bcef98 100644
--- a/schist_desktop_gui/src/gui/components/navigation/update_navigation.rs
+++ b/schist_desktop_gui/src/gui/components/navigation/update_navigation.rs
@@ -4,14 +4,14 @@ use crate::{
gui::components::navigation::{
navigation_select_option::SelectOptionResult, navigation_set_options::SetOptionsResult,
},
- traits::{Component, Navigable, NavigationResult},
+ traits::{Component, Focusable, Navigable, NavigationResult},
};
use super::{Action, Message, Navigation};
impl<'a, TOption> Component<'a, Message<TOption>, Action<TOption>> for Navigation<TOption>
where
- TOption: Clone + PartialEq + 'a,
+ TOption: Clone + Focusable + PartialEq + 'a,
Element<'a, Message<TOption>>: From<&'a TOption>,
{
fn update(&mut self, message: Message<TOption>) -> Action<TOption> {
@@ -21,23 +21,22 @@ where
| SelectOptionResult::SelectedNewOption(option) => Action::SelectOption(option),
SelectOptionResult::AlreadySelected => Action::None,
},
-
- Message::SetOptions(options) => match self.set_options(options) {
+ Message::SetOptions(mut options) => match self.set_options(&mut options) {
SetOptionsResult::SetOptionsAndKeptSelection => Action::None,
SetOptionsResult::SetOptionsAndSetSelectedOption(option) => {
Action::SelectOption(option)
}
},
-
Message::SelectNext => match self.move_next() {
NavigationResult::Moved(option) => Action::SelectOption(option),
NavigationResult::LastOption => Action::None,
},
-
Message::SelectPrev => match self.move_prev() {
NavigationResult::Moved(option) => Action::SelectOption(option),
NavigationResult::LastOption => Action::None,
},
+
+ Message::ActivateOption(option) => Action::ActivateOption(option),
}
}
}
diff --git a/schist_desktop_gui/src/gui/components/navigation/view_navigation_button.rs b/schist_desktop_gui/src/gui/components/navigation/view_navigation_button.rs
index d65328c..80d6d59 100644
--- a/schist_desktop_gui/src/gui/components/navigation/view_navigation_button.rs
+++ b/schist_desktop_gui/src/gui/components/navigation/view_navigation_button.rs
@@ -1,6 +1,6 @@
use iced::Element;
-use crate::gui::components::{active_button, inactive_button};
+use crate::gui::components::button;
use super::{Message, Navigation};
@@ -13,15 +13,10 @@ where
&'a self,
option: &'a TOption,
) -> Element<'a, Message<TOption>> {
- if self
+ let focus = self
.active_option
.as_ref()
- .is_some_and(|o| TOption::eq(o, option))
- {
- active_button(option)
- } else {
- inactive_button(option, Message::SelectOption(option.clone()))
- }
- .into()
+ .is_some_and(|o| TOption::eq(o, option));
+ button(option, Message::ActivateOption(option.clone()), focus).into()
}
}
diff --git a/schist_desktop_gui/src/gui/components/panel.rs b/schist_desktop_gui/src/gui/components/panel.rs
new file mode 100644
index 0000000..364659b
--- /dev/null
+++ b/schist_desktop_gui/src/gui/components/panel.rs
@@ -0,0 +1,101 @@
+use iced::{
+ widget::{button::Status, column},
+ Theme,
+};
+
+use crate::{
+ gui::components::{navigation, panel_button, Text},
+ traits::Focusable,
+};
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct Panel<Id> {
+ pub err: Option<String>,
+ pub id: Id,
+ pub small: Option<String>,
+ pub name: String,
+ pub is_focused: bool,
+}
+
+impl<Id> Panel<Id> {
+ pub fn new(id: Id, text: &str, small: Option<String>, err: Option<String>) -> Self {
+ Self {
+ err: err.clone(),
+ id,
+ small: small.clone(),
+ name: text.to_string(),
+ is_focused: false,
+ }
+ }
+}
+
+impl<'a, Id> From<&'a Panel<Id>> for iced::Element<'a, navigation::Message<Panel<Id>>>
+where
+ Id: Clone,
+{
+ fn from(value: &'a Panel<Id>) -> Self {
+ let mut cols: Vec<iced::Element<'a, navigation::Message<Panel<Id>>>> = Vec::new();
+ if let Some(err) = &value.err {
+ cols.push(
+ Text::default(err)
+ .highlight_maybe(value.is_focused)
+ .small()
+ .danger()
+ .into(),
+ );
+ }
+ if let Some(small) = &value.small {
+ cols.push(
+ Text::default(small)
+ .highlight_maybe(value.is_focused)
+ .small()
+ .weak()
+ .into(),
+ );
+ }
+ cols.push(
+ Text::default(&value.name)
+ .highlight_maybe(value.is_focused)
+ .into(),
+ );
+ panel_button(
+ column(cols),
+ navigation::Message::ActivateOption(value.clone()),
+ )
+ .style(|theme, status| match (status, value.is_focused) {
+ (Status::Hovered, false) => hovered_style(theme),
+ (_, true) | (Status::Pressed, _) => focused_style(theme),
+ (Status::Active, false) | (Status::Disabled, false) => Default::default(),
+ })
+ .into()
+ }
+}
+
+fn hovered_style(theme: &Theme) -> iced::widget::button::Style {
+ iced::widget::button::Style {
+ background: Some(iced::Background::Color(
+ theme.extended_palette().background.weak.color,
+ )),
+ text_color: theme.extended_palette().primary.base.text,
+ ..Default::default()
+ }
+}
+
+fn focused_style(theme: &Theme) -> iced::widget::button::Style {
+ iced::widget::button::Style {
+ background: Some(iced::Background::Color(
+ theme.extended_palette().primary.base.text,
+ )),
+ text_color: theme.extended_palette().primary.base.color,
+ ..Default::default()
+ }
+}
+
+impl<Id> Focusable for Panel<Id> {
+ fn focus(&mut self) {
+ self.is_focused = true;
+ }
+ fn unfocus(&mut self) {
+ self.is_focused = false;
+ }
+}
diff --git a/schist_desktop_gui/src/gui/components/text.rs b/schist_desktop_gui/src/gui/components/text.rs
index a3093ac..d171043 100644
--- a/schist_desktop_gui/src/gui/components/text.rs
+++ b/schist_desktop_gui/src/gui/components/text.rs
@@ -1,6 +1,6 @@
use std::borrow::Borrow;
-use crate::style::*;
+use crate::{impl_focusable, style::*};
#[derive(Clone, Debug, PartialEq)]
pub struct Text {
@@ -8,12 +8,14 @@ pub struct Text {
colour: Option<Colour>,
size: Option<Size>,
strength: Option<Strength>,
+ is_focused: bool,
}
#[derive(Clone, Debug, PartialEq)]
enum Colour {
Danger,
Primary,
+ Highlight,
}
#[derive(Clone, Debug, PartialEq)]
@@ -35,6 +37,7 @@ impl Text {
colour: Some(Colour::Primary),
size: Some(Size::Base),
strength: Some(Strength::Base),
+ is_focused: false,
}
}
@@ -44,6 +47,7 @@ impl Text {
colour: None,
size: None,
strength: None,
+ is_focused: false,
}
}
@@ -71,6 +75,17 @@ impl Text {
..self.clone()
}
}
+
+ pub fn highlight_maybe(&self, do_highlight: bool) -> Self {
+ if do_highlight {
+ Self {
+ colour: Some(Colour::Highlight),
+ ..self.clone()
+ }
+ } else {
+ self.clone()
+ }
+ }
}
impl<'a, Message> From<Text> for iced::Element<'a, Message> {
@@ -111,8 +126,16 @@ where
(Some(Colour::Primary), Some(Strength::Weak)) => {
Some(theme.extended_palette().primary.weak.text)
}
+ (Some(Colour::Highlight), Some(Strength::Base) | None) => {
+ Some(theme.extended_palette().primary.base.color)
+ }
+ (Some(Colour::Highlight), Some(Strength::Weak)) => {
+ Some(theme.extended_palette().primary.weak.color)
+ }
(None, _) => None,
},
})
.into()
}
+
+impl_focusable!(Text);
diff --git a/schist_desktop_gui/src/gui/components/transactions_view.rs b/schist_desktop_gui/src/gui/components/transactions_view.rs
index 1956d38..1cb569c 100644
--- a/schist_desktop_gui/src/gui/components/transactions_view.rs
+++ b/schist_desktop_gui/src/gui/components/transactions_view.rs
@@ -62,7 +62,8 @@ impl<'a> Component<'a, Message, Action> for TransactionsView {
match message {
Message::NavigationMessage(message) => {
match self.navigation.update(message) {
- navigation::Action::SelectOption(group_name) => {
+ navigation::Action::ActivateOption(group_name)
+ | navigation::Action::SelectOption(group_name) => {
self.greeting = make_greeting(group_name);
Action::None
}
@@ -77,7 +78,8 @@ impl<'a> Component<'a, Message, Action> for TransactionsView {
.update(navigation::Message::SetOptions(view_account_names(
accounts,
))) {
- navigation::Action::SelectOption(option) => {
+ navigation::Action::ActivateOption(option)
+ | navigation::Action::SelectOption(option) => {
self.greeting = make_greeting(option);
Action::None
}
@@ -86,7 +88,8 @@ impl<'a> Component<'a, Message, Action> for TransactionsView {
}
Message::SelectNextTransaction => {
match self.navigation.update(navigation::Message::SelectNext) {
- navigation::Action::SelectOption(option) => {
+ navigation::Action::ActivateOption(option)
+ | navigation::Action::SelectOption(option) => {
self.greeting = make_greeting(option);
Action::None
}
@@ -95,7 +98,8 @@ impl<'a> Component<'a, Message, Action> for TransactionsView {
}
Message::SelectPrevTransaction => {
match self.navigation.update(navigation::Message::SelectPrev) {
- navigation::Action::SelectOption(option) => {
+ navigation::Action::ActivateOption(option)
+ | navigation::Action::SelectOption(option) => {
self.greeting = make_greeting(option);
Action::None
}
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) {}
+}
diff --git a/schist_desktop_gui/src/traits.rs b/schist_desktop_gui/src/traits.rs
index 9d79d27..9f6e235 100644
--- a/schist_desktop_gui/src/traits.rs
+++ b/schist_desktop_gui/src/traits.rs
@@ -1,7 +1,9 @@
mod component;
+mod focusable;
mod navigable;
mod viewable;
pub use component::Component;
+pub use focusable::Focusable;
pub use navigable::{Navigable, NavigationResult};
pub use viewable::Viewable;
diff --git a/schist_desktop_gui/src/traits/focusable.rs b/schist_desktop_gui/src/traits/focusable.rs
new file mode 100644
index 0000000..8eb9628
--- /dev/null
+++ b/schist_desktop_gui/src/traits/focusable.rs
@@ -0,0 +1,18 @@
+pub trait Focusable {
+ fn focus(&mut self);
+ fn unfocus(&mut self);
+}
+
+#[macro_export]
+macro_rules! impl_focusable {
+ ($target:ty) => {
+ impl $crate::traits::Focusable for $target {
+ fn focus(&mut self) {
+ self.is_focused = true;
+ }
+ fn unfocus(&mut self) {
+ self.is_focused = false;
+ }
+ }
+ };
+}