Compare commits

...

3 Commits

7 changed files with 300 additions and 281 deletions

View File

@ -0,0 +1,44 @@
use crate::{
components::{Date, TransitClock},
types::State,
};
use gtk::prelude::*;
#[derive(Clone)]
pub struct ApplicationWindow {
pub window: gtk::ApplicationWindow,
pub date_label: Date,
pub transit_clock: TransitClock,
}
impl ApplicationWindow {
pub fn new(app: &gtk::Application) -> Self {
let window = gtk::ApplicationWindow::new(app);
let date_label = Date::new();
let transit_clock = TransitClock::new();
let layout = gtk::Box::builder()
.orientation(gtk::Orientation::Vertical)
.hexpand(true)
.vexpand(true)
.build();
layout.append(&date_label);
layout.append(&transit_clock);
window.set_child(Some(&layout));
Self {
window,
date_label,
transit_clock,
}
}
pub fn update_state(&self, state: State) {
self.date_label.update_date(state.date);
if let Some(transit) = state.transit {
self.transit_clock.update_transit(transit);
}
}
}

View File

@ -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<RefCell<IFC>>,
label: Rc<RefCell<gtk::Label>>,
}
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<DatePrivate>) @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()
));
}
}

View File

@ -0,0 +1,5 @@
mod date;
pub use date::Date;
mod transit_clock;
pub use transit_clock::TransitClock;

View File

@ -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<RefCell<Option<SunMoon>>>,
}
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<TransitClockPrivate>) @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();
}
}

View File

@ -1,30 +1,29 @@
/*
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate lazy_static;
*/
use chrono::{Datelike, Duration, NaiveTime};
use glib::Object;
use chrono::{Datelike, Local};
use geo_types::{Latitude, Longitude};
use glib::Sender;
use gtk::{prelude::*, subclass::prelude::*, Orientation};
use ifc::IFC;
use std::{cell::RefCell, env, f64::consts::PI, ops::Deref, rc::Rc};
use std::{
env,
sync::{Arc, RwLock},
};
mod app_window;
use app_window::ApplicationWindow;
mod components;
use components::{Date, TransitClock};
mod drawing;
use drawing::{Color, PieChart, Wedge};
/*
use geo_types::{Latitude, Longitude};
*/
mod soluna_client;
use soluna_client::{LunarPhase, SunMoon};
use soluna_client::{SolunaClient, SunMoon};
/*
mod solstices;
use solstices::EVENTS;
*/
mod types;
use types::State;
/*
const EO_TEXT: &'static str = "
@ -59,238 +58,16 @@ summer_solstice = Somera Solstico
autumn_equinox = Aŭtuna Ekvinokso
winter_solstice = Vintra Solstico
";
fn date(fluent: Arc<fluent_ergonomics::FluentErgo>, 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<Tz: TimeZone>(sun_moon: SunMoon<Tz>) -> 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<fluent_ergonomics::FluentErgo>,
event: Option<Event>,
) -> 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<fluent_ergonomics::FluentErgo>,
today: ifc::IFC,
sun_moon: SunMoon,
event: Option<solstices::Event>,
) -> 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();
}
}
}
)
#[derive(Clone, Debug)]
pub enum Message {
Refresh(State),
}
async fn page(
fluent: Arc<fluent_ergonomics::FluentErgo>,
solar_client: Arc<SolunaClient>,
latitude: Latitude,
longitude: Longitude,
) -> Result<Html<String>, 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(Default)]
pub struct DatePrivate {}
#[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<DatePrivate>) @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);
let dt = IFC::from(chrono::Local::now().date_naive().with_year(12023).unwrap());
s.append(&gtk::Label::new(Some(
format!(
"{:?}, {:?} {}, {}",
dt.weekday(),
dt.month(),
dt.day(),
dt.year()
)
.as_ref(),
)));
s
}
}
pub struct TransitClockPrivate {
info: Rc<RefCell<Option<SunMoon>>>,
}
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<TransitClockPrivate>) @extends gtk::DrawingArea, gtk::Widget;
}
impl TransitClock {
pub fn new(sun_moon_info: SunMoon) -> Self {
let s: Self = Object::builder().build();
s.set_width_request(500);
s.set_height_request(500);
*s.imp().info.borrow_mut() = Some(sun_moon_info);
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
}
#[derive(Clone)]
pub struct Core {
tx: Arc<RwLock<Option<Sender<Message>>>>,
}
pub fn main() {
@ -299,45 +76,69 @@ pub fn main() {
.resource_base_path("/com/luminescent-dreams/dashboard")
.build();
app.connect_activate(
// let runtime = runtime.clone();
|app| {
let window = gtk::ApplicationWindow::new(app);
window.present();
let latitude = Latitude::from(41.78);
let longitude = Longitude::from(-71.41);
let layout = gtk::Box::builder()
.orientation(Orientation::Vertical)
.hexpand(true)
.vexpand(true)
.build();
let date = Date::new();
layout.append(&date);
let button = gtk::Button::builder()
.label("Text")
.margin_bottom(8)
.margin_top(8)
.margin_start(8)
.margin_end(8)
.build();
layout.append(&button);
let day = TransitClock::new(SunMoon {
sunrise: NaiveTime::from_hms_opt(7, 0, 0).unwrap(),
sunset: NaiveTime::from_hms_opt(22, 0, 0).unwrap(),
moonrise: NaiveTime::from_hms_opt(15, 0, 0),
moonset: NaiveTime::from_hms_opt(5, 0, 0),
moon_phase: LunarPhase::WaningCrescent,
});
layout.append(&day);
window.set_child(Some(&layout));
},
let runtime = Arc::new(
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap(),
);
let core = Core {
tx: Arc::new(RwLock::new(None)),
};
let app_handle = runtime.spawn({
let core = core.clone();
async move {
let soluna_client = SolunaClient::new();
let transit = soluna_client
.request(latitude, longitude, Local::now())
.await;
let state = State {
date: IFC::from(Local::now().date_naive().with_year(12023).unwrap()),
transit: Some(transit),
};
loop {
if let Some(ref gtk_tx) = *core.tx.read().unwrap() {
let _ = gtk_tx.send(Message::Refresh(state.clone()));
}
std::thread::sleep(std::time::Duration::from_secs(300));
}
}
});
app.connect_activate(move |app| {
let (gtk_tx, gtk_rx) =
gtk::glib::MainContext::channel::<Message>(gtk::glib::PRIORITY_DEFAULT);
*core.tx.write().unwrap() = Some(gtk_tx);
let window = ApplicationWindow::new(app);
window.window.present();
gtk_rx.attach(None, {
let window = window.clone();
move |msg| {
let Message::Refresh(state) = msg;
ApplicationWindow::update_state(&window, state);
Continue(true)
}
});
std::thread::spawn(move || {});
});
let args: Vec<String> = env::args().collect();
ApplicationExtManual::run_with_args(&app, &args);
let _ = runtime.block_on(async { app_handle.await });
/*
let now = Local::now();
let ifc = ifc::IFC::from(now.date_naive().with_year(12023).unwrap());

View File

@ -1,7 +1,7 @@
use chrono;
use chrono::prelude::*;
use lazy_static::lazy_static;
use serde_derive::{Deserialize, Serialize};
use serde_json;
use std::collections::HashMap;
// http://astropixels.com/ephemeris/soleq2001.html
@ -133,7 +133,7 @@ fn parse_events() -> Vec<Option<YearlyEvents>> {
pub struct Solstices(HashMap<i32, YearlyEvents>);
impl Solstices {
pub fn akiru(&self, year: i32) -> Option<YearlyEvents> {
pub fn acquire(&self, year: i32) -> Option<YearlyEvents> {
self.0.get(&year).map(|c| c.clone())
}

8
dashboard/src/types.rs Normal file
View File

@ -0,0 +1,8 @@
use crate::soluna_client::SunMoon;
use ifc::IFC;
#[derive(Clone, Debug)]
pub struct State {
pub date: IFC,
pub transit: Option<SunMoon>,
}