Write up a Pie Chart widget

This commit is contained in:
Savanni D'Gerinel 2023-08-08 17:25:21 -04:00
parent 6f31ac1e8d
commit 36b0238662
7 changed files with 280 additions and 34 deletions

34
Cargo.lock generated
View File

@ -341,6 +341,7 @@ dependencies = [
"gtk4", "gtk4",
"ifc", "ifc",
"lazy_static", "lazy_static",
"libadwaita",
"memorycache", "memorycache",
"reqwest", "reqwest",
"serde", "serde",
@ -1263,6 +1264,39 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" 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]] [[package]]
name = "libc" name = "libc"
version = "0.2.147" version = "0.2.147"

View File

@ -6,6 +6,7 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
adw = { package = "libadwaita", version = "0.4", features = ["v1_3"] }
cairo-rs = { version = "0.17" } cairo-rs = { version = "0.17" }
chrono = { version = "0.4", features = ["serde"] } chrono = { version = "0.4", features = ["serde"] }
fluent-ergonomics = { path = "../fluent-ergonomics/" } fluent-ergonomics = { path = "../fluent-ergonomics/" }

View File

@ -9,7 +9,10 @@ use chrono::{Datelike, Duration, NaiveTime};
use glib::Object; use glib::Object;
use gtk::{prelude::*, subclass::prelude::*, Orientation}; use gtk::{prelude::*, subclass::prelude::*, Orientation};
use ifc::IFC; 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}; use geo_types::{Latitude, Longitude};
@ -187,6 +190,12 @@ glib::wrapper! {
impl Date { impl Date {
fn new() -> Self { fn new() -> Self {
let s: Self = Object::builder().build(); 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()); let dt = IFC::from(chrono::Local::now().date_naive().with_year(12023).unwrap());
s.append(&gtk::Label::new(Some( s.append(&gtk::Label::new(Some(
@ -203,56 +212,79 @@ impl Date {
} }
} }
#[derive(Default)] pub struct TransitClockPrivate {
pub struct ClockPrivate {
info: Rc<RefCell<Option<SunMoon>>>, info: Rc<RefCell<Option<SunMoon>>>,
chart: Rc<RefCell<PieChart>>,
}
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] #[glib::object_subclass]
impl ObjectSubclass for ClockPrivate { impl ObjectSubclass for TransitClockPrivate {
const NAME: &'static str = "Clock"; const NAME: &'static str = "TransitClock";
type Type = Clock; type Type = TransitClock;
type ParentType = gtk::DrawingArea; type ParentType = gtk::Box;
} }
impl ObjectImpl for ClockPrivate {} impl ObjectImpl for TransitClockPrivate {}
impl WidgetImpl for ClockPrivate {} impl WidgetImpl for TransitClockPrivate {}
impl DrawingAreaImpl for ClockPrivate {} impl BoxImpl for TransitClockPrivate {}
glib::wrapper! { glib::wrapper! {
pub struct Clock(ObjectSubclass<ClockPrivate>) @extends gtk::DrawingArea, gtk::Widget; pub struct TransitClock(ObjectSubclass<TransitClockPrivate>) @extends gtk::Box, gtk::Widget;
} }
impl Clock { impl TransitClock {
pub fn new(sun_moon_info: SunMoon) -> Self { pub fn new(sun_moon_info: SunMoon) -> Self {
let s: Self = Object::builder().build(); let s: Self = Object::builder().build();
s.set_width_request(500); s.set_width_request(500);
s.set_height_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.imp().info.borrow_mut() = Some(sun_moon_info);
/*
s.set_draw_func({ s.set_draw_func({
let s = s.clone(); let s = s.clone();
move |_, context, width, height| { move |_, context, width, height| {
let info = s.imp().info.borrow(); let info = s.imp().info.borrow();
// context.set_source_rgb(240., 240., 240.); context.set_source_rgba(0., 0., 0., 0.);
context.set_source_rgb(0., 0., 0.);
let _ = context.paint(); let _ = context.paint();
context.set_line_width(5.); context.set_line_width(2.);
context.set_source_rgb(0.7, 0., 0.9); context.set_source_rgb(0.7, 0., 0.9);
context.arc(width as f64 / 2., height as f64 / 2., 200., 0., PI * 2.); context.arc(width as f64 / 2., height as f64 / 2., 200., 0., PI * 2.);
let _ = context.stroke(); 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() { if let Some(info) = info.deref() {
context.move_to(width as f64 / 2., height as f64 / 2.); context.move_to(width as f64 / 2., height as f64 / 2.);
let full_day = Duration::days(1).num_seconds() as f64; 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); context.arc(width as f64 / 2., height as f64 / 2., 200., start, end);
let _ = context.fill(); 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 s
} }
} }
/*
pub struct AppPrivate {
}
impl AppPrivate {
const NAME: &'static str = "App";
type Type = App;
type ParentType = adw::Application;
}
*/
pub fn main() { pub fn main() {
/* /*
let runtime = Arc::new( let runtime = Arc::new(
@ -284,6 +337,7 @@ pub fn main() {
*/ */
let app = gtk::Application::builder() let app = gtk::Application::builder()
.application_id("com.luminescent-dreams.dashboard") .application_id("com.luminescent-dreams.dashboard")
.resource_base_path("/com/luminescent-dreams/dashboard")
.build(); .build();
app.connect_activate( app.connect_activate(
@ -294,22 +348,36 @@ pub fn main() {
let layout = gtk::Box::builder() let layout = gtk::Box::builder()
.orientation(Orientation::Vertical) .orientation(Orientation::Vertical)
.hexpand(true)
.vexpand(true)
.build(); .build();
let date = Date::new(); let date = Date::new();
let day = Clock::new(SunMoon { layout.append(&date);
sunrise: NaiveTime::from_hms_opt(6, 0, 0).unwrap(),
sunset: NaiveTime::from_hms_opt(0, 0, 0).unwrap(), 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), moonrise: NaiveTime::from_hms_opt(15, 0, 0),
moonset: NaiveTime::from_hms_opt(5, 0, 0), moonset: NaiveTime::from_hms_opt(5, 0, 0),
moon_phase: LunarPhase::WaningCrescent, moon_phase: LunarPhase::WaningCrescent,
}); });
layout.append(&date);
layout.append(&day); layout.append(&day);
window.set_child(Some(&layout)); window.set_child(Some(&layout));
}, },
); );
app.run(); let args: Vec<String> = env::args().collect();
ApplicationExtManual::run_with_args(&app, &args);
/* /*
let now = Local::now(); let now = Local::now();

2
dashboard/src/ui/mod.rs Normal file
View File

@ -0,0 +1,2 @@
mod pie_chart;
pub use pie_chart::{Color, PieChart, Wedge};

View File

@ -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<RefCell<f64>>,
wedges: Rc<RefCell<Vec<Wedge>>>,
}
#[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<PieChartPrivate>) @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<Wedge>,
}
impl PieChartBuilder {
pub fn rotation(mut self, rotation: f64) -> Self {
self.rotation = rotation;
self
}
pub fn wedges(mut self, wedges: Vec<Wedge>) -> Self {
self.wedges = wedges;
self
}
pub fn build(self) -> PieChart {
PieChart::new(self)
}
}

View File

@ -51,16 +51,16 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1688392541, "lastModified": 1691421349,
"narHash": "sha256-lHrKvEkCPTUO+7tPfjIcb7Trk6k31rz18vkyqmkeJfY=", "narHash": "sha256-RRJyX0CUrs4uW4gMhd/X4rcDG8PTgaaCQM5rXEJOx6g=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "ea4c80b39be4c09702b0cb3b42eab59e2ba4f24b", "rev": "011567f35433879aae5024fc6ec53f2a0568a6c4",
"type": "github" "type": "github"
}, },
"original": { "original": {
"id": "nixpkgs", "id": "nixpkgs",
"ref": "nixos-22.11", "ref": "nixos-23.05",
"type": "indirect" "type": "indirect"
} }
}, },

View File

@ -2,7 +2,7 @@
description = "Lumenescent Dreams Tools"; description = "Lumenescent Dreams Tools";
inputs = { inputs = {
nixpkgs.url = "nixpkgs/nixos-22.11"; nixpkgs.url = "nixpkgs/nixos-23.05";
unstable.url = "nixpkgs/nixos-unstable"; unstable.url = "nixpkgs/nixos-unstable";
pkgs-cargo2nix.url = "github:cargo2nix/cargo2nix"; pkgs-cargo2nix.url = "github:cargo2nix/cargo2nix";
typeshare.url = "github:1Password/typeshare"; typeshare.url = "github:1Password/typeshare";