summaryrefslogtreecommitdiff
path: root/schist_desktop_gui/src/gui/components
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/src/gui/components
parent179878d12ba10ac242da2044f4c6dfe4d4829e59 (diff)
task-001: can navigate file screen options with keyboard
Diffstat (limited to 'schist_desktop_gui/src/gui/components')
-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
18 files changed, 351 insertions, 102 deletions
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
}