fridge/src/main.rs

274 lines
9 KiB
Rust

use std::fs;
use iced::{
Color, Element, Renderer, Subscription, Task, Theme,
alignment::{Horizontal, Vertical},
color, event,
keyboard::{self, Key, key},
theme, widget,
};
mod data;
macro_rules! data {
() => {
dirs::data_dir().unwrap().join("fridge.ron")
};
}
#[derive(Clone, Debug)]
enum Message {
AddCategory,
RemoveCategory {
i_category: usize,
},
CategoryChanged {
i_category: usize,
new_value: String,
},
AddElement {
i_category: usize,
},
RemoveElement {
i_category: usize,
i_element: usize,
},
MoveElement {
i_category: usize,
i_element: usize,
target_category: usize,
},
ElementChanged {
i_category: usize,
i_element: usize,
new_value: String,
},
EditNotes(widget::text_editor::Action),
Event(iced::Event),
}
#[derive(Clone, Debug, Default)]
struct State {
table: data::Table,
content: widget::text_editor::Content,
/// has the application initialized successfully?
ok: bool,
}
impl State {
fn new() -> Self {
let mut state = Self::default();
if let Ok(str) =
fs::read_to_string(data!()).inspect_err(|e| eprintln!("failed to open data file: {e}"))
{
let saved = ron::de::from_str::<data::Saved>(&str).unwrap();
state.table = saved.table;
state.content = widget::text_editor::Content::with_text(&saved.notes);
}
state.ok = true;
state
}
fn view(&self) -> Element<'_, Message, Theme, Renderer> {
widget::column([
widget::button("add category")
.on_press(Message::AddCategory)
.into(),
widget::row(self.table.iter().enumerate().map(|(i_category, category)| {
widget::column([
widget::row([
widget::text_input("category", &category.header)
.on_input(move |new_value| Message::CategoryChanged {
i_category,
new_value,
})
.size(30)
.into(),
widget::button("X")
.style(widget::button::danger)
.on_press(Message::RemoveCategory { i_category })
.height(50)
.into(),
])
.align_y(Vertical::Center)
.into(),
widget::scrollable(
widget::column(category.elements.iter().enumerate().map(
|(i_element, element)| {
widget::row([
widget::text_input("content", element)
.on_input(move |new_value| Message::ElementChanged {
i_category,
i_element,
new_value,
})
.into(),
widget::button("◄")
.on_press_maybe(if i_category > 0 {
Some(Message::MoveElement {
i_category,
i_element,
target_category: i_category - 1,
})
} else {
None
})
.into(),
widget::button("►")
.on_press_maybe(if i_category < self.table.len() - 1 {
Some(Message::MoveElement {
i_category,
i_element,
target_category: i_category + 1,
})
} else {
None
})
.into(),
widget::button("X")
.style(widget::button::warning)
.on_press(Message::RemoveElement {
i_category,
i_element,
})
.into(),
])
.into()
},
))
.align_x(Horizontal::Center)
.push(
widget::button("add element")
.on_press(Message::AddElement { i_category }),
),
)
.into(),
])
.align_x(Horizontal::Center)
.into()
}))
.spacing(15)
.into(),
])
.push(widget::text_editor(&self.content).on_action(Message::EditNotes))
.align_x(Horizontal::Center)
.spacing(15)
.padding(15)
.into()
}
fn update(&mut self, message: Message) -> Task<Message> {
match message {
Message::AddCategory => {
self.table.push(Default::default());
}
Message::RemoveCategory { i_category } => {
self.table.remove(i_category);
}
Message::CategoryChanged {
i_category,
new_value,
} => {
self.table[i_category].header = new_value;
}
Message::AddElement { i_category } => {
self.table[i_category].elements.push(Default::default());
}
Message::RemoveElement {
i_category,
i_element,
} => {
self.table[i_category].elements.remove(i_element);
}
Message::MoveElement {
i_category,
i_element,
target_category,
} => {
let moved_element = self.table[i_category].elements.remove(i_element);
self.table[target_category].elements.reverse();
self.table[target_category].elements.push(moved_element);
self.table[target_category].elements.reverse();
}
Message::ElementChanged {
i_category,
i_element,
new_value,
} => {
self.table[i_category].elements[i_element] = new_value;
}
Message::EditNotes(action) => {
self.content.perform(action);
}
Message::Event(iced::Event::Keyboard(keyboard::Event::KeyPressed {
key,
modifiers,
..
})) => match key.as_ref() {
// input field cycling
Key::Named(key::Named::Tab) => {
if modifiers.shift() {
return widget::operation::focus_previous();
} else {
return widget::operation::focus_next();
}
}
// ignore unused keys
_ => {}
},
// ignore unused events
Message::Event(_) => {}
}
Task::none()
}
fn subscription(&self) -> Subscription<Message> {
event::listen().map(Message::Event)
}
}
impl Drop for State {
fn drop(&mut self) {
if !self.ok {
eprintln!("app failed to start, not saving data");
return;
}
ron::Options::default()
.to_io_writer(
fs::File::create(data!()).unwrap(),
&data::Saved {
table: self.table.clone(),
notes: self.content.text(),
},
)
.unwrap();
eprintln!("successfully saved data");
}
}
fn main() -> Result<(), iced::Error> {
iced::application(State::new, State::update, State::view)
.subscription(State::subscription)
.theme(Theme::custom(
"custom",
theme::Palette {
background: color!(0x080808),
text: Color::WHITE,
primary: color!(0xff00ff),
success: color!(0x00ff00),
warning: color!(0x880000),
danger: color!(0xff0000),
},
))
.run()
}