diff --git a/src/compare.rs b/src/compare.rs index 049ae66..f9a7000 100644 --- a/src/compare.rs +++ b/src/compare.rs @@ -13,6 +13,7 @@ use itertools::Itertools; use crate::{ CompareLoad, CompareSide, Report, ReportDate, Spendings, TEXT_EMPH2, TEXT_H1, TEXT_H2, + TEXT_NORMAL, }; pub(crate) struct Compare<'a, F> { @@ -38,7 +39,7 @@ pub(crate) enum CompareMsg { pub(crate) enum Section { Recurring, Variable, - Total, + Accounts, } impl Section { @@ -46,7 +47,7 @@ impl Section { match self { Section::Recurring => "Recurring", Section::Variable => "Variable", - Section::Total => "Total", + Section::Accounts => "Accounts", } } } @@ -116,54 +117,131 @@ where ]; let headings = [ - Some( - column![text("Item").size(TEXT_H1), text("").size(TEXT_H2)] - .align_items(Alignment::Center) - .into(), - ), - Some( - column![text("Left").size(TEXT_H1), heading_left] - .align_items(Alignment::Center) - .into(), - ), - Some( - column![text("Difference").size(TEXT_H1), text("").size(TEXT_H2)] - .align_items(Alignment::Center) - .into(), - ), - Some( - column![text("Right").size(TEXT_H1), heading_right] - .align_items(Alignment::Center) - .into(), - ), + column![text("Item").size(TEXT_H1), text("").size(TEXT_H2)] + .align_items(Alignment::Center) + .into(), + column![text("Left").size(TEXT_H1), heading_left] + .align_items(Alignment::Center) + .into(), + column![text("Difference").size(TEXT_H1), text("").size(TEXT_H2)] + .align_items(Alignment::Center) + .into(), + column![text("Right").size(TEXT_H1), heading_right] + .align_items(Alignment::Center) + .into(), ]; + fn text_row<'a, T, M>(size: u16, r: [Option; 4]) -> [iced::Element<'a, M>; 4] + where + T: Into>, + { + let [a, b, c, d] = r; + let map = |e: Option| { + e.map(Into::into) + .unwrap_or_else(|| text("").size(size).into()) + }; + [map(a), map(b), map(c), map(d)] + } + let mk_section = |section: Section| { let status = state.collapsed[section]; - [ - Some( - row![ - text(section.name()).size(TEXT_EMPH2), - horizontal_space(Length::Fill), - toggler(None, !status, move |b| CompareMsg::SetCollapse(section, !b)), - ] - .align_items(Alignment::Center) - .into(), - ), - Some(text("").size(TEXT_EMPH2).into()), - Some(text("").size(TEXT_EMPH2).into()), - Some(text("").size(TEXT_EMPH2).into()), - ] + text_row( + TEXT_EMPH2, + [ + Some( + row![ + text(section.name()).size(TEXT_EMPH2), + horizontal_space(Length::Fill), + toggler(None, !status, move |b| CompareMsg::SetCollapse(section, !b)) + .width(Length::Shrink), + ] + .align_items(Alignment::Center), + ), + None, + None, + None, + ], + ) }; let left = self.left.as_ref().map(|(_, r)| &**r); let right = self.right.as_ref().map(|(_, r)| &**r); + fn compare_row<'b, M>( + size: u16, + always: Option<&str>, + left: Option<(&str, f64)>, + right: Option<(&str, f64)>, + ) -> Vec<[iced::Element<'b, M>; 4]> { + let float_text = |f: f64| Some(text(format!("{f:.2}")).size(size)); + + match (left, right) { + (None, None) => match always { + None => vec![], + Some(l) => vec![text_row(size, [Some(text(l).size(size)), None, None, None])], + }, + (None, Some((k, v))) => { + vec![text_row( + size, + [Some(text(k).size(size)), None, None, float_text(v)], + )] + } + (Some((k, v)), None) => { + vec![text_row( + size, + [Some(text(k).size(size)), float_text(v), None, None], + )] + } + (Some((lk, lv)), Some((rk, rv))) if lk != rk => { + vec![ + text_row( + size, + [Some(text(lk).size(size)), float_text(lv), None, None], + ), + text_row( + size, + [Some(text(rk).size(size)), None, None, float_text(rv)], + ), + ] + } + (Some((k, lv)), Some((_, rv))) => { + if rv == 0. || lv == 0. { + vec![text_row( + size, + [ + Some(text(k).size(size)), + float_text(lv), + None, + float_text(rv), + ], + )] + } else { + let difference = ((rv / lv - 1.) * 100.) as i32; + let sign = match difference.cmp(&0) { + Ordering::Less => "- ", + Ordering::Equal => "", + Ordering::Greater => "+ ", + }; + + vec![text_row( + size, + [ + Some(text(k).size(size)), + float_text(lv), + Some(text(format!("{sign}{}%", difference.abs())).size(size)), + float_text(rv), + ], + )] + } + } + } + } + fn item_compare<'a, I, M>( collapse: bool, left: Option, right: Option, - ) -> Vec<[Option>; 4]> + ) -> Vec<[iced::Element<'a, M>; 4]> where I: IntoIterator, { @@ -173,7 +251,7 @@ where let to_btree = |i: I| i.into_iter().collect::>(); - let float_text = |f: f64| Some(text(format!("{f:.2}")).into()); + let float_text = |f: f64| Some(text(format!("{f:.2}")).size(TEXT_NORMAL)); let (left, right) = match (left, right) { (None, None) => return Vec::new(), @@ -182,14 +260,24 @@ where return u .into_iter() .sorted_by_key(|(k, _)| *k) - .map(|(k, v)| [Some(text(k).into()), float_text(v), None, None]) + .map(|(k, v)| { + text_row( + TEXT_NORMAL, + [Some(text(k).size(TEXT_NORMAL)), float_text(v), None, None], + ) + }) .collect(); } (None, Some(u)) => { return u .into_iter() .sorted_by_key(|(k, _)| *k) - .map(|(k, v)| [Some(text(k).into()), None, None, float_text(v)]) + .map(|(k, v)| { + text_row( + TEXT_NORMAL, + [Some(text(k).size(TEXT_NORMAL)), None, None, float_text(v)], + ) + }) .collect(); } }; @@ -221,49 +309,21 @@ where let mut compare = Vec::new(); type Item<'a> = Option<(&'a &'a str, &'a f64)>; - let mut insert_row = |l: Item, r: Item| match (l, r) { - (None, None) => false, - (None, Some((k, v))) => { - compare.push([Some(text(k).into()), None, None, float_text(*v)]); - true - } - (Some((k, v)), None) => { - compare.push([Some(text(k).into()), float_text(*v), None, None]); - true - } - (Some((lk, lv)), Some((rk, rv))) if lk != rk => { - compare.push([Some(text(lk).into()), float_text(*lv), None, None]); - - compare.push([Some(text(rk).into()), None, None, float_text(*rv)]); - - true - } - (Some((k, lv)), Some((_, rv))) => { - if *rv == 0. || *lv == 0. { - compare.push([ - Some(text(k).into()), - float_text(*lv), - None, - float_text(*rv), - ]); - } else { - let difference = ((rv / lv - 1.) * 100.) as i32; - let sign = match difference.cmp(&0) { - Ordering::Less => "- ", - Ordering::Equal => "", - Ordering::Greater => "+ ", - }; - - compare.push([ - Some(text(k).into()), - float_text(*lv), - Some(text(format!("{sign}{}%", difference.abs())).into()), - float_text(*rv), - ]); - } - - true - } + let mut insert_row = |l: Item, r: Item| { + let mut inserted = false; + compare.extend( + compare_row( + TEXT_NORMAL, + None, + l.map(|(a, b)| (*a, *b)), + r.map(|(a, b)| (*a, *b)), + ) + .into_iter() + .inspect(|_| { + inserted = true; + }), + ); + inserted }; loop { @@ -288,7 +348,7 @@ where compare } - fn mk_total<'a>( + fn mk_accounts<'a>( archive: &BTreeMap, side: Option<&'a dyn Spendings>, ) -> Option> { @@ -298,6 +358,12 @@ where Some(std::iter::once(("Main", main_account)).chain(side.savings())) } + let mk_total = |side: Option<&dyn Spendings>| { + let side = side?; + + Some(("Total", side.total_spendings())) + }; + scrollable(table::<_, _, iced::Element<_>, _>( properties, itertools::chain![ @@ -314,11 +380,12 @@ where left.map(|r| r.variable()), right.map(|r| r.variable()) ), - iter::once(mk_section(Section::Total)), + compare_row(TEXT_EMPH2, Some("Total"), mk_total(left), mk_total(right)), + iter::once(mk_section(Section::Accounts)), item_compare( - state.collapsed[Section::Variable], - mk_total(self.archive, left), - mk_total(self.archive, right), + state.collapsed[Section::Accounts], + mk_accounts(self.archive, left), + mk_accounts(self.archive, right), ), ], )) @@ -336,7 +403,7 @@ pub struct ColumnProperties { pub fn table<'a, I, J, T, M>(props: Vec, items: I) -> Row<'a, M> where I: IntoIterator, - J: IntoIterator>, + J: IntoIterator, T: Into>, M: 'a, { @@ -350,7 +417,7 @@ where col.push(horizontal_rule(5.).into()); } - let inner = item.map(Into::into).unwrap_or_else(|| text("").into()); + let inner = item.into(); col.push(inner); }); columns diff --git a/src/main.rs b/src/main.rs index 017eeda..222a0ef 100644 --- a/src/main.rs +++ b/src/main.rs @@ -29,6 +29,7 @@ mod edit; const TEXT_H1: u16 = 30; const TEXT_H2: u16 = 25; +const TEXT_NORMAL: u16 = 15; const TEXT_EMPH1: u16 = 17; const TEXT_EMPH2: u16 = 20;