Skip to content

Commit 2ad70aa

Browse files
authored
merge pr(#3) from proficiency/ae1200
implement AE-1200
2 parents 0aae715 + 2854b45 commit 2ad70aa

File tree

5 files changed

+690
-1
lines changed

5 files changed

+690
-1
lines changed

src/display.rs

Lines changed: 356 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,356 @@
1+
use ratatui::{
2+
prelude::*,
3+
widgets::{Block, Borders, Paragraph, Clear},
4+
};
5+
use chrono::Timelike;
6+
7+
use crate::watch::{Watch, WatchMode};
8+
9+
pub fn ui(f: &mut Frame, watch: &Watch) {
10+
let size = f.area();
11+
let watch_size = 20;
12+
13+
let watch_area = Rect {
14+
x: (size.width.saturating_sub(watch_size)) / 2,
15+
y: (size.height.saturating_sub(watch_size)) / 2,
16+
width: watch_size.min(size.width),
17+
height: watch_size.min(size.height),
18+
};
19+
20+
// draw the frame
21+
let watch_block = Block::default()
22+
.title("Casio AE-1200")
23+
.borders(Borders::ALL)
24+
.border_style(Style::default().fg(Color::Blue));
25+
26+
f.render_widget(Clear, watch_area);
27+
let watch_inner = watch_block.inner(watch_area);
28+
f.render_widget(watch_block, watch_area);
29+
30+
match watch.mode {
31+
WatchMode::Home => {
32+
render_time_display(f, watch_inner, watch);
33+
}
34+
WatchMode::WorldTime => {
35+
render_world_time_display(f, watch_inner, watch);
36+
}
37+
WatchMode::Alarm => {
38+
render_alarm_display(f, watch_inner, watch);
39+
}
40+
WatchMode::Timer => {
41+
render_timer_display(f, watch_inner, watch);
42+
}
43+
WatchMode::Stopwatch => {
44+
render_stopwatch_display(f, watch_inner, watch);
45+
}
46+
}
47+
48+
render_status_indicators(f, watch_area, watch);
49+
}
50+
51+
fn render_time_display(f: &mut Frame, area: Rect, watch: &Watch) {
52+
let time_text = watch.time_manager.format_time(watch.settings.time_format_24h);
53+
let date_text = watch.time_manager.format_date(watch.settings.date_format_us);
54+
let day_text = watch.time_manager.format_day_of_week();
55+
56+
let time_display = Paragraph::new(vec![
57+
Line::from(""),
58+
Line::from(vec![
59+
Span::styled(
60+
time_text,
61+
Style::default().fg(Color::Green).add_modifier(Modifier::BOLD),
62+
)
63+
]).alignment(Alignment::Center),
64+
Line::from(""),
65+
Line::from(vec![
66+
Span::styled(
67+
format!("{} {}", day_text, date_text),
68+
Style::default().fg(Color::Cyan),
69+
)
70+
]).alignment(Alignment::Center),
71+
Line::from(""),
72+
Line::from(""),
73+
Line::from("press 'M' for mode, 'L' for backlight"),
74+
])
75+
.block(Block::default());
76+
77+
f.render_widget(time_display, area);
78+
79+
render_analog_display(f, area, watch);
80+
}
81+
82+
fn render_analog_display(f: &mut Frame, area: Rect, watch: &Watch) {
83+
use std::f64::consts::PI;
84+
85+
// create a small area for the analog clock in the top-left corner
86+
let analog_area = Rect {
87+
x: area.x + 2,
88+
y: area.y + 1,
89+
width: 10.min(area.width),
90+
height: 5.min(area.height),
91+
};
92+
93+
let time = &watch.time_manager.current_time;
94+
let hour = time.hour() as f64;
95+
let minute = time.minute() as f64;
96+
let second = time.second() as f64;
97+
98+
// hour hand: 360 degrees / 12 hours = 30 degrees per hour + minute adjustment
99+
let hour_angle = (hour % 12.0) * 30.0 + minute * 0.5;
100+
let _hour_angle_rad = hour_angle * PI / 180.0;
101+
102+
// minute hand: 360 degrees / 60 minutes = 6 degrees per minute + second adjustment
103+
let minute_angle = minute * 6.0 + second * 0.1;
104+
let _minute_angle_rad = minute_angle * PI / 180.0;
105+
106+
// second hand: 360 degrees / 60 seconds = 6 degrees per second
107+
let second_angle = second * 6.0;
108+
let _second_angle_rad = second_angle * PI / 180.0;
109+
110+
let analog_display = Paragraph::new(vec![
111+
Line::from(format!("┌────────┐")),
112+
Line::from(format!("│ {}{} │",
113+
if hour_angle >= 270.0 || hour_angle <= 90.0 { "▲" } else { " " },
114+
if minute_angle >= 270.0 || minute_angle <= 90.0 { "●" } else { " " }
115+
)),
116+
Line::from(format!("│{}{} {}{}{}│",
117+
if hour_angle > 180.0 && hour_angle <= 360.0 { "◀" } else { " " },
118+
if minute_angle > 180.0 && minute_angle <= 360.0 { "●" } else { " " },
119+
if second_angle > 180.0 && second_angle <= 360.0 { "·" } else { " " },
120+
if minute_angle > 0.0 && minute_angle <= 180.0 { "●" } else { " " },
121+
if hour_angle > 0.0 && hour_angle <= 180.0 { "▶" } else { " " }
122+
)),
123+
Line::from(format!("│ {}{} │",
124+
if minute_angle > 90.0 && minute_angle <= 270.0 { "●" } else { " " },
125+
if hour_angle > 90.0 && hour_angle <= 270.0 { "▼" } else { " " }
126+
)),
127+
Line::from(format!("└────────┘")),
128+
])
129+
.style(Style::default().fg(Color::Yellow));
130+
131+
f.render_widget(analog_display, analog_area);
132+
}
133+
134+
fn render_date_display(f: &mut Frame, area: Rect, watch: &Watch) {
135+
let date_text = watch.time_manager.format_date(watch.settings.date_format_us);
136+
let day_text = watch.time_manager.format_day_of_week();
137+
let year_text = watch.time_manager.current_time.format("%Y").to_string();
138+
139+
let date_display = Paragraph::new(vec![
140+
Line::from(""),
141+
Line::from(""),
142+
Line::from(""),
143+
Line::from(vec![
144+
Span::styled(
145+
format!("{} {}", day_text, date_text),
146+
Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD),
147+
)
148+
]).alignment(Alignment::Center),
149+
Line::from(""),
150+
Line::from(vec![
151+
Span::styled(
152+
year_text,
153+
Style::default().fg(Color::Cyan),
154+
)
155+
]).alignment(Alignment::Center),
156+
Line::from(""),
157+
Line::from(""),
158+
Line::from(""),
159+
Line::from("Press 'M' for mode, 'L' for light"),
160+
])
161+
.block(Block::default());
162+
163+
f.render_widget(date_display, area);
164+
}
165+
166+
fn render_stopwatch_display(f: &mut Frame, area: Rect, watch: &Watch) {
167+
let time_text = format_stopwatch_time(watch.stopwatch_time);
168+
let status = if watch.stopwatch_running { "RUNNING" } else { "STOPPED" };
169+
170+
let stopwatch_display = Paragraph::new(vec![
171+
Line::from(""),
172+
Line::from(""),
173+
Line::from(vec![
174+
Span::styled(
175+
"STOPWATCH",
176+
Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD),
177+
)
178+
]).alignment(Alignment::Center),
179+
Line::from(""),
180+
Line::from(vec![
181+
Span::styled(
182+
time_text,
183+
Style::default().fg(Color::Green).add_modifier(Modifier::BOLD),
184+
)
185+
]).alignment(Alignment::Center),
186+
Line::from(""),
187+
Line::from(vec![
188+
Span::styled(
189+
status,
190+
if watch.stopwatch_running {
191+
Style::default().fg(Color::Red)
192+
} else {
193+
Style::default().fg(Color::Blue)
194+
},
195+
)
196+
]).alignment(Alignment::Center),
197+
Line::from(""),
198+
Line::from(""),
199+
Line::from("press 'S' start/stop, 'R' reset"),
200+
Line::from("press 'M' for mode, 'L' for backlight"),
201+
])
202+
.block(Block::default());
203+
204+
f.render_widget(stopwatch_display, area);
205+
}
206+
207+
fn format_stopwatch_time(milliseconds: u64) -> String {
208+
let total_seconds = milliseconds / 1000;
209+
let minutes = total_seconds / 60;
210+
let seconds = total_seconds % 60;
211+
let millis = (milliseconds % 1000) / 10;
212+
213+
format!("{:02}:{:02}.{:02}", minutes, seconds, millis)
214+
}
215+
216+
fn render_status_indicators(f: &mut Frame, area: Rect, watch: &Watch) {
217+
if watch.light_on {
218+
let light_indicator = Paragraph::new("LGT")
219+
.style(Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD))
220+
.alignment(Alignment::Right);
221+
222+
let light_area = Rect {
223+
x: area.x + area.width.saturating_sub(8),
224+
y: area.y + 1,
225+
width: 7.min(area.width),
226+
height: 1,
227+
};
228+
229+
f.render_widget(light_indicator, light_area);
230+
}
231+
232+
if watch.settings.alarm_enabled {
233+
let alarm_indicator = Paragraph::new("ALM")
234+
.style(Style::default().fg(Color::Red).add_modifier(Modifier::BOLD))
235+
.alignment(Alignment::Left);
236+
237+
let alarm_area = Rect {
238+
x: area.x + 1,
239+
y: area.y + 1,
240+
width: 8.min(area.width),
241+
height: 1,
242+
};
243+
244+
f.render_widget(alarm_indicator, alarm_area);
245+
}
246+
}
247+
248+
fn render_world_time_display(f: &mut Frame, area: Rect, watch: &Watch) {
249+
let time_text = watch.time_manager.format_time(watch.settings.time_format_24h);
250+
251+
let world_time_display = Paragraph::new(vec![
252+
Line::from(""),
253+
Line::from(""),
254+
Line::from(vec![
255+
Span::styled(
256+
"WT",
257+
Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD),
258+
)
259+
]).alignment(Alignment::Center),
260+
Line::from(""),
261+
Line::from(vec![
262+
Span::styled(
263+
time_text,
264+
Style::default().fg(Color::Green).add_modifier(Modifier::BOLD),
265+
)
266+
]).alignment(Alignment::Center),
267+
Line::from(""),
268+
Line::from(""),
269+
Line::from("Press 'M' for mode, 'L' for backlight"),
270+
])
271+
.block(Block::default());
272+
273+
f.render_widget(world_time_display, area);
274+
}
275+
276+
fn render_alarm_display(f: &mut Frame, area: Rect, watch: &Watch) {
277+
let alarm_status = if watch.settings.alarm_enabled {
278+
watch.settings.alarm_time.clone().unwrap_or_else(|| "Not set".to_string())
279+
} else {
280+
"Disabled".to_string()
281+
};
282+
283+
let alarm_display = Paragraph::new(vec![
284+
Line::from(""),
285+
Line::from(""),
286+
Line::from(vec![
287+
Span::styled(
288+
"ALM",
289+
Style::default().fg(Color::Red).add_modifier(Modifier::BOLD),
290+
)
291+
]).alignment(Alignment::Center),
292+
Line::from(""),
293+
Line::from(vec![
294+
Span::styled(
295+
alarm_status,
296+
Style::default().fg(Color::Cyan),
297+
)
298+
]).alignment(Alignment::Center),
299+
Line::from(""),
300+
Line::from(""),
301+
Line::from("Press 'A' to toggle, 'M' for mode"),
302+
])
303+
.block(Block::default());
304+
305+
f.render_widget(alarm_display, area);
306+
}
307+
308+
fn render_timer_display(f: &mut Frame, area: Rect, watch: &Watch) {
309+
let time_text = format_timer_time(watch.timer_time);
310+
let status = if watch.timer_running { "RUNNING" } else { "STOPPED" };
311+
312+
let timer_display = Paragraph::new(vec![
313+
Line::from(""),
314+
Line::from(""),
315+
Line::from(vec![
316+
Span::styled(
317+
"TMR",
318+
Style::default().fg(Color::Magenta).add_modifier(Modifier::BOLD),
319+
)
320+
]).alignment(Alignment::Center),
321+
Line::from(""),
322+
Line::from(vec![
323+
Span::styled(
324+
time_text,
325+
Style::default().fg(Color::Green).add_modifier(Modifier::BOLD),
326+
)
327+
]).alignment(Alignment::Center),
328+
Line::from(""),
329+
Line::from(vec![
330+
Span::styled(
331+
status,
332+
if watch.timer_running {
333+
Style::default().fg(Color::Red)
334+
} else {
335+
Style::default().fg(Color::Blue)
336+
},
337+
)
338+
]).alignment(Alignment::Center),
339+
Line::from(""),
340+
Line::from(""),
341+
Line::from("Press 'S' start/stop, 'R' reset"),
342+
Line::from("Press 'M' for mode, 'L' for backlight"),
343+
])
344+
.block(Block::default());
345+
346+
f.render_widget(timer_display, area);
347+
}
348+
349+
fn format_timer_time(milliseconds: u64) -> String {
350+
let total_seconds = milliseconds / 1000;
351+
let minutes = total_seconds / 60;
352+
let seconds = total_seconds % 60;
353+
let millis = (milliseconds % 1000) / 10;
354+
355+
format!("{:02}:{:02}.{:02}", minutes, seconds, millis)
356+
}

0 commit comments

Comments
 (0)