Handle variable costs
This commit is contained in:
parent
3824700332
commit
9e849349d4
1 changed files with 208 additions and 5 deletions
213
src/main.rs
213
src/main.rs
|
|
@ -1,5 +1,5 @@
|
||||||
use std::{
|
use std::{
|
||||||
collections::BTreeMap,
|
collections::{BTreeMap, HashMap},
|
||||||
fs::{File, OpenOptions},
|
fs::{File, OpenOptions},
|
||||||
io::BufReader,
|
io::BufReader,
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
|
|
@ -8,8 +8,10 @@ use std::{
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use directories::ProjectDirs;
|
use directories::ProjectDirs;
|
||||||
use iced::{
|
use iced::{
|
||||||
font, subscription,
|
font, subscription, theme,
|
||||||
widget::{button, column, component, horizontal_rule, horizontal_space, row, text, text_input},
|
widget::{
|
||||||
|
button, column, component, horizontal_rule, horizontal_space, row, rule, text, text_input,
|
||||||
|
},
|
||||||
window, Application, Command, Event, Length, Renderer, Settings, Theme,
|
window, Application, Command, Event, Length, Renderer, Settings, Theme,
|
||||||
};
|
};
|
||||||
use iced_aw::{card, modal};
|
use iced_aw::{card, modal};
|
||||||
|
|
@ -26,6 +28,7 @@ const TEXT_EMPH1: u16 = 17;
|
||||||
const TEXT_EMPH2: u16 = 20;
|
const TEXT_EMPH2: u16 = 20;
|
||||||
|
|
||||||
const LIST_RULE: u16 = 5;
|
const LIST_RULE: u16 = 5;
|
||||||
|
const SECTION_RULE: u16 = 15;
|
||||||
|
|
||||||
struct AddRecurring<F> {
|
struct AddRecurring<F> {
|
||||||
on_submit: F,
|
on_submit: F,
|
||||||
|
|
@ -233,8 +236,7 @@ where
|
||||||
),
|
),
|
||||||
horizontal_rule(LIST_RULE),
|
horizontal_rule(LIST_RULE),
|
||||||
text(&format!("Total: {} €", self.items.values().sum::<f64>())).size(TEXT_EMPH2)
|
text(&format!("Total: {} €", self.items.values().sum::<f64>())).size(TEXT_EMPH2)
|
||||||
]
|
];
|
||||||
.max_width(500.0);
|
|
||||||
|
|
||||||
let overlay = match state.add_recurring {
|
let overlay = match state.add_recurring {
|
||||||
true => Some(
|
true => Some(
|
||||||
|
|
@ -255,21 +257,190 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ExprInput<'a, F> {
|
||||||
|
expr: &'a str,
|
||||||
|
on_submit: F,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct ExprInputState {
|
||||||
|
state: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
enum ExprInputMessage {
|
||||||
|
Edit(String),
|
||||||
|
Submit,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M, F> iced::widget::Component<M, Renderer> for ExprInput<'_, F>
|
||||||
|
where
|
||||||
|
F: FnMut(String) -> M,
|
||||||
|
{
|
||||||
|
type State = ExprInputState;
|
||||||
|
type Event = ExprInputMessage;
|
||||||
|
|
||||||
|
fn update(&mut self, state: &mut Self::State, event: Self::Event) -> Option<M> {
|
||||||
|
match event {
|
||||||
|
ExprInputMessage::Edit(v) => {
|
||||||
|
state.state = Some(v);
|
||||||
|
}
|
||||||
|
ExprInputMessage::Submit => {
|
||||||
|
return Some((self.on_submit)(
|
||||||
|
state.state.clone().unwrap_or_else(|| self.expr.to_string()),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, state: &Self::State) -> iced_aw::Element<'_, Self::Event, Renderer> {
|
||||||
|
text_input("expr", state.state.as_deref().unwrap_or(self.expr))
|
||||||
|
.on_input(ExprInputMessage::Edit)
|
||||||
|
.on_submit(ExprInputMessage::Submit)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
enum VariableMessage {
|
||||||
|
CloseAdd,
|
||||||
|
Add,
|
||||||
|
EditAdd(String),
|
||||||
|
ExprEdit(String, String),
|
||||||
|
DoAdd,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct VariableState {
|
||||||
|
modal: bool,
|
||||||
|
add_value: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct VariableSpendings<'a, F, G> {
|
||||||
|
items: &'a BTreeMap<String, (String, Option<f64>)>,
|
||||||
|
on_add: F,
|
||||||
|
on_expr_edit: G,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M, F, G> iced::widget::Component<M, Renderer> for VariableSpendings<'_, F, G>
|
||||||
|
where
|
||||||
|
F: FnMut(String) -> M,
|
||||||
|
G: FnMut(String, String) -> M,
|
||||||
|
{
|
||||||
|
type State = VariableState;
|
||||||
|
type Event = VariableMessage;
|
||||||
|
|
||||||
|
fn update(&mut self, state: &mut Self::State, event: Self::Event) -> Option<M> {
|
||||||
|
match event {
|
||||||
|
VariableMessage::CloseAdd => {
|
||||||
|
state.add_value.clear();
|
||||||
|
state.modal = false;
|
||||||
|
}
|
||||||
|
VariableMessage::Add => {
|
||||||
|
state.modal = true;
|
||||||
|
}
|
||||||
|
VariableMessage::EditAdd(v) => {
|
||||||
|
state.add_value = v;
|
||||||
|
}
|
||||||
|
VariableMessage::DoAdd => {
|
||||||
|
state.modal = false;
|
||||||
|
return Some((self.on_add)(std::mem::take(&mut state.add_value)));
|
||||||
|
}
|
||||||
|
VariableMessage::ExprEdit(name, value) => {
|
||||||
|
return Some((self.on_expr_edit)(name, value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unstable_name_collisions)]
|
||||||
|
fn view(&self, state: &Self::State) -> iced_aw::Element<'_, Self::Event, Renderer> {
|
||||||
|
let underlay = column![
|
||||||
|
text("Variable").size(TEXT_H2),
|
||||||
|
button(text("Add")).on_press(VariableMessage::Add),
|
||||||
|
horizontal_rule(LIST_RULE),
|
||||||
|
column(
|
||||||
|
self.items
|
||||||
|
.iter()
|
||||||
|
.map(|(name, (expr, value))| {
|
||||||
|
let row = row![
|
||||||
|
text(name).size(TEXT_EMPH1),
|
||||||
|
component(ExprInput {
|
||||||
|
expr,
|
||||||
|
on_submit: |expr| VariableMessage::ExprEdit(name.clone(), expr)
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
.spacing(5)
|
||||||
|
.align_items(iced::Alignment::Center);
|
||||||
|
|
||||||
|
let mut col = vec![row.into()];
|
||||||
|
if let Some(value) = value {
|
||||||
|
col.push(
|
||||||
|
row![
|
||||||
|
horizontal_space(Length::Fill),
|
||||||
|
text(&format!(" = {value} €")),
|
||||||
|
]
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
column(col).into()
|
||||||
|
})
|
||||||
|
.intersperse_with(|| horizontal_rule(LIST_RULE).into())
|
||||||
|
.collect()
|
||||||
|
),
|
||||||
|
horizontal_rule(LIST_RULE),
|
||||||
|
text(&format!(
|
||||||
|
"Total: {} €",
|
||||||
|
self.items.values().flat_map(|(_, v)| v).sum::<f64>()
|
||||||
|
))
|
||||||
|
.size(TEXT_EMPH2)
|
||||||
|
];
|
||||||
|
|
||||||
|
let overlay = match state.modal {
|
||||||
|
true => Some(
|
||||||
|
card(
|
||||||
|
text("Add variable spending"),
|
||||||
|
text_input("name", &state.add_value)
|
||||||
|
.on_input(VariableMessage::EditAdd)
|
||||||
|
.on_submit(VariableMessage::DoAdd),
|
||||||
|
)
|
||||||
|
.max_width(300.0),
|
||||||
|
),
|
||||||
|
false => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
modal(underlay, overlay)
|
||||||
|
.backdrop(VariableMessage::CloseAdd)
|
||||||
|
.on_esc(VariableMessage::CloseAdd)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
enum Message {
|
enum Message {
|
||||||
Event(Event),
|
Event(Event),
|
||||||
AddRecurring(String, f64),
|
AddRecurring(String, f64),
|
||||||
FontLoaded(Result<(), font::Error>),
|
FontLoaded(Result<(), font::Error>),
|
||||||
|
AddVariable(String),
|
||||||
|
EditVariable(String, String),
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Glaurung {
|
struct Glaurung {
|
||||||
recurring: BTreeMap<String, f64>,
|
recurring: BTreeMap<String, f64>,
|
||||||
|
variable: BTreeMap<String, (String, Option<f64>)>,
|
||||||
save_file: PathBuf,
|
save_file: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Default)]
|
#[derive(Serialize, Deserialize, Default)]
|
||||||
struct SaveFile {
|
struct SaveFile {
|
||||||
|
#[serde(default)]
|
||||||
recurring: BTreeMap<String, f64>,
|
recurring: BTreeMap<String, f64>,
|
||||||
|
#[serde(default)]
|
||||||
|
variable: HashMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
|
@ -288,6 +459,15 @@ impl Application for Glaurung {
|
||||||
(
|
(
|
||||||
Self {
|
Self {
|
||||||
recurring: config.save.recurring,
|
recurring: config.save.recurring,
|
||||||
|
variable: config
|
||||||
|
.save
|
||||||
|
.variable
|
||||||
|
.into_iter()
|
||||||
|
.map(|(k, e)| {
|
||||||
|
let f = calc::calc_parser::calc(&e);
|
||||||
|
(k, (e, f.ok()))
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
save_file: config.save_file,
|
save_file: config.save_file,
|
||||||
},
|
},
|
||||||
Command::batch(vec![
|
Command::batch(vec![
|
||||||
|
|
@ -322,6 +502,10 @@ impl Application for Glaurung {
|
||||||
save_file,
|
save_file,
|
||||||
&SaveFile {
|
&SaveFile {
|
||||||
recurring: std::mem::take(&mut self.recurring),
|
recurring: std::mem::take(&mut self.recurring),
|
||||||
|
variable: std::mem::take(&mut self.variable)
|
||||||
|
.into_iter()
|
||||||
|
.map(|(k, (e, _))| (k, e))
|
||||||
|
.collect(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.expect("could not write save file");
|
.expect("could not write save file");
|
||||||
|
|
@ -329,6 +513,15 @@ impl Application for Glaurung {
|
||||||
return window::close();
|
return window::close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Message::AddVariable(name) => {
|
||||||
|
self.variable.insert(name, ("0".into(), Some(0.)));
|
||||||
|
}
|
||||||
|
Message::EditVariable(v, expr) => {
|
||||||
|
if let Some(entry) = self.variable.get_mut(&v) {
|
||||||
|
entry.1 = calc::calc_parser::calc(&expr).ok();
|
||||||
|
entry.0 = expr;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Command::none()
|
Command::none()
|
||||||
|
|
@ -340,8 +533,18 @@ impl Application for Glaurung {
|
||||||
component(Recurring {
|
component(Recurring {
|
||||||
items: &self.recurring,
|
items: &self.recurring,
|
||||||
on_add: Message::AddRecurring,
|
on_add: Message::AddRecurring,
|
||||||
|
}),
|
||||||
|
horizontal_rule(SECTION_RULE).style(|theme: &Theme| {
|
||||||
|
let def = rule::StyleSheet::appearance(theme, &theme::Rule::Default);
|
||||||
|
rule::Appearance { width: 3, ..def }
|
||||||
|
}),
|
||||||
|
component(VariableSpendings {
|
||||||
|
items: &self.variable,
|
||||||
|
on_add: Message::AddVariable,
|
||||||
|
on_expr_edit: Message::EditVariable,
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
|
.max_width(500)
|
||||||
.padding(5)
|
.padding(5)
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue