2023-11-11 22:24:31 +01:00
|
|
|
use std::collections::BTreeMap;
|
2023-11-09 23:17:53 +01:00
|
|
|
|
2023-11-11 22:24:31 +01:00
|
|
|
use iced::{
|
|
|
|
|
font,
|
|
|
|
|
widget::{button, column, component, horizontal_rule, row, text, text_input},
|
|
|
|
|
Application, Command, Renderer, Settings, Theme,
|
|
|
|
|
};
|
|
|
|
|
use iced_aw::{card, modal};
|
|
|
|
|
use itertools::Itertools;
|
2023-11-09 23:17:53 +01:00
|
|
|
|
2023-11-11 22:24:31 +01:00
|
|
|
type Element<'a> = iced::Element<'a, Message>;
|
2023-11-09 23:17:53 +01:00
|
|
|
|
2023-11-11 22:24:31 +01:00
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
|
enum Message {
|
|
|
|
|
AddRecurring(String, f64),
|
|
|
|
|
FontLoaded(Result<(), font::Error>),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct Glaurung {
|
|
|
|
|
recurring: BTreeMap<String, f64>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct AddRecurring<F> {
|
|
|
|
|
on_submit: F,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Default)]
|
|
|
|
|
struct AddRecurringState {
|
|
|
|
|
value: String,
|
|
|
|
|
item: String,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
enum AddRecurringEvent {
|
|
|
|
|
SetItem(String),
|
|
|
|
|
SetValue(String),
|
|
|
|
|
SubmitValue,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<F> AddRecurring<F> {
|
|
|
|
|
fn new(on_submit: F) -> Self {
|
|
|
|
|
Self { on_submit }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<M, F> iced::widget::Component<M, Renderer> for AddRecurring<F>
|
|
|
|
|
where
|
|
|
|
|
F: FnMut(String, f64) -> M,
|
|
|
|
|
{
|
|
|
|
|
type State = AddRecurringState;
|
|
|
|
|
type Event = AddRecurringEvent;
|
|
|
|
|
|
|
|
|
|
fn update(&mut self, state: &mut Self::State, event: Self::Event) -> Option<M> {
|
|
|
|
|
match event {
|
|
|
|
|
AddRecurringEvent::SetItem(i) => state.item = i,
|
|
|
|
|
AddRecurringEvent::SetValue(v) => state.value = v,
|
|
|
|
|
AddRecurringEvent::SubmitValue => {
|
|
|
|
|
if let Ok(v) = state.value.parse() {
|
|
|
|
|
state.value.clear();
|
|
|
|
|
return Some((self.on_submit)(std::mem::take(&mut state.item), v));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn view(&self, state: &Self::State) -> iced::Element<'_, Self::Event, Renderer> {
|
|
|
|
|
column![
|
|
|
|
|
text_input("item", &state.item).on_input(AddRecurringEvent::SetItem),
|
|
|
|
|
text_input("value", &state.value)
|
|
|
|
|
.on_input(AddRecurringEvent::SetValue)
|
|
|
|
|
.on_submit(AddRecurringEvent::SubmitValue)
|
|
|
|
|
]
|
|
|
|
|
.into()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct EditRecurring<'a, F> {
|
|
|
|
|
value: f64,
|
|
|
|
|
name: &'a str,
|
|
|
|
|
on_submit: F,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Default)]
|
|
|
|
|
struct EditRecurringState {
|
|
|
|
|
edit: String,
|
|
|
|
|
modal: bool,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
|
enum EditRecurringEvent {
|
|
|
|
|
Edit(String),
|
|
|
|
|
Open,
|
|
|
|
|
Close,
|
|
|
|
|
Submit,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<M, F> iced::widget::Component<M, Renderer> for EditRecurring<'_, F>
|
|
|
|
|
where
|
|
|
|
|
F: FnMut(f64) -> M,
|
|
|
|
|
{
|
|
|
|
|
type State = EditRecurringState;
|
|
|
|
|
type Event = EditRecurringEvent;
|
|
|
|
|
|
|
|
|
|
fn update(&mut self, state: &mut Self::State, event: Self::Event) -> Option<M> {
|
|
|
|
|
match event {
|
|
|
|
|
EditRecurringEvent::Edit(_) => todo!(),
|
|
|
|
|
EditRecurringEvent::Submit => {
|
|
|
|
|
if let Ok(v) = state.edit.parse() {
|
|
|
|
|
state.edit.clear();
|
|
|
|
|
state.modal = false;
|
|
|
|
|
return Some((self.on_submit)(v));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
EditRecurringEvent::Open => {
|
|
|
|
|
state.modal = true;
|
|
|
|
|
}
|
|
|
|
|
EditRecurringEvent::Close => {
|
|
|
|
|
state.modal = false;
|
|
|
|
|
state.edit.clear();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn view(&self, state: &Self::State) -> iced_aw::Element<'_, Self::Event, Renderer> {
|
|
|
|
|
let underlay = button(text("Edit")).on_press(EditRecurringEvent::Open);
|
|
|
|
|
let overlay = match state.modal {
|
|
|
|
|
true => Some(
|
|
|
|
|
card(text(&format!("Edit {}", self.name)), text("todo"))
|
|
|
|
|
.max_width(300.0)
|
|
|
|
|
.on_close(EditRecurringEvent::Close),
|
|
|
|
|
),
|
|
|
|
|
false => None,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
modal(underlay, overlay)
|
|
|
|
|
.backdrop(EditRecurringEvent::Close)
|
|
|
|
|
.on_esc(EditRecurringEvent::Close)
|
|
|
|
|
.into()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
|
enum RecurringMessage {
|
|
|
|
|
CloseAdd,
|
|
|
|
|
Add,
|
|
|
|
|
DoAdd(String, f64),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Default)]
|
|
|
|
|
struct RecurringState {
|
|
|
|
|
add_recurring: bool,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct Recurring<'a, F> {
|
|
|
|
|
items: &'a BTreeMap<String, f64>,
|
|
|
|
|
on_add: F,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<M, F> iced::widget::Component<M, Renderer> for Recurring<'_, F>
|
|
|
|
|
where
|
|
|
|
|
F: FnMut(String, f64) -> M,
|
|
|
|
|
{
|
|
|
|
|
type State = RecurringState;
|
|
|
|
|
type Event = RecurringMessage;
|
|
|
|
|
|
|
|
|
|
fn update(&mut self, state: &mut Self::State, event: Self::Event) -> Option<M> {
|
|
|
|
|
match event {
|
|
|
|
|
RecurringMessage::Add => state.add_recurring = true,
|
|
|
|
|
RecurringMessage::CloseAdd => {
|
|
|
|
|
state.add_recurring = false;
|
|
|
|
|
}
|
|
|
|
|
RecurringMessage::DoAdd(item, value) => {
|
|
|
|
|
state.add_recurring = false;
|
|
|
|
|
return Some((self.on_add)(item, value));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn view(&self, state: &Self::State) -> iced_aw::Element<'_, Self::Event, Renderer> {
|
|
|
|
|
let underlay = column![
|
|
|
|
|
text("Recurring").size(20),
|
|
|
|
|
button(text("Add")).on_press(RecurringMessage::Add),
|
|
|
|
|
horizontal_rule(5),
|
|
|
|
|
column(
|
|
|
|
|
self.items
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|(name, &value)| row![
|
|
|
|
|
text(name).size(17),
|
|
|
|
|
text(&format!("{value} €")),
|
|
|
|
|
component(EditRecurring {
|
|
|
|
|
value,
|
|
|
|
|
name,
|
|
|
|
|
on_submit: |v| RecurringMessage::DoAdd(name.to_string(), v)
|
|
|
|
|
})
|
|
|
|
|
]
|
|
|
|
|
.spacing(5)
|
|
|
|
|
.align_items(iced::Alignment::Center)
|
|
|
|
|
.into())
|
|
|
|
|
.intersperse_with(|| horizontal_rule(5).into())
|
|
|
|
|
.collect()
|
|
|
|
|
),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
let overlay = match state.add_recurring {
|
|
|
|
|
true => Some(
|
|
|
|
|
card(
|
|
|
|
|
text("Add a recurring spending"),
|
|
|
|
|
component(AddRecurring::new(RecurringMessage::DoAdd)),
|
|
|
|
|
)
|
|
|
|
|
.on_close(RecurringMessage::CloseAdd)
|
|
|
|
|
.max_width(300.0),
|
|
|
|
|
),
|
|
|
|
|
false => None,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
modal(underlay, overlay)
|
|
|
|
|
.backdrop(RecurringMessage::CloseAdd)
|
|
|
|
|
.on_esc(RecurringMessage::CloseAdd)
|
|
|
|
|
.into()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Application for Glaurung {
|
2023-11-09 23:17:53 +01:00
|
|
|
type Message = Message;
|
2023-11-11 22:24:31 +01:00
|
|
|
type Theme = Theme;
|
|
|
|
|
type Executor = iced::executor::Default;
|
|
|
|
|
type Flags = ();
|
2023-11-09 23:17:53 +01:00
|
|
|
|
2023-11-11 22:24:31 +01:00
|
|
|
fn new(_flags: Self::Flags) -> (Self, Command<Message>) {
|
|
|
|
|
(
|
|
|
|
|
Self {
|
|
|
|
|
recurring: BTreeMap::new(),
|
|
|
|
|
},
|
|
|
|
|
Command::batch(vec![
|
|
|
|
|
font::load(iced_aw::graphics::icons::ICON_FONT_BYTES).map(Message::FontLoaded)
|
|
|
|
|
]),
|
|
|
|
|
)
|
2023-11-09 23:17:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn title(&self) -> String {
|
|
|
|
|
"Glaurung - Account Manager".into()
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-11 22:24:31 +01:00
|
|
|
fn update(&mut self, message: Self::Message) -> Command<Message> {
|
|
|
|
|
match message {
|
|
|
|
|
Message::AddRecurring(name, value) => {
|
|
|
|
|
self.recurring.insert(name, value);
|
|
|
|
|
}
|
|
|
|
|
Message::FontLoaded(r) => r.expect("could not load font"),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Command::none()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn view(&self) -> Element {
|
|
|
|
|
column![
|
|
|
|
|
text("Spendings").size(30),
|
|
|
|
|
component(Recurring {
|
|
|
|
|
items: &self.recurring,
|
|
|
|
|
on_add: Message::AddRecurring,
|
|
|
|
|
})
|
|
|
|
|
]
|
|
|
|
|
.padding(5)
|
|
|
|
|
.into()
|
2023-11-09 23:17:53 +01:00
|
|
|
}
|
|
|
|
|
|
2023-11-11 22:24:31 +01:00
|
|
|
fn theme(&self) -> iced::Theme {
|
|
|
|
|
iced::Theme::Dark
|
2023-11-09 23:17:53 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn main() -> iced::Result {
|
|
|
|
|
Glaurung::run(Settings::default())
|
2023-11-09 22:37:24 +01:00
|
|
|
}
|