From e9ff7f7dcc2f76a48dc9fa060a72cad28a3f780c Mon Sep 17 00:00:00 2001 From: traxys Date: Sat, 9 Dec 2023 17:28:21 +0100 Subject: [PATCH 01/10] Add a total spendings section --- src/compare.rs | 247 +++++++++++++++++++++++++++++++------------------ src/main.rs | 1 + 2 files changed, 158 insertions(+), 90 deletions(-) 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; From 7545d7768f7c41e3dce01bf2a30a62476437a314 Mon Sep 17 00:00:00 2001 From: traxys Date: Sat, 9 Dec 2023 17:43:21 +0100 Subject: [PATCH 02/10] Reduce boilerplate in compare sections --- src/compare.rs | 50 +++++++++++++++++++++----------------------------- 1 file changed, 21 insertions(+), 29 deletions(-) diff --git a/src/compare.rs b/src/compare.rs index f9a7000..0715adb 100644 --- a/src/compare.rs +++ b/src/compare.rs @@ -143,7 +143,7 @@ where [map(a), map(b), map(c), map(d)] } - let mk_section = |section: Section| { + let mk_section_header = |section: Section| { let status = state.collapsed[section]; text_row( TEXT_EMPH2, @@ -348,45 +348,37 @@ where compare } - fn mk_accounts<'a>( - archive: &BTreeMap, - side: Option<&'a dyn Spendings>, - ) -> Option> { - let side = side?; - let (main_account, _) = side.main_account(archive); - - 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())) }; + macro_rules! mk_section { + {$section:expr, $map:expr} => { + itertools::chain![ + iter::once(mk_section_header($section)), + item_compare( + state.collapsed[$section], + left.map($map), + right.map($map) + ) + ] + }; + } + scrollable(table::<_, _, iced::Element<_>, _>( properties, itertools::chain![ iter::once(headings), - iter::once(mk_section(Section::Recurring)), - item_compare( - state.collapsed[Section::Recurring], - left.map(|r| r.recurring()), - right.map(|r| r.recurring()) - ), - iter::once(mk_section(Section::Variable)), - item_compare( - state.collapsed[Section::Variable], - left.map(|r| r.variable()), - right.map(|r| r.variable()) - ), + mk_section!(Section::Recurring, |r| r.recurring()), + mk_section!(Section::Variable, |r| r.variable()), compare_row(TEXT_EMPH2, Some("Total"), mk_total(left), mk_total(right)), - iter::once(mk_section(Section::Accounts)), - item_compare( - state.collapsed[Section::Accounts], - mk_accounts(self.archive, left), - mk_accounts(self.archive, right), - ), + mk_section!(Section::Accounts, |s| { + let (main_account, _) = s.main_account(self.archive); + + std::iter::once(("Main", main_account)).chain(s.savings()) + }), ], )) .into() From 09f35f84ea74b6ed1a50210221173ee4f9436f7f Mon Sep 17 00:00:00 2001 From: traxys Date: Sat, 9 Dec 2023 18:05:38 +0100 Subject: [PATCH 03/10] Add a section for comparing remaining amounts --- src/compare.rs | 85 +++++++++++++++++++++++++++++++++++--------------- src/main.rs | 2 ++ 2 files changed, 62 insertions(+), 25 deletions(-) diff --git a/src/compare.rs b/src/compare.rs index 0715adb..8556ba9 100644 --- a/src/compare.rs +++ b/src/compare.rs @@ -1,4 +1,4 @@ -use std::{cmp::Ordering, collections::BTreeMap, iter}; +use std::{borrow::Cow, cmp::Ordering, collections::BTreeMap, iter}; use enum_map::{Enum, EnumMap}; use iced::{ @@ -20,6 +20,8 @@ pub(crate) struct Compare<'a, F> { pub left: Option<(CompareLoad, Box)>, pub right: Option<(CompareLoad, Box)>, pub archive: &'a BTreeMap, + pub person_1: &'a str, + pub person_2: &'a str, pub on_load: F, } @@ -40,6 +42,7 @@ pub(crate) enum Section { Recurring, Variable, Accounts, + Remaining, } impl Section { @@ -48,6 +51,7 @@ impl Section { Section::Recurring => "Recurring", Section::Variable => "Variable", Section::Accounts => "Accounts", + Section::Remaining => "Remaining", } } } @@ -167,12 +171,15 @@ where let left = self.left.as_ref().map(|(_, r)| &**r); let right = self.right.as_ref().map(|(_, r)| &**r); - fn compare_row<'b, M>( + fn compare_row<'b, S, M>( size: u16, always: Option<&str>, - left: Option<(&str, f64)>, - right: Option<(&str, f64)>, - ) -> Vec<[iced::Element<'b, M>; 4]> { + left: Option<(&S, f64)>, + right: Option<(&S, f64)>, + ) -> Vec<[iced::Element<'b, M>; 4]> + where + S: AsRef + ?Sized, + { let float_text = |f: f64| Some(text(format!("{f:.2}")).size(size)); match (left, right) { @@ -183,24 +190,34 @@ where (None, Some((k, v))) => { vec![text_row( size, - [Some(text(k).size(size)), None, None, float_text(v)], + [Some(text(k.as_ref()).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(text(k.as_ref()).size(size)), float_text(v), None, None], )] } - (Some((lk, lv)), Some((rk, rv))) if lk != rk => { + (Some((lk, lv)), Some((rk, rv))) if lk.as_ref() != rk.as_ref() => { vec![ text_row( size, - [Some(text(lk).size(size)), float_text(lv), None, None], + [ + Some(text(lk.as_ref()).size(size)), + float_text(lv), + None, + None, + ], ), text_row( size, - [Some(text(rk).size(size)), None, None, float_text(rv)], + [ + Some(text(rk.as_ref()).size(size)), + None, + None, + float_text(rv), + ], ), ] } @@ -209,7 +226,7 @@ where vec![text_row( size, [ - Some(text(k).size(size)), + Some(text(k.as_ref()).size(size)), float_text(lv), None, float_text(rv), @@ -226,7 +243,7 @@ where vec![text_row( size, [ - Some(text(k).size(size)), + Some(text(k.as_ref()).size(size)), float_text(lv), Some(text(format!("{sign}{}%", difference.abs())).size(size)), float_text(rv), @@ -237,20 +254,21 @@ where } } - fn item_compare<'a, I, M>( + fn item_compare<'a, 's, I, S, M>( collapse: bool, left: Option, right: Option, ) -> Vec<[iced::Element<'a, M>; 4]> where - I: IntoIterator, + I: IntoIterator, + S: Into>, { if collapse { return Vec::new(); } - let to_btree = |i: I| i.into_iter().collect::>(); - + let entry_ref = |(s, v): (S, f64)| (s.into(), v); + let to_btree = |i: I| i.into_iter().map(entry_ref).collect::>(); let float_text = |f: f64| Some(text(format!("{f:.2}")).size(TEXT_NORMAL)); let (left, right) = match (left, right) { @@ -259,7 +277,8 @@ where (Some(u), None) => { return u .into_iter() - .sorted_by_key(|(k, _)| *k) + .map(entry_ref) + .sorted_by(|(ka, _), (kb, _)| ka.cmp(kb)) .map(|(k, v)| { text_row( TEXT_NORMAL, @@ -271,7 +290,8 @@ where (None, Some(u)) => { return u .into_iter() - .sorted_by_key(|(k, _)| *k) + .map(entry_ref) + .sorted_by(|(ka, _), (kb, _)| ka.cmp(kb)) .map(|(k, v)| { text_row( TEXT_NORMAL, @@ -290,16 +310,16 @@ where Right, } - type Peek<'a> = Option<&'a (&'a &'a str, &'a f64)>; + type Peek<'a> = Option<&'a (&'a Cow<'a, str>, &'a f64)>; let needs_to_wait = |l: Peek, r: Peek| match (l, r) { (_, None) | (None, _) => None, - (Some((&l, _)), Some((&r, _))) => match l.cmp(r) { - Ordering::Less => match left.contains_key(r) { + (Some((l, _)), Some((r, _))) => match l.cmp(r) { + Ordering::Less => match left.contains_key(*r) { false => None, true => Some(Side::Left), }, Ordering::Equal => None, - Ordering::Greater => match right.contains_key(l) { + Ordering::Greater => match right.contains_key(*l) { false => None, true => Some(Side::Right), }, @@ -308,15 +328,15 @@ where let mut compare = Vec::new(); - type Item<'a> = Option<(&'a &'a str, &'a f64)>; + type Item<'a> = Option<(&'a Cow<'a, str>, &'a f64)>; 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)), + l.map(|(a, b)| (a, *b)), + r.map(|(a, b)| (a, *b)), ) .into_iter() .inspect(|_| { @@ -379,6 +399,21 @@ where std::iter::once(("Main", main_account)).chain(s.savings()) }), + mk_section!(Section::Remaining, move |s| { + let contrib = s.contributions(self.archive); + + let remaining_1 = format!("Equal {}", self.person_1); + let remaining_2 = format!("Equal {}", self.person_2); + + [ + ( + Cow::Borrowed("Same"), + contrib.same_remaining.remaining.person_1, + ), + (remaining_1.into(), contrib.proportional.remaining.person_1), + (remaining_2.into(), contrib.proportional.remaining.person_2), + ] + }), ], )) .into() diff --git a/src/main.rs b/src/main.rs index 222a0ef..3839d88 100644 --- a/src/main.rs +++ b/src/main.rs @@ -521,6 +521,8 @@ impl Application for Glaurung { right: self.compare_load(self.compare_right), archive: &self.archive, on_load: Message::CompareLoad, + person_1: &self.config.person_1, + person_2: &self.config.person_2, }), }; From afccfcfb56be1f80e79ad6650eb5c5f95da8af24 Mon Sep 17 00:00:00 2001 From: traxys Date: Sat, 9 Dec 2023 18:09:55 +0100 Subject: [PATCH 04/10] Add a section comparing earnings --- src/compare.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/compare.rs b/src/compare.rs index 8556ba9..89cc21a 100644 --- a/src/compare.rs +++ b/src/compare.rs @@ -43,6 +43,7 @@ pub(crate) enum Section { Variable, Accounts, Remaining, + Earnings, } impl Section { @@ -52,6 +53,7 @@ impl Section { Section::Variable => "Variable", Section::Accounts => "Accounts", Section::Remaining => "Remaining", + Section::Earnings => "Earnings", } } } @@ -399,6 +401,14 @@ where std::iter::once(("Main", main_account)).chain(s.savings()) }), + mk_section!(Section::Earnings, |s| { + let earnings = s.earnings(); + + [ + (self.person_1, earnings.person_1), + (self.person_2, earnings.person_2), + ] + }), mk_section!(Section::Remaining, move |s| { let contrib = s.contributions(self.archive); From 283cf7a4a5bb79c3d4e0f35c94737621e6eabd95 Mon Sep 17 00:00:00 2001 From: traxys Date: Sat, 9 Dec 2023 19:05:26 +0100 Subject: [PATCH 05/10] Avoid using HashMap for coherent iteration order --- src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 3839d88..ffed6e2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,5 @@ use std::{ - collections::{BTreeMap, HashMap}, + collections::BTreeMap, fs::File, io::BufReader, num::NonZeroU8, @@ -209,7 +209,7 @@ struct Report { #[serde(default)] savings: BTreeMap, #[serde(default)] - variable: HashMap, + variable: BTreeMap, #[serde(default)] earnings_1: f64, #[serde(default)] From e67eb6200b19dbb779c0cdbe8d7e82d09a3f65a9 Mon Sep 17 00:00:00 2001 From: traxys Date: Sat, 9 Dec 2023 22:03:51 +0100 Subject: [PATCH 06/10] Refactor saves --- src/edit.rs | 38 ++++++++++++++++---------------------- src/main.rs | 47 +++++++++++++++++++++++++++++++++-------------- 2 files changed, 49 insertions(+), 36 deletions(-) diff --git a/src/edit.rs b/src/edit.rs index 1bab333..425eed7 100644 --- a/src/edit.rs +++ b/src/edit.rs @@ -1,4 +1,4 @@ -use std::{collections::BTreeMap, fs::OpenOptions, num::NonZeroU8, path::PathBuf}; +use std::{collections::BTreeMap, num::NonZeroU8}; use iced::{ theme, @@ -10,10 +10,11 @@ use iced::{ }; use iced_aw::{card, modal, number_input, selection_list}; use itertools::Itertools; +use serde::{Deserialize, Serialize}; use crate::{ - calc::calc_parser, BoxIter, Config, ContributionPoint, Message, Report, ReportDate, SaveFile, - Spendings, LIST_RULE, SECTION_RULE, TEXT_EMPH1, TEXT_EMPH2, TEXT_H1, TEXT_H2, + calc::calc_parser, BoxIter, Config, ContributionPoint, Message, Report, ReportDate, Spendings, + LIST_RULE, SECTION_RULE, TEXT_EMPH1, TEXT_EMPH2, TEXT_H1, TEXT_H2, }; struct AddFixed { @@ -778,7 +779,6 @@ pub struct EditState { recurring: BTreeMap, variable: BTreeMap)>, savings: BTreeMap, - save_file: PathBuf, earnings_1: f64, earnings_2: f64, average: NonZeroU8, @@ -850,15 +850,22 @@ macro_rules! msg2 { }; } +#[derive(Serialize, Deserialize, Default)] +pub(crate) struct EditSaveFile { + #[serde(flatten)] + pub current: Report, + #[serde(default)] + pub archive: BTreeMap, +} + impl EditState { - pub fn new(save_file: PathBuf) -> Self { + pub fn new() -> Self { Self { recurring: Default::default(), savings: Default::default(), variable: Default::default(), earnings_1: Default::default(), earnings_2: Default::default(), - save_file, average: NonZeroU8::MIN, date: None, } @@ -910,8 +917,8 @@ impl EditState { } } - pub(super) fn save(&self, archive: BTreeMap, pretty: bool) { - let mut save = SaveFile { + pub(super) fn save(&self, archive: BTreeMap) -> EditSaveFile { + let mut save = EditSaveFile { current: Default::default(), archive, }; @@ -923,20 +930,7 @@ impl EditState { None => save.current = self.report(), } - let tmp_path = format!(".glaurung-{}", std::process::id()); - let save_file = OpenOptions::new() - .create(true) - .write(true) - .truncate(true) - .open(&tmp_path) - .expect("Can't open temp save file"); - let to_writer = match pretty { - true => serde_json::to_writer_pretty, - false => serde_json::to_writer, - }; - to_writer(save_file, &save).expect("could not write save file"); - - std::fs::rename(tmp_path, &self.save_file).expect("could not save data"); + save } pub(super) fn update(&mut self, message: EditMessage) { diff --git a/src/main.rs b/src/main.rs index ffed6e2..611b6b0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ use std::{ collections::BTreeMap, - fs::File, + fs::{File, OpenOptions}, io::BufReader, num::NonZeroU8, ops::{Mul, MulAssign, Sub, SubAssign}, @@ -11,7 +11,7 @@ use anyhow::anyhow; use calc::calc_parser; use compare::Compare; use directories::ProjectDirs; -use edit::{EditMessage, EditState}; +use edit::{EditMessage, EditState, EditSaveFile}; use either::Either; use iced::{ font, subscription, @@ -203,7 +203,7 @@ pub(crate) trait Spendings { } #[derive(Serialize, Deserialize, Clone)] -struct Report { +pub(crate) struct Report { #[serde(default)] recurring: BTreeMap, #[serde(default)] @@ -283,7 +283,7 @@ impl Spendings for (ReportDate, Report) { } #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug, Hash)] -struct ReportDate { +pub(crate) struct ReportDate { pub year: u64, pub month: u8, } @@ -362,9 +362,7 @@ impl<'de> Deserialize<'de> for ReportDate { #[derive(Serialize, Deserialize, Default)] struct SaveFile { #[serde(flatten)] - current: Report, - #[serde(default)] - archive: BTreeMap, + edit: EditSaveFile, } #[derive(Default)] @@ -417,6 +415,8 @@ struct Glaurung { edit: EditState, view: CurrentView, + save_file: PathBuf, + compare_left: Option, compare_right: Option, @@ -436,6 +436,26 @@ impl Glaurung { .map(|rep| (e, Box::new((r, rep.clone())) as Box)), } } + + fn save(&self) { + let edit = self.edit.save(self.archive.clone()); + let save = SaveFile { edit }; + + let tmp_path = format!(".glaurung-{}", std::process::id()); + let save_file = OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(&tmp_path) + .expect("Can't open temp save file"); + let to_writer = match self.config.pretty_save { + true => serde_json::to_writer_pretty, + false => serde_json::to_writer, + }; + to_writer(save_file, &save).expect("could not write save file"); + + std::fs::rename(tmp_path, &self.save_file).expect("could not save data"); + } } impl Application for Glaurung { @@ -447,14 +467,15 @@ impl Application for Glaurung { fn new(config: Self::Flags) -> (Self, Command) { let mut this = Self { config: config.config, - edit: EditState::new(config.save_file), - archive: config.save.archive, + edit: EditState::new(), + save_file: config.save_file, + archive: config.save.edit.archive, view: CurrentView::Edit, compare_left: None, compare_right: None, }; - this.edit.load(None, config.save.current); + this.edit.load(None, config.save.edit.current); ( this, @@ -481,14 +502,12 @@ impl Application for Glaurung { Message::FontLoaded(r) => r.expect("could not load font"), Message::Event(ev) => { if let Event::Window(window::Event::CloseRequested) = ev { - self.edit - .save(self.archive.clone(), self.config.pretty_save); + self.save(); return window::close(); } } Message::Load(d) => { - self.edit - .save(self.archive.clone(), self.config.pretty_save); + self.save(); self.edit .load(Some(d), self.archive.get(&d).cloned().unwrap_or_default()); } From 3fd54ff8ea5f4032e1f5fda7a05f1996d067eea2 Mon Sep 17 00:00:00 2001 From: traxys Date: Sat, 27 Jan 2024 12:01:15 +0100 Subject: [PATCH 07/10] Add desktop file --- flake.nix | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index 5b2d80e..e4fe434 100644 --- a/flake.nix +++ b/flake.nix @@ -33,10 +33,17 @@ defaultPackage = naersk'.buildPackage { src = ./.; - nativeBuildInputs = [pkgs.makeWrapper]; + nativeBuildInputs = [pkgs.makeWrapper pkgs.copyDesktopItems]; postInstall = '' wrapProgram "$out/bin/glaurung" --prefix LD_LIBRARY_PATH : "${libPath}" ''; + desktopItems = [ + (pkgs.makeDesktopItem { + name = "glaurung"; + exec = "glaurung"; + desktopName = "Glaurung"; + }) + ]; }; }); } From 27e40f6b254432c84a21b3ec2485bff10f9c63c9 Mon Sep 17 00:00:00 2001 From: traxys Date: Mon, 13 May 2024 23:04:12 +0200 Subject: [PATCH 08/10] Update flake.nix --- flake.nix | 97 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 57 insertions(+), 40 deletions(-) diff --git a/flake.nix b/flake.nix index e4fe434..6f3ae39 100644 --- a/flake.nix +++ b/flake.nix @@ -5,45 +5,62 @@ inputs.naersk.url = "github:nix-community/naersk"; inputs.rust-overlay.url = "github:oxalica/rust-overlay"; - outputs = { - self, - nixpkgs, - flake-utils, - naersk, - rust-overlay, - }: - flake-utils.lib.eachDefaultSystem (system: let - pkgs = import nixpkgs { - inherit system; - overlays = [(import rust-overlay)]; - }; - rust = pkgs.rust-bin.stable.latest.default; - naersk' = pkgs.callPackage naersk { - cargo = rust; - rustc = rust; - }; - libPath = with pkgs; lib.makeLibraryPath [libxkbcommon wayland vulkan-loader]; - in { - devShell = pkgs.mkShell { - nativeBuildInputs = [rust pkgs.cargo-watch]; - RUST_PATH = "${rust}"; - RUST_DOC_PATH = "${rust}/share/doc/rust/html/std/index.html"; - LD_LIBRARY_PATH = libPath; - }; + outputs = + { + self, + nixpkgs, + flake-utils, + naersk, + rust-overlay, + }: + flake-utils.lib.eachDefaultSystem ( + system: + let + pkgs = import nixpkgs { + inherit system; + overlays = [ (import rust-overlay) ]; + }; + rust = pkgs.rust-bin.stable.latest.default; + naersk' = pkgs.callPackage naersk { + cargo = rust; + rustc = rust; + }; + libPath = + with pkgs; + lib.makeLibraryPath [ + libxkbcommon + wayland + vulkan-loader + ]; + in + { + devShell = pkgs.mkShell { + nativeBuildInputs = [ rust ] ++ (with pkgs; [ cargo-watch ]); + RUST_PATH = "${rust}"; + RUST_DOC_PATH = "${rust}/share/doc/rust/html/std/index.html"; + LD_LIBRARY_PATH = libPath; + }; - defaultPackage = naersk'.buildPackage { - src = ./.; - nativeBuildInputs = [pkgs.makeWrapper pkgs.copyDesktopItems]; - postInstall = '' - wrapProgram "$out/bin/glaurung" --prefix LD_LIBRARY_PATH : "${libPath}" - ''; - desktopItems = [ - (pkgs.makeDesktopItem { - name = "glaurung"; - exec = "glaurung"; - desktopName = "Glaurung"; - }) - ]; - }; - }); + packages = rec { + glaurung = default; + default = naersk'.buildPackage { + src = ./.; + nativeBuildInputs = with pkgs; [ + makeWrapper + copyDesktopItems + ]; + postInstall = '' + wrapProgram "$out/bin/glaurung" --prefix LD_LIBRARY_PATH : "${libPath}" + ''; + desktopItems = [ + (pkgs.makeDesktopItem { + name = "glaurung"; + exec = "glaurung"; + desktopName = "Glaurung"; + }) + ]; + }; + }; + } + ); } From 4d65995af1c8592d1e4ec8836c99430f0fa3ab37 Mon Sep 17 00:00:00 2001 From: traxys Date: Wed, 29 May 2024 20:01:19 +0200 Subject: [PATCH 09/10] Fix typo in same remaining --- src/edit.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/edit.rs b/src/edit.rs index 425eed7..06000ee 100644 --- a/src/edit.rs +++ b/src/edit.rs @@ -1014,7 +1014,7 @@ impl EditState { )), ]; - for (account, contribution) in &contributions.proportional.savings { + for (account, contribution) in &contributions.same_remaining.savings { same_remaining = same_remaining .push(text(account).size(TEXT_EMPH2)) .push(text(format!( From 8cc79d23311dbaee5edec3a471039535d2686f21 Mon Sep 17 00:00:00 2001 From: traxys Date: Wed, 29 May 2024 20:13:14 +0200 Subject: [PATCH 10/10] Correctly calculate same remaining main account This used to include the total savings --- src/main.rs | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index 611b6b0..7328390 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,7 @@ use std::{ fs::{File, OpenOptions}, io::BufReader, num::NonZeroU8, - ops::{Mul, MulAssign, Sub, SubAssign}, + ops::{AddAssign, Mul, MulAssign, Sub, SubAssign}, path::PathBuf, }; @@ -11,7 +11,7 @@ use anyhow::anyhow; use calc::calc_parser; use compare::Compare; use directories::ProjectDirs; -use edit::{EditMessage, EditState, EditSaveFile}; +use edit::{EditMessage, EditSaveFile, EditState}; use either::Either; use iced::{ font, subscription, @@ -54,6 +54,13 @@ impl ContributionPoint { } } +impl AddAssign for ContributionPoint { + fn add_assign(&mut self, rhs: ContributionPoint) { + self.person_1 += rhs.person_1; + self.person_2 += rhs.person_2; + } +} + impl SubAssign for ContributionPoint { fn sub_assign(&mut self, rhs: ContributionPoint) { self.person_1 -= rhs.person_1; @@ -156,6 +163,10 @@ pub(crate) trait Spendings { remaining -= main; let mut total_depositing = main_account; + let mut total_savings = ContributionPoint { + person_1: 0., + person_2: 0., + }; let (savings_same_percent, saving_same_remaining) = self .savings() @@ -166,6 +177,12 @@ pub(crate) trait Spendings { total_depositing += amount; remaining -= contributions; + let savings = ContributionPoint { + person_1: amount / 2., + person_2: amount / 2., + }; + total_savings += savings; + ( (account.to_string(), contributions), ( @@ -192,7 +209,7 @@ pub(crate) trait Spendings { savings: savings_same_percent, }, same_remaining: Contribution { - main: self.earnings() - remaining_per_person, + main: self.earnings() - remaining_per_person - total_savings, remaining: remaining_per_person, savings: saving_same_remaining, },