274 lines
9 KiB
Rust
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()
|
|
}
|