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<M> {
pub on_click: Option<M>,
pub current_weather: Weather,
}
impl<M> WeatherCard<M> {
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<M: Clone> Widget<M, Renderer> for WeatherCard<M> {
fn size(&self) -> Size<Length> {
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::<State>();
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,
) {
let day_time = matches!(OffsetDateTime::now_utc().hour(), 5..=19);
let local_state = state.state.downcast_ref::<State>();
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::<State>()
}
}
#[derive(Default)]
pub struct State {
header: text::State<Paragraph>,
conditions: text::State<Paragraph>,
}
impl<'a, M> From<WeatherCard<M>> for Element<'a, M>
where
M: 'a + Clone,
{
fn from(modal: WeatherCard<M>) -> Self {
Element::new(modal)
}
}