diff --git a/dashboard/src/components/date.rs b/dashboard/src/components/date.rs new file mode 100644 index 0000000..3df0e39 --- /dev/null +++ b/dashboard/src/components/date.rs @@ -0,0 +1,69 @@ +use chrono::Datelike; +use glib::Object; +use gtk::{prelude::*, subclass::prelude::*}; +use ifc::IFC; +use std::{cell::RefCell, rc::Rc}; + +pub struct DatePrivate { + date: Rc>, + label: Rc>, +} + +impl Default for DatePrivate { + fn default() -> Self { + Self { + date: Rc::new(RefCell::new(IFC::from( + chrono::Local::now().date_naive().with_year(12023).unwrap(), + ))), + label: Rc::new(RefCell::new(gtk::Label::new(None))), + } + } +} + +#[glib::object_subclass] +impl ObjectSubclass for DatePrivate { + const NAME: &'static str = "Date"; + type Type = Date; + type ParentType = gtk::Box; +} + +impl ObjectImpl for DatePrivate {} +impl WidgetImpl for DatePrivate {} +impl BoxImpl for DatePrivate {} + +glib::wrapper! { + pub struct Date(ObjectSubclass) @extends gtk::Box, gtk::Widget; +} + +impl Date { + pub fn new() -> Self { + let s: Self = Object::builder().build(); + s.add_css_class("card"); + s.add_css_class("activatable"); + s.set_margin_bottom(8); + s.set_margin_top(8); + s.set_margin_start(8); + s.set_margin_end(8); + + s.append(&*s.imp().label.borrow()); + + s.redraw(); + s + } + + pub fn update_date(&self, date: IFC) { + *self.imp().date.borrow_mut() = date; + self.redraw(); + } + + fn redraw(&self) { + let date = self.imp().date.borrow().clone(); + self.imp().label.borrow_mut().set_text(&format!( + "{:?}, {:?} {}, {}", + date.weekday(), + date.month(), + date.day(), + date.year() + )); + } +} diff --git a/dashboard/src/components/mod.rs b/dashboard/src/components/mod.rs new file mode 100644 index 0000000..a011020 --- /dev/null +++ b/dashboard/src/components/mod.rs @@ -0,0 +1,5 @@ +mod date; +pub use date::Date; + +mod transit_clock; +pub use transit_clock::TransitClock; diff --git a/dashboard/src/components/transit_clock.rs b/dashboard/src/components/transit_clock.rs new file mode 100644 index 0000000..24a0d5b --- /dev/null +++ b/dashboard/src/components/transit_clock.rs @@ -0,0 +1,92 @@ +use crate::{ + drawing::{Color, PieChart, Wedge}, + soluna_client::SunMoon, +}; +use chrono::{Duration, NaiveTime}; +use glib::Object; +use gtk::{prelude::*, subclass::prelude::*}; +use std::{cell::RefCell, f64::consts::PI, rc::Rc}; + +pub struct TransitClockPrivate { + info: Rc>>, +} + +impl Default for TransitClockPrivate { + fn default() -> Self { + Self { + info: Rc::new(RefCell::new(None)), + } + } +} + +#[glib::object_subclass] +impl ObjectSubclass for TransitClockPrivate { + const NAME: &'static str = "TransitClock"; + type Type = TransitClock; + type ParentType = gtk::DrawingArea; +} + +impl ObjectImpl for TransitClockPrivate {} +impl WidgetImpl for TransitClockPrivate {} +impl DrawingAreaImpl for TransitClockPrivate {} + +glib::wrapper! { + pub struct TransitClock(ObjectSubclass) @extends gtk::DrawingArea, gtk::Widget; +} + +impl TransitClock { + pub fn new() -> Self { + let s: Self = Object::builder().build(); + s.set_width_request(500); + s.set_height_request(500); + + s.set_draw_func({ + let s = s.clone(); + move |_, context, width, height| { + let center_x = width as f64 / 2.; + let center_y = height as f64 / 2.; + let radius = width.min(height) as f64 / 2. * 0.9; + if let Some(ref info) = *s.imp().info.borrow() { + let full_day = Duration::days(1).num_seconds() as f64; + let sunrise = info.sunrise - NaiveTime::from_hms_opt(0, 0, 0).unwrap(); + let sunset = info.sunset - NaiveTime::from_hms_opt(0, 0, 0).unwrap(); + + PieChart::new() + .center(center_x, center_y) + .radius(radius) + .rotation(-PI / 2.) + .wedges( + vec![Wedge { + start_angle: (PI * 2.) * sunset.num_seconds() as f64 / full_day, + end_angle: (PI * 2.) * sunrise.num_seconds() as f64 / full_day, + color: Color { + r: 0.1, + g: 0.1, + b: 0.8, + }, + }] + .into_iter(), + ) + .draw(context); + + (0..24).for_each(|tick| { + context.set_source_rgb(0., 0., 0.); + context.translate(center_x, center_y); + context.rotate(tick as f64 * (PI / 12.)); + context.move_to(radius - 5., 0.); + context.line_to(radius - 10., 0.); + let _ = context.stroke(); + context.identity_matrix(); + }); + } + } + }); + + s + } + + pub fn update_transit(&self, transit_info: SunMoon) { + *self.imp().info.borrow_mut() = Some(transit_info); + self.queue_draw(); + } +} diff --git a/dashboard/src/main.rs b/dashboard/src/main.rs index 4207ecf..3ed36a5 100644 --- a/dashboard/src/main.rs +++ b/dashboard/src/main.rs @@ -1,30 +1,17 @@ -/* -#[macro_use] -extern crate serde_derive; -#[macro_use] -extern crate lazy_static; -*/ - -use chrono::{Datelike, Duration, Local, NaiveTime}; +use chrono::{Datelike, Local}; use geo_types::{Latitude, Longitude}; -use glib::{Object, Sender}; +use glib::Sender; use gtk::{prelude::*, subclass::prelude::*, Orientation}; use ifc::IFC; use std::{ - cell::RefCell, env, - f64::consts::PI, - ops::Deref, - rc::Rc, sync::{Arc, RwLock}, }; -mod drawing; -use drawing::{Color, PieChart, Wedge}; +mod components; +use components::{Date, TransitClock}; -/* -use geo_types::{Latitude, Longitude}; -*/ +mod drawing; mod soluna_client; use soluna_client::{SolunaClient, SunMoon}; @@ -34,7 +21,6 @@ mod solstices; use solstices::EVENTS; */ -/* const EO_TEXT: &'static str = " day = {$day -> *[Sunday] Dimanĉo @@ -68,115 +54,6 @@ autumn_equinox = Aŭtuna Ekvinokso winter_solstice = Vintra Solstico "; -fn date(fluent: Arc, today: ifc::IFC) -> impl Render { - let mut day_args = FluentArgs::new(); - day_args.set( - "day", - FluentValue::String(Cow::Owned(String::from(today.weekday_ifc()))), - ); - let tago = fluent.tr("tago", Some(&day_args)).unwrap(); - - let mut month_args = FluentArgs::new(); - month_args.set( - "month", - FluentValue::String(Cow::Owned(String::from(today.month_ifc()))), - ); - let monato = fluent.tr("month", Some(&month_args)).unwrap(); - - owned_html! {p : format!("{}, {} {}, {}", tago, monato, today.day(), today.year());} -} -*/ - -/* -fn soluna_desegno(sun_moon: SunMoon) -> impl Render { - owned_html! { - table { - tr { - th : "Sunleviĝo"; - th : "Sunfalo"; - th : "Lunleviĝo"; - th : "Lunfalo"; - } - tr { - td : format!("{}", sun_moon.sunrise.format("%H:%M")); - td : format!("{}", sun_moon.sunset.format("%H:%M")); - td : sun_moon.moonrise.map(|v| format!("{}", v.format("%H:%M"))).unwrap_or("".to_string()); - td : sun_moon.moonset.map(|v| format!("{}", v.format("%H:%M"))).unwrap_or("".to_string()); - td : format!("{:?}", sun_moon.moon_phase); - } - } - } -} -*/ - -/* -fn astronomia_eventa_desegno( - fluent: Arc, - event: Option, -) -> impl Render { - let s = match event { - None => "".to_owned(), - Some(event) => { - let eventa_str = match event { - Event::SpringEquinox(_) => fluent.tr("spring_equinox", None), - Event::SummerSolstice(_) => fluent.tr("summer_solstice", None), - Event::AutumnEquinox(_) => fluent.tr("autumn_equinox", None), - Event::WinterSolstice(_) => fluent.tr("winter_solstice", None), - } - .unwrap(); - format!("{} {}", eventa_str, event.date().format("%Y-%m-%d")) - } - }; - owned_html! { - div : s.clone(); - } -} -*/ - -/* -fn page_template( - fluent: Arc, - today: ifc::IFC, - sun_moon: SunMoon, - event: Option, -) -> String { - format!( - "{}", - html! { - : doctype::HTML; - html { - head { - title: "Superrigardo"; - } - body { - h1(id="heading", class="title") : date(fluent.clone(), today); - div : soluna_desegno(sun_moon.clone()); - div : astronomia_eventa_desegno(fluent.clone(), event); - // div : cirklo(); - } - } - } - ) -} - -async fn page( - fluent: Arc, - solar_client: Arc, - latitude: Latitude, - longitude: Longitude, -) -> Result, Rejection> { - let now = Utc::now(); - let d = ifc::IFC::from(now.with_timezone(&New_York).date()); - let sun_moon = solar_client - .request(latitude, longitude, now.with_timezone(&New_York).date()) - .await; - let next_event = EVENTS.next_event(now); - Ok(warp::reply::html(page_template( - fluent, d, sun_moon, next_event, - ))) -} -*/ - #[derive(Clone, Debug)] pub enum Message { Refresh(State), @@ -193,154 +70,6 @@ pub struct Core { tx: Arc>>>, } -pub struct DatePrivate { - date: Rc>, - label: Rc>, -} - -impl Default for DatePrivate { - fn default() -> Self { - Self { - date: Rc::new(RefCell::new(IFC::from( - chrono::Local::now().date_naive().with_year(12023).unwrap(), - ))), - label: Rc::new(RefCell::new(gtk::Label::new(None))), - } - } -} - -#[glib::object_subclass] -impl ObjectSubclass for DatePrivate { - const NAME: &'static str = "Date"; - type Type = Date; - type ParentType = gtk::Box; -} - -impl ObjectImpl for DatePrivate {} -impl WidgetImpl for DatePrivate {} -impl BoxImpl for DatePrivate {} - -glib::wrapper! { - pub struct Date(ObjectSubclass) @extends gtk::Box, gtk::Widget; -} - -impl Date { - fn new() -> Self { - let s: Self = Object::builder().build(); - s.add_css_class("card"); - s.add_css_class("activatable"); - s.set_margin_bottom(8); - s.set_margin_top(8); - s.set_margin_start(8); - s.set_margin_end(8); - - s.append(&*s.imp().label.borrow()); - - s.redraw(); - s - } - - fn update_date(&self, date: IFC) { - *self.imp().date.borrow_mut() = date; - self.redraw(); - } - - fn redraw(&self) { - let date = self.imp().date.borrow().clone(); - self.imp().label.borrow_mut().set_text(&format!( - "{:?}, {:?} {}, {}", - date.weekday(), - date.month(), - date.day(), - date.year() - )); - } -} - -pub struct TransitClockPrivate { - info: Rc>>, -} - -impl Default for TransitClockPrivate { - fn default() -> Self { - Self { - info: Rc::new(RefCell::new(None)), - } - } -} - -#[glib::object_subclass] -impl ObjectSubclass for TransitClockPrivate { - const NAME: &'static str = "TransitClock"; - type Type = TransitClock; - type ParentType = gtk::DrawingArea; -} - -impl ObjectImpl for TransitClockPrivate {} -impl WidgetImpl for TransitClockPrivate {} -impl DrawingAreaImpl for TransitClockPrivate {} - -glib::wrapper! { - pub struct TransitClock(ObjectSubclass) @extends gtk::DrawingArea, gtk::Widget; -} - -impl TransitClock { - pub fn new() -> Self { - let s: Self = Object::builder().build(); - s.set_width_request(500); - s.set_height_request(500); - - s.set_draw_func({ - let s = s.clone(); - move |_, context, width, height| { - let center_x = width as f64 / 2.; - let center_y = height as f64 / 2.; - let radius = width.min(height) as f64 / 2. * 0.9; - if let Some(ref info) = *s.imp().info.borrow() { - let full_day = Duration::days(1).num_seconds() as f64; - let sunrise = info.sunrise - NaiveTime::from_hms_opt(0, 0, 0).unwrap(); - let sunset = info.sunset - NaiveTime::from_hms_opt(0, 0, 0).unwrap(); - - PieChart::new() - .center(center_x, center_y) - .radius(radius) - .rotation(-PI / 2.) - .wedges( - vec![Wedge { - start_angle: (PI * 2.) * sunset.num_seconds() as f64 / full_day, - end_angle: (PI * 2.) * sunrise.num_seconds() as f64 / full_day, - color: Color { - r: 0.1, - g: 0.1, - b: 0.8, - }, - }] - .into_iter(), - ) - .draw(context); - - (0..24).for_each(|tick| { - context.set_source_rgb(0., 0., 0.); - context.translate(center_x, center_y); - context.rotate(tick as f64 * (PI / 12.)); - context.move_to(radius - 5., 0.); - context.line_to(radius - 10., 0.); - let _ = context.stroke(); - context.identity_matrix(); - }); - } - } - }); - - s - } - - pub fn update_transit(&self, transit_info: SunMoon) { - *self.imp().info.borrow_mut() = Some(transit_info); - self.queue_draw(); - } -} - pub fn main() { let app = gtk::Application::builder() .application_id("com.luminescent-dreams.dashboard")