// Prevent console window in addition to Slint window in Windows release builds when, e.g., starting the app via file manager. Ignored on other platforms. #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] use std::{error::Error, rc::Rc, sync::{Arc, Mutex}}; use aliveline::{color_id_from_name, config::Config, load_config, log::{Event, Log}}; use chrono::{Datelike, Timelike}; use slint::{Color, Model, ModelRc, SharedString, ToSharedString, VecModel, Weak}; use toml::value::{Date as TomlDate, Time}; slint::include_modules!(); impl From for TimelineEvent { fn from(event: Event) -> Self { let start = (event.start.hour as i32) * 3600 + (event.start.minute as i32) * 60 + (event.start.second as i32); let end = (event.end.hour as i32) * 3600 + (event.end.minute as i32) * 60 + (event.end.second as i32); TimelineEvent { start, duration: end - start, label: event.name.to_shared_string(), finished: event.finished, color_id: color_id_from_name(event.name) } } } impl From for Event { fn from(event: TimelineEvent) -> Self { let start = Time { hour: (event.start / 3600) as u8, minute: ((event.start % 3600) / 60) as u8, second: (event.start % 60) as u8, nanosecond: 0 }; let endsecs = event.start + event.duration; let end = Time { hour: (endsecs / 3600) as u8, minute: ((endsecs % 3600) / 60) as u8, second: (endsecs % 60) as u8, nanosecond: 0 }; Event { start, end, name: event.label.to_string(), finished: event.finished } } } fn load_log(ui_weak: Weak, log: Arc>) { let ui = ui_weak.unwrap(); let log_guard = log.lock().expect("Log shouldn't be used twice"); let events: Vec = (*log_guard) .events .iter() .map(|event| TimelineEvent::from((*event).clone())) .collect(); let in_progress = events.iter().any(|event| !event.finished); let model: ModelRc = Rc::new(VecModel::from(events)).into(); ui.set_record_events(model); ui.set_in_progress(in_progress); ui.invoke_get_previous_event(); } fn load_colors(ui_weak: Weak, config: Arc) { let ui = ui_weak.unwrap(); let pal = ui.global::(); pal.set_background(Color::from_argb_encoded(config.colors.background)); pal.set_timeline(Color::from_argb_encoded(config.colors.timeline)); pal.set_background_text(Color::from_argb_encoded(config.colors.background_text)); // This looks like war crime let event_colors_rc: ModelRc = Rc::new(VecModel::from( config.event_colors.iter() .map(|value| Color::from_argb_encoded(*value)).collect::>() )).into(); pal.set_event_colors(event_colors_rc); let event_text_rc: ModelRc = Rc::new(VecModel::from( config.text_colors.iter() .map(|value| Color::from_argb_encoded(*value)).collect::>() )).into(); pal.set_event_text(event_text_rc); } fn main() -> Result<(), Box> { let ui = AppWindow::new()?; let now = chrono::Local::now(); let offset = now.hour() * 3600 + now.minute() * 60 + now.second(); let date: TomlDate = TomlDate { day: now.day() as u8, month: now.month() as u8, year: now.year() as u16 }; let config: Arc = Arc::new(load_config()); let writing_log: Arc> = Arc::new(Mutex::new(Log::load_from(&config, date))); let ui_weak = ui.as_weak(); let log = writing_log.clone(); load_log(ui_weak, log); let ui_weak = ui.as_weak(); let config_arc = config.clone(); load_colors(ui_weak, config_arc); ui.invoke_update_record_offset(offset as i32); ui.on_fetch_log({ let config = config.clone(); let ui_weak = ui.as_weak(); move |year: i32, month: i32, day: i32| { let ui = ui_weak.unwrap(); let date = TomlDate { year: year as u16, month: month as u8, day: day as u8 }; let events: Vec = Log::load_from(&config, date) .events .iter() .map(|event| TimelineEvent::from((*event).clone())) .collect(); let model: ModelRc = Rc::new(VecModel::from(events)).into(); ui.set_review_events(model); } }); ui.on_save_log({ let config = config.clone(); let log = writing_log.clone(); move || { let log_guard = log.lock().expect("Log shouldn't be used twice"); if let Err(error) = (*log_guard).save(&config) { eprintln!("Error occured while saving log: {error}"); } } }); ui.on_update_visible_time({ let ui_weak = ui.as_weak(); move |is_record: bool, hours_string: SharedString| { let ui = ui_weak.unwrap(); let hours = hours_string.split(' ') .next() .map(|h| h.parse::().unwrap()) .unwrap(); if is_record { ui.set_record_visible_time(hours * 3600); } else { ui.set_review_visible_time(hours * 3600); } } }); ui.on_start_new_event({ let ui_weak = ui.as_weak(); let log = writing_log.clone(); move |event_name: SharedString| { let ui = ui_weak.unwrap(); let events_rc = ui.get_record_events(); let events = events_rc.as_any() .downcast_ref::>() .unwrap(); let offset = ui.get_record_offset(); let event = TimelineEvent { duration: 0, finished: false, label: event_name.clone(), start: offset, color_id: color_id_from_name(event_name.to_string()) }; { let mut log_guard = log.lock().expect("Log shouldn't be used twice"); (*log_guard).events.push(Event::from(event.clone())); } ui.invoke_save_log(); events.push(event); } }); ui.on_stop_event({ let ui_weak = ui.as_weak(); let log = writing_log.clone(); move || { let ui = ui_weak.unwrap(); let events_rc = ui.get_record_events(); let events = events_rc.as_any() .downcast_ref::>() .unwrap(); let offset = ui.get_record_offset(); let event_id = events.iter() .position(|data| !data.finished) .unwrap(); let event = events.row_data(event_id) .expect("stop-event called without unfinished events"); let new_event = TimelineEvent { duration: offset - event.start, finished: true, label: event.label.clone(), start: event.start, color_id: color_id_from_name(event.label.to_string()) }; { let mut log_guard = log.lock().expect("Log shouldn't be used twice"); log_guard.events.push(Event::from(new_event.clone())); let index = log_guard.events.iter().position(|data| !data.finished).unwrap(); log_guard.events.swap_remove(index); } ui.invoke_save_log(); events.set_row_data(event_id, new_event); } }); ui.on_chain_event({ let ui_weak = ui.as_weak(); move |event_name: SharedString| { let ui = ui_weak.unwrap(); ui.invoke_stop_event(); ui.invoke_start_new_event(event_name); } }); ui.on_get_previous_event({ let ui_weak = ui.as_weak(); let log = writing_log.clone(); move || { let ui = ui_weak.unwrap(); let prev_event_name = { let log_guard = log.lock().expect("Log shouldn't be used twice"); match log_guard.events.len().checked_sub(2) { Some(prev_index) => { let prev_event = log_guard.events .get(prev_index) .expect("Index is already checked") .name .clone(); Some(prev_event) }, None => None, } }; ui.set_previous_event_name(prev_event_name.unwrap_or_default().to_shared_string()); } }); ui.on_new_day_started({ let ui_weak = ui.as_weak(); let log = writing_log.clone(); move || { let ui = ui_weak.unwrap(); let new_event: Option = { let log_guard = log.lock().expect("Log shouldn't be used twice"); let maybe_unfinished_event = log_guard.events.iter().find(|event| !event.finished); match maybe_unfinished_event { Some(unfinished_event) => Some(Event::new(unfinished_event.name.clone(), 0, 0, false)), _ => None } }; ui.invoke_stop_event(); { let mut log_guard = log.lock().expect("Log shouldn't be used twice"); log_guard.events.clear(); let now = chrono::Local::now(); let date = TomlDate { year: now.year() as u16, month: now.month() as u8, day: now.day() as u8 }; log_guard.date = date; log_guard.events.clear(); if let Some(event) = new_event { log_guard.events.push(event); } } load_log(ui.as_weak(), log.clone()); ui.invoke_save_log(); } }); ui.invoke_get_previous_event(); ui.run()?; Ok(()) }