use std::f32::consts::PI;
use super::{
complication_names, complication_options, complications, date_formats, Complication,
EnabledComplications, Face, Theme,
};
const DEFAULT_TIME_SIZE: f32 = 32.0;
use crate::rendering::Canvas;
use crate::sensors::data::SystemData;
fn dim_color(color: u32, background: u32, factor: f32) -> u32 {
let r1 = ((color >> 16) & 0xFF) as f32;
let g1 = ((color >> 8) & 0xFF) as f32;
let b1 = (color & 0xFF) as f32;
let r2 = ((background >> 16) & 0xFF) as f32;
let g2 = ((background >> 8) & 0xFF) as f32;
let b2 = (background & 0xFF) as f32;
let r = (r1 * factor + r2 * (1.0 - factor)) as u32;
let g = (g1 * factor + g2 * (1.0 - factor)) as u32;
let b = (b1 * factor + b2 * (1.0 - factor)) as u32;
(r << 16) | (g << 8) | b
}
struct FaceColors {
outline: u32,
hour_hand: u32,
minute_hand: u32,
center: u32,
text: u32,
dim: u32,
}
impl FaceColors {
fn from_theme(theme: &Theme) -> Self {
Self {
outline: theme.primary,
hour_hand: theme.text,
minute_hand: theme.text,
center: theme.primary,
text: theme.text,
dim: dim_color(theme.text, theme.background, 0.7),
}
}
}
const FONT_NORMAL: f32 = 14.0;
const FONT_SMALL: f32 = 12.0;
pub struct ClockFace;
struct ClockLayout {
show_hostname: bool,
show_date: bool,
hostname: String,
date: Option<String>,
}
impl ClockFace {
pub fn new() -> Self {
Self
}
fn draw_centered_text(
canvas: &mut Canvas,
y: i32,
text: &str,
font_size: f32,
color: u32,
) -> i32 {
let (width, _) = canvas.dimensions();
let text_width = canvas.text_width(text, font_size);
let x = (width as i32 - text_width) / 2;
canvas.draw_text(x, y, text, font_size, color);
canvas.line_height(font_size)
}
fn draw_digital_time(
canvas: &mut Canvas,
hour: u8,
minute: u8,
layout: &ClockLayout,
colors: &FaceColors,
time_font_size: f32,
) {
let (width, height) = canvas.dimensions();
let time_str = format!("{:02}:{:02}", hour, minute);
let portrait = width < 200;
let (effective_font_size, x_scale) =
if portrait && time_font_size > 56.0 && time_font_size <= 96.0 {
let hostname_space = if layout.show_hostname {
canvas.line_height(FONT_SMALL) + 8
} else {
0
};
let date_space = if layout.show_date && layout.date.is_some() {
canvas.line_height(FONT_NORMAL) + 8
} else {
0
};
let margin_v = 8.0;
let available_height =
height as f32 - hostname_space as f32 - date_space as f32 - margin_v * 2.0;
let target_font_size = (available_height / 1.2).min(96.0);
let effective_size = time_font_size.max(target_font_size).min(96.0);
let margin_h = 4.0;
let available_width = width as f32 - margin_h * 2.0;
let natural_width = canvas.text_width(&time_str, effective_size) as f32;
let scale = if natural_width > available_width {
available_width / natural_width
} else {
1.0
};
(effective_size, scale)
} else if portrait && time_font_size > 96.0 {
let margin = 4.0;
let available_width = width as f32 - margin * 2.0;
let natural_width = canvas.text_width(&time_str, time_font_size) as f32;
let scale = if natural_width > available_width {
available_width / natural_width
} else {
1.0
};
(time_font_size, scale)
} else {
(time_font_size, 1.0)
};
let time_height = canvas.line_height(effective_font_size);
let mut total_height = time_height;
if layout.show_hostname {
total_height += canvas.line_height(FONT_SMALL) + 4;
}
if layout.show_date && layout.date.is_some() {
total_height += canvas.line_height(FONT_NORMAL) + 4;
}
let mut y = (height as i32 - total_height) / 2;
if layout.show_hostname {
let h = Self::draw_centered_text(canvas, y, &layout.hostname, FONT_SMALL, colors.dim);
y += h + 4;
}
if x_scale < 1.0 {
let text_width = canvas.text_width_scaled(&time_str, effective_font_size, x_scale);
let x = (width as i32 - text_width) / 2;
canvas.draw_text_scaled(x, y, &time_str, effective_font_size, colors.text, x_scale);
} else {
Self::draw_centered_text(canvas, y, &time_str, effective_font_size, colors.text);
}
y += time_height + 4;
if layout.show_date {
if let Some(date) = &layout.date {
Self::draw_centered_text(canvas, y, date, FONT_NORMAL, colors.dim);
}
}
}
fn draw_analog_clock(
canvas: &mut Canvas,
hour: u8,
minute: u8,
layout: &ClockLayout,
colors: &FaceColors,
) {
let (width, height) = canvas.dimensions();
let margin = 10;
let hostname_height = if layout.show_hostname {
canvas.line_height(FONT_SMALL) + 4
} else {
0
};
let date_height = if layout.show_date && layout.date.is_some() {
canvas.line_height(FONT_NORMAL) + 8
} else {
0
};
let available_height = height as i32 - margin * 2 - hostname_height - date_height;
let available_width = width as i32 - margin * 2;
let radius = (available_height.min(available_width) / 2) as u32;
let total_height = hostname_height + (radius as i32 * 2) + date_height;
let start_y = (height as i32 - total_height) / 2;
let cx = width as i32 / 2;
let mut y = start_y;
if layout.show_hostname {
Self::draw_centered_text(canvas, y, &layout.hostname, FONT_SMALL, colors.dim);
y += hostname_height;
}
let cy = y + radius as i32;
Self::draw_clock_face(canvas, cx, cy, radius, hour, minute, colors);
if layout.show_date {
if let Some(date) = &layout.date {
let date_y = cy + radius as i32 + 8;
Self::draw_centered_text(canvas, date_y, date, FONT_NORMAL, colors.text);
}
}
}
fn draw_clock_face(
canvas: &mut Canvas,
cx: i32,
cy: i32,
radius: u32,
hour: u8,
minute: u8,
colors: &FaceColors,
) {
let radius_f = radius as f32;
canvas.draw_arc(cx, cy, radius, 0.0, 2.0 * PI, 2.0, colors.outline);
for i in 0..12 {
let angle = (i as f32) * PI / 6.0 - PI / 2.0;
let inner_r = radius_f * 0.85;
let outer_r = radius_f * 0.95;
let x1 = cx as f32 + inner_r * angle.cos();
let y1 = cy as f32 + inner_r * angle.sin();
let x2 = cx as f32 + outer_r * angle.cos();
let y2 = cy as f32 + outer_r * angle.sin();
let stroke = if i % 3 == 0 { 3.0 } else { 1.5 };
canvas.draw_line(
x1 as i32,
y1 as i32,
x2 as i32,
y2 as i32,
stroke,
colors.outline,
);
}
let minute_angle = (minute as f32) * PI / 30.0 - PI / 2.0;
let hour_angle = ((hour % 12) as f32 + minute as f32 / 60.0) * PI / 6.0 - PI / 2.0;
let hour_length = radius_f * 0.5;
let hour_x = cx as f32 + hour_length * hour_angle.cos();
let hour_y = cy as f32 + hour_length * hour_angle.sin();
canvas.draw_line(cx, cy, hour_x as i32, hour_y as i32, 4.0, colors.hour_hand);
let minute_length = radius_f * 0.75;
let minute_x = cx as f32 + minute_length * minute_angle.cos();
let minute_y = cy as f32 + minute_length * minute_angle.sin();
canvas.draw_line(
cx,
cy,
minute_x as i32,
minute_y as i32,
2.5,
colors.minute_hand,
);
canvas.fill_circle(cx, cy, 4, colors.center);
}
}
impl Default for ClockFace {
fn default() -> Self {
Self::new()
}
}
impl Face for ClockFace {
fn name(&self) -> &str {
"clock"
}
fn available_complications(&self) -> Vec<Complication> {
vec![
complications::hostname(false),
complications::digital_time(false),
complications::date(false, date_formats::SHORT),
]
}
fn render(
&self,
canvas: &mut Canvas,
data: &SystemData,
theme: &Theme,
comp: &EnabledComplications,
) {
let colors = FaceColors::from_theme(theme);
let is_on = |id: &str| comp.is_enabled(self.name(), id, false);
let date_format = comp
.get_option(
self.name(),
complication_names::DATE,
complication_options::DATE_FORMAT,
)
.map(|s| s.as_str())
.unwrap_or(date_formats::SHORT);
let time_size = comp
.get_option(self.name(), "digital_time", complication_options::SIZE)
.and_then(|s| s.parse::<f32>().ok())
.unwrap_or(DEFAULT_TIME_SIZE);
let layout = ClockLayout {
show_hostname: is_on("hostname"),
show_date: is_on(complication_names::DATE),
hostname: data.hostname.clone(),
date: data.format_date(date_format),
};
if is_on("digital_time") {
Self::draw_digital_time(canvas, data.hour, data.minute, &layout, &colors, time_size);
} else {
Self::draw_analog_clock(canvas, data.hour, data.minute, &layout, &colors);
}
}
}