Glaurung/src/main.rs
2023-11-11 22:24:31 +01:00

280 lines
7.2 KiB
Rust

use std::collections::BTreeMap;
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;
type Element<'a> = iced::Element<'a, Message>;
#[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 {
type Message = Message;
type Theme = Theme;
type Executor = iced::executor::Default;
type Flags = ();
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)
]),
)
}
fn title(&self) -> String {
"Glaurung - Account Manager".into()
}
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()
}
fn theme(&self) -> iced::Theme {
iced::Theme::Dark
}
}
fn main() -> iced::Result {
Glaurung::run(Settings::default())
}