From 36b0238662a851179ffd30600e9d65c46f2cc5f7 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Tue, 8 Aug 2023 17:25:21 -0400 Subject: [PATCH] Write up a Pie Chart widget --- Cargo.lock | 34 ++++++++ dashboard/Cargo.toml | 1 + dashboard/src/main.rs | 126 +++++++++++++++++++++++------- dashboard/src/ui/mod.rs | 2 + dashboard/src/ui/pie_chart.rs | 141 ++++++++++++++++++++++++++++++++++ flake.lock | 8 +- flake.nix | 2 +- 7 files changed, 280 insertions(+), 34 deletions(-) create mode 100644 dashboard/src/ui/mod.rs create mode 100644 dashboard/src/ui/pie_chart.rs diff --git a/Cargo.lock b/Cargo.lock index 0679578..7c88714 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -341,6 +341,7 @@ dependencies = [ "gtk4", "ifc", "lazy_static", + "libadwaita", "memorycache", "reqwest", "serde", @@ -1263,6 +1264,39 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" +[[package]] +name = "libadwaita" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ab9c0843f9f23ff25634df2743690c3a1faffe0a190e60c490878517eb81abf" +dependencies = [ + "bitflags 1.3.2", + "gdk-pixbuf", + "gdk4", + "gio", + "glib", + "gtk4", + "libadwaita-sys", + "libc", + "pango", +] + +[[package]] +name = "libadwaita-sys" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4231cb2499a9f0c4cdfa4885414b33e39901ddcac61150bc0bb4ff8a57ede404" +dependencies = [ + "gdk4-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "gtk4-sys", + "libc", + "pango-sys", + "system-deps", +] + [[package]] name = "libc" version = "0.2.147" diff --git a/dashboard/Cargo.toml b/dashboard/Cargo.toml index a50b084..f4b1434 100644 --- a/dashboard/Cargo.toml +++ b/dashboard/Cargo.toml @@ -6,6 +6,7 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +adw = { package = "libadwaita", version = "0.4", features = ["v1_3"] } cairo-rs = { version = "0.17" } chrono = { version = "0.4", features = ["serde"] } fluent-ergonomics = { path = "../fluent-ergonomics/" } diff --git a/dashboard/src/main.rs b/dashboard/src/main.rs index e945f3c..2afaeea 100644 --- a/dashboard/src/main.rs +++ b/dashboard/src/main.rs @@ -9,7 +9,10 @@ use chrono::{Datelike, Duration, NaiveTime}; use glib::Object; use gtk::{prelude::*, subclass::prelude::*, Orientation}; use ifc::IFC; -use std::{cell::RefCell, f64::consts::PI, ops::Deref, rc::Rc}; +use std::{cell::RefCell, env, f64::consts::PI, ops::Deref, rc::Rc}; + +mod ui; +use ui::{Color, PieChart, Wedge}; /* use geo_types::{Latitude, Longitude}; @@ -187,6 +190,12 @@ glib::wrapper! { 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(>k::Label::new(Some( @@ -203,56 +212,79 @@ impl Date { } } -#[derive(Default)] -pub struct ClockPrivate { +pub struct TransitClockPrivate { info: Rc>>, + chart: Rc>, +} + +impl Default for TransitClockPrivate { + fn default() -> Self { + Self { + info: Rc::new(RefCell::new(None)), + chart: Rc::new(RefCell::new(PieChart::builder().rotation(-PI / 2.).build())), + } + } } #[glib::object_subclass] -impl ObjectSubclass for ClockPrivate { - const NAME: &'static str = "Clock"; - type Type = Clock; - type ParentType = gtk::DrawingArea; +impl ObjectSubclass for TransitClockPrivate { + const NAME: &'static str = "TransitClock"; + type Type = TransitClock; + type ParentType = gtk::Box; } -impl ObjectImpl for ClockPrivate {} -impl WidgetImpl for ClockPrivate {} -impl DrawingAreaImpl for ClockPrivate {} +impl ObjectImpl for TransitClockPrivate {} +impl WidgetImpl for TransitClockPrivate {} +impl BoxImpl for TransitClockPrivate {} glib::wrapper! { - pub struct Clock(ObjectSubclass) @extends gtk::DrawingArea, gtk::Widget; + pub struct TransitClock(ObjectSubclass) @extends gtk::Box, gtk::Widget; } -impl Clock { +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); + { + let mut chart = s.imp().chart.borrow_mut(); + s.append(&*chart); + { + let full_day = Duration::days(1).num_seconds() as f64; + let sunrise = sun_moon_info.sunrise - NaiveTime::from_hms_opt(0, 0, 0).unwrap(); + let sunset = sun_moon_info.sunset - NaiveTime::from_hms_opt(0, 0, 0).unwrap(); + + println!("{:?} -> {:?}", sunrise, sunset); + + chart.add_wedge(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, + }, + }); + } + } + *s.imp().info.borrow_mut() = Some(sun_moon_info); + + /* s.set_draw_func({ let s = s.clone(); move |_, context, width, height| { let info = s.imp().info.borrow(); - // context.set_source_rgb(240., 240., 240.); - context.set_source_rgb(0., 0., 0.); + context.set_source_rgba(0., 0., 0., 0.); let _ = context.paint(); - context.set_line_width(5.); + context.set_line_width(2.); context.set_source_rgb(0.7, 0., 0.9); context.arc(width as f64 / 2., height as f64 / 2., 200., 0., PI * 2.); let _ = context.stroke(); - (0..24).for_each(|hour| { - context.translate(width as f64 / 2., height as f64 / 2.); - context.rotate(PI / 12. * hour as f64); - context.move_to(210., 0.); - context.line_to(230., 0.); - context.identity_matrix(); - let _ = context.stroke(); - }); - if let Some(info) = info.deref() { context.move_to(width as f64 / 2., height as f64 / 2.); let full_day = Duration::days(1).num_seconds() as f64; @@ -266,13 +298,34 @@ impl Clock { context.arc(width as f64 / 2., height as f64 / 2., 200., start, end); let _ = context.fill(); } + + (0..24).for_each(|hour| { + context.translate(width as f64 / 2., height as f64 / 2.); + context.rotate(PI / 12. * hour as f64); + context.move_to(185., 0.); + context.line_to(195., 0.); + context.identity_matrix(); + let _ = context.stroke(); + }); } }); + */ s } } +/* +pub struct AppPrivate { +} + +impl AppPrivate { + const NAME: &'static str = "App"; + type Type = App; + type ParentType = adw::Application; +} +*/ + pub fn main() { /* let runtime = Arc::new( @@ -284,6 +337,7 @@ pub fn main() { */ let app = gtk::Application::builder() .application_id("com.luminescent-dreams.dashboard") + .resource_base_path("/com/luminescent-dreams/dashboard") .build(); app.connect_activate( @@ -294,22 +348,36 @@ pub fn main() { let layout = gtk::Box::builder() .orientation(Orientation::Vertical) + .hexpand(true) + .vexpand(true) .build(); let date = Date::new(); - let day = Clock::new(SunMoon { - sunrise: NaiveTime::from_hms_opt(6, 0, 0).unwrap(), - sunset: NaiveTime::from_hms_opt(0, 0, 0).unwrap(), + 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(&date); layout.append(&day); + window.set_child(Some(&layout)); }, ); - app.run(); + let args: Vec = env::args().collect(); + ApplicationExtManual::run_with_args(&app, &args); /* let now = Local::now(); diff --git a/dashboard/src/ui/mod.rs b/dashboard/src/ui/mod.rs new file mode 100644 index 0000000..1fa5546 --- /dev/null +++ b/dashboard/src/ui/mod.rs @@ -0,0 +1,2 @@ +mod pie_chart; +pub use pie_chart::{Color, PieChart, Wedge}; diff --git a/dashboard/src/ui/pie_chart.rs b/dashboard/src/ui/pie_chart.rs new file mode 100644 index 0000000..f51732d --- /dev/null +++ b/dashboard/src/ui/pie_chart.rs @@ -0,0 +1,141 @@ +use chrono::{Duration, NaiveTime}; +use glib::Object; +use gtk::{prelude::*, subclass::prelude::*}; +use std::{cell::RefCell, f64::consts::PI, rc::Rc}; + +#[derive(Clone, Debug)] +pub struct Color { + pub r: f64, + pub g: f64, + pub b: f64, +} + +#[derive(Clone, Debug)] +pub struct Wedge { + pub start_angle: f64, + pub end_angle: f64, + pub color: Color, +} + +#[derive(Default)] +pub struct PieChartPrivate { + rotation: Rc>, + wedges: Rc>>, +} + +#[glib::object_subclass] +impl ObjectSubclass for PieChartPrivate { + const NAME: &'static str = "PieChart"; + type Type = PieChart; + type ParentType = gtk::DrawingArea; +} + +impl ObjectImpl for PieChartPrivate {} +impl WidgetImpl for PieChartPrivate {} +impl DrawingAreaImpl for PieChartPrivate {} + +glib::wrapper! { + pub struct PieChart(ObjectSubclass) @extends gtk::DrawingArea, gtk::Widget; +} + +impl PieChart { + pub fn new(builder: PieChartBuilder) -> Self { + let s: Self = Object::builder().build(); + s.set_content_width(500); + s.set_content_height(500); + + *s.imp().rotation.borrow_mut() = builder.rotation; + *s.imp().wedges.borrow_mut() = builder.wedges; + + s.set_draw_func({ + let s = s.clone(); + move |_, context, width, height| { + println!("redrawing: {} {}", width, height); + let radius = width.min(height) as f64 / 2. * 0.9; + let center_x = (width / 2) as f64; + let center_y = (height / 2) as f64; + + let rotation = s.imp().rotation.borrow().clone(); + let wedges = s.imp().wedges.borrow().clone(); + + context.set_source_rgba(0., 0., 0., 0.); + let _ = context.paint(); + + context.set_line_width(2.); + + wedges.iter().for_each( + |Wedge { + start_angle, + end_angle, + color, + }| { + println!( + "wedge: {:.02?} {:.02?} {:.02?}", + start_angle, end_angle, color + ); + println!( + "wedge: {:.02?} {:.02?} [{:.02?}]", + start_angle + rotation, + end_angle + rotation, + rotation + ); + context.move_to(center_x, center_y); + context.set_source_rgb(color.r, color.g, color.b); + context.arc( + center_x, + center_y, + radius, + start_angle + rotation, + end_angle + rotation, + ); + let _ = context.fill(); + }, + ); + + context.set_source_rgb(1., 1., 1.); + context.arc(center_x, center_y, radius, 0., 2. * PI); + let _ = context.stroke(); + } + }); + + s + } + + pub fn set_rotation(&mut self, rotation: f64) { + *self.imp().rotation.borrow_mut() = rotation; + self.queue_draw(); + } + + pub fn add_wedge(&mut self, wedge: Wedge) { + (*self.imp().wedges.borrow_mut()).push(wedge); + self.queue_draw(); + } + + pub fn builder() -> PieChartBuilder { + PieChartBuilder { + rotation: 0., + wedges: vec![], + } + } +} + +pub struct PieChartBuilder { + rotation: f64, + wedges: Vec, +} + +impl PieChartBuilder { + pub fn rotation(mut self, rotation: f64) -> Self { + self.rotation = rotation; + self + } + + pub fn wedges(mut self, wedges: Vec) -> Self { + self.wedges = wedges; + self + } + + pub fn build(self) -> PieChart { + PieChart::new(self) + } +} diff --git a/flake.lock b/flake.lock index 0df1f2e..ca25ad3 100644 --- a/flake.lock +++ b/flake.lock @@ -51,16 +51,16 @@ }, "nixpkgs": { "locked": { - "lastModified": 1688392541, - "narHash": "sha256-lHrKvEkCPTUO+7tPfjIcb7Trk6k31rz18vkyqmkeJfY=", + "lastModified": 1691421349, + "narHash": "sha256-RRJyX0CUrs4uW4gMhd/X4rcDG8PTgaaCQM5rXEJOx6g=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "ea4c80b39be4c09702b0cb3b42eab59e2ba4f24b", + "rev": "011567f35433879aae5024fc6ec53f2a0568a6c4", "type": "github" }, "original": { "id": "nixpkgs", - "ref": "nixos-22.11", + "ref": "nixos-23.05", "type": "indirect" } }, diff --git a/flake.nix b/flake.nix index 39da673..9fdfd6e 100644 --- a/flake.nix +++ b/flake.nix @@ -2,7 +2,7 @@ description = "Lumenescent Dreams Tools"; inputs = { - nixpkgs.url = "nixpkgs/nixos-22.11"; + nixpkgs.url = "nixpkgs/nixos-23.05"; unstable.url = "nixpkgs/nixos-unstable"; pkgs-cargo2nix.url = "github:cargo2nix/cargo2nix"; typeshare.url = "github:1Password/typeshare";