use iced::{ advanced::{ graphics::text::Paragraph, layout::{Limits, Node}, renderer::{Quad, Style}, svg::Renderer as SvgRenderer, text::{LineHeight, Shaping}, widget::{tree::Tag, Tree}, Layout, Renderer as AdvancedRenderer, Widget, }, alignment::{Horizontal, Vertical}, font::Weight, gradient::Linear, mouse::Cursor, widget::{text, text::Appearance}, Alignment, Background, Color, Degrees, Element, Font, Gradient, Length, Rectangle, Renderer, Size, Theme, }; use time::OffsetDateTime; use crate::oracle::Weather; #[allow(clippy::module_name_repetitions)] pub struct WeatherCard { pub on_click: Option, pub current_weather: Weather, } impl WeatherCard { pub fn new(current_weather: Weather) -> Self { Self { current_weather, on_click: None, } } fn build_temperature(&self) -> String { format!("{}°", self.current_weather.temperature) } fn build_conditions(&self) -> String { format!( "{}\nH:{}° L:{}°", self.current_weather.weather_condition(), self.current_weather.high, self.current_weather.low, ) } } impl Widget for WeatherCard { fn size(&self) -> Size { Size::new(Length::Fixed(192.0), Length::Fixed(192.0)) } fn layout(&self, tree: &mut Tree, renderer: &Renderer, limits: &Limits) -> Node { let limits = limits .height(self.size().height) .width(self.size().width) .shrink([32, 32]); let container_size = limits.resolve(self.size().width, self.size().height, Size::ZERO); let local_state = tree.state.downcast_mut::(); let header_node = text::layout( &mut local_state.header, renderer, &limits, Length::Shrink, Length::Shrink, &self.build_temperature(), LineHeight::default(), Some(42.0.into()), Some(Font { weight: Weight::Normal, ..Font::with_name("Helvetica Neue") }), Horizontal::Left, Vertical::Top, Shaping::Basic, ) .move_to([16., 16.]) .align(Alignment::Start, Alignment::Start, container_size); let icon_node = Node::new(Size::new(16., 16.)).translate([16., -48.]).align( Alignment::Start, Alignment::End, container_size, ); let conditions_node = text::layout( &mut local_state.conditions, renderer, &limits, Length::Shrink, Length::Shrink, &self.build_conditions(), LineHeight::default(), Some(12.0.into()), Some(Font { weight: Weight::Bold, ..Font::with_name("Helvetica Neue") }), Horizontal::Left, Vertical::Bottom, Shaping::Basic, ) .move_to([16., -16.]); Node::with_children( container_size, vec![header_node, icon_node, conditions_node], ) } fn draw( &self, state: &Tree, renderer: &mut Renderer, _theme: &Theme, style: &Style, layout: Layout<'_>, _cursor: Cursor, viewport: &Rectangle, ) { // TODO: get sunrise/sunset from somewhere reasonable let day_time = matches!(OffsetDateTime::now_utc().hour(), 5..=19); let local_state = state.state.downcast_ref::(); let gradient = if day_time { Linear::new(Degrees(90.)) .add_stop(0.0, Color::from_rgba8(104, 146, 190, 1.0)) .add_stop(1.0, Color::from_rgba8(10, 54, 120, 1.0)) } else { Linear::new(Degrees(90.)) .add_stop(0.0, Color::from_rgba8(43, 44, 66, 1.0)) .add_stop(1.0, Color::from_rgba8(15, 18, 27, 1.0)) }; renderer.fill_quad( Quad { bounds: layout.bounds(), border_radius: [20., 20., 20., 20.].into(), border_width: 0., border_color: Color::WHITE, }, Background::Gradient(Gradient::Linear(gradient)), ); let mut children = layout.children(); let header_layout = children.next().unwrap(); text::draw( renderer, style, header_layout, &local_state.header, Appearance { color: Some(Color::WHITE), }, viewport, ); let icon_bounds = children.next().unwrap().bounds(); if let Some(icon) = self.current_weather.weather_condition().icon(day_time) { renderer.draw(icon.handle(), None, icon_bounds); } let conditions_layout = children.next().unwrap(); text::draw( renderer, style, conditions_layout, &local_state.conditions, Appearance { color: Some(Color::WHITE), }, viewport, ); } fn state(&self) -> iced::advanced::widget::tree::State { iced::advanced::widget::tree::State::new(State::default()) } fn tag(&self) -> Tag { Tag::of::() } } #[derive(Default)] pub struct State { header: text::State, conditions: text::State, } impl<'a, M> From> for Element<'a, M> where M: 'a + Clone, { fn from(modal: WeatherCard) -> Self { Element::new(modal) } }