Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ pub enum InputMode {
AddingLocation,
EditingDatetime,
EditingTimezone,
AlmanacBodyPicker,
}

pub struct NewLocationDraft {
Expand Down Expand Up @@ -95,6 +96,9 @@ pub struct App {
pub orrery: OrreryInfo,
pub almanac: AlmanacInfo,

pub selected_bodies: Vec<bool>,
pub almanac_picker_sel: usize,

pub forecasts: Option<Vec<HourlyForecast>>,
pub weather_loading: bool,
pub weather_error: Option<String>,
Expand Down Expand Up @@ -133,6 +137,8 @@ impl App {
planets: Vec::new(),
orrery: OrreryInfo { planets: Vec::new() },
almanac: AlmanacInfo { tracks: Vec::new(), current_step: 0 },
selected_bodies: Vec::new(),
almanac_picker_sel: 0,
sun_moon: SunMoonInfo {
sun_stereo: None,
moon_stereo: None,
Expand Down Expand Up @@ -173,6 +179,9 @@ impl App {
self.orrery = sky::compute_orrery(self.datetime);
if matches!(self.tab, Tab::Almanac) {
self.almanac = sky::compute_almanac(self.lat, self.lon, self.height, self.datetime, self.timezone);
while self.selected_bodies.len() < self.almanac.tracks.len() {
self.selected_bodies.push(true);
}
}
}
}
Expand Down
32 changes: 31 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,13 @@ fn run(
KeyCode::Char('a') | KeyCode::Char('A') => {
app.tab = Tab::Almanac;
app.almanac = sky::compute_almanac(app.lat, app.lon, app.height, app.datetime, app.timezone);
while app.selected_bodies.len() < app.almanac.tracks.len() {
app.selected_bodies.push(true);
}
}
KeyCode::Char('b') | KeyCode::Char('B') if matches!(app.tab, Tab::Almanac) => {
app.input_mode = InputMode::AlmanacBodyPicker;
app.almanac_picker_sel = 0;
}
KeyCode::Char('l') | KeyCode::Char('L') => {
app.input_mode = InputMode::LocationPicker;
Expand Down Expand Up @@ -360,6 +367,29 @@ fn run(
_ => {}
}
}
InputMode::AlmanacBodyPicker => match key.code {
KeyCode::Esc => {
app.input_mode = InputMode::Normal;
}
KeyCode::Up | KeyCode::Char('k') => {
if app.almanac_picker_sel > 0 { app.almanac_picker_sel -= 1; }
else { app.almanac_picker_sel = app.almanac.tracks.len().saturating_sub(1); }
}
KeyCode::Down | KeyCode::Char('j') => {
if app.almanac_picker_sel + 1 < app.almanac.tracks.len() {
app.almanac_picker_sel += 1;
} else {
app.almanac_picker_sel = 0;
}
}
KeyCode::Char(' ') | KeyCode::Enter => {
let sel = app.almanac_picker_sel;
if let Some(v) = app.selected_bodies.get_mut(sel) {
*v = !*v;
}
}
_ => {}
},
InputMode::EditingDatetime | InputMode::EditingTimezone => {
match key.code {
KeyCode::Esc => {
Expand Down Expand Up @@ -413,6 +443,6 @@ fn apply_input(app: &mut App) {
app.timezone = Some(tz);
}
}
InputMode::Normal | InputMode::LocationPicker | InputMode::AddingLocation => {}
InputMode::Normal | InputMode::LocationPicker | InputMode::AddingLocation | InputMode::AlmanacBodyPicker => {}
}
}
60 changes: 52 additions & 8 deletions src/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ pub fn render(f: &mut Frame, app: &App) {
match app.input_mode {
InputMode::LocationPicker => render_location_picker(f, app),
InputMode::AddingLocation => render_add_location(f, app),
InputMode::AlmanacBodyPicker => render_almanac_body_picker(f, app),
_ => {}
}
}
Expand Down Expand Up @@ -554,7 +555,7 @@ fn render_status(f: &mut Frame, app: &App, area: ratatui::layout::Rect) {
let loc_name = app.locations.get(app.location_index).map(|l| l.name.as_str()).unwrap_or("");

let editing_hint = match app.input_mode {
InputMode::Normal | InputMode::LocationPicker | InputMode::AddingLocation => String::new(),
InputMode::Normal | InputMode::LocationPicker | InputMode::AddingLocation | InputMode::AlmanacBodyPicker => String::new(),
InputMode::EditingDatetime => format!(" Editing time (local): {}_", app.input_buf),
InputMode::EditingTimezone => format!(" Editing timezone: {}_", app.input_buf),
};
Expand All @@ -576,7 +577,7 @@ fn render_status(f: &mut Frame, app: &App, area: ratatui::layout::Rect) {
Tab::SolarSystem =>
" [L]locations [T]time [Z]tz [N]now [Space]pause [,/.]speed [S/W/P/A]tab [Q]quit",
Tab::Almanac =>
" [L]locations [T]time [Z]tz [N]now [Space]pause [,/.]speed [S/W/P/A]tab [Q]quit",
" [L]locations [T]time [Z]tz [N]now [Space]pause [,/.]speed [b]bodies [S/W/P/A]tab [Q]quit",
};

let text = vec![Line::from(line1), Line::from(line2)];
Expand All @@ -600,7 +601,10 @@ fn render_almanac_canvas(f: &mut Frame, app: &App, area: ratatui::layout::Rect)

// Pre-compute arc segments: Vec<(x1, y1, x2, y2, r, g, b)>
let mut segments: Vec<(f64, f64, f64, f64, u8, u8, u8)> = Vec::new();
for track in &almanac.tracks {
for (idx, track) in almanac.tracks.iter().enumerate() {
if !app.selected_bodies.get(idx).copied().unwrap_or(true) {
continue;
}
let (base_r, base_g, base_b) = track.color_rgb;
for k in 0..96usize {
let idx0 = (current_step + k) % ALMANAC_STEPS;
Expand Down Expand Up @@ -789,6 +793,43 @@ fn render_add_location(f: &mut Frame, app: &App) {
f.render_widget(para, inner);
}

fn render_almanac_body_picker(f: &mut Frame, app: &App) {
let n = app.almanac.tracks.len() as u16;
let height = n + 4;
let area = centered_popup(f, 35, height);
f.render_widget(Clear, area);

let block = Block::default().borders(Borders::ALL).title(" Bodies [b] ");
let inner = block.inner(area);
f.render_widget(block, area);

let mut lines: Vec<Line> = app.almanac.tracks.iter().enumerate().map(|(i, track)| {
let checked = app.selected_bodies.get(i).copied().unwrap_or(true);
let check = if checked { "✓" } else { " " };
let text = format!(" [{}] {} {}", check, track.symbol, track.name);
let (r, g, b) = track.color_rgb;
if i == app.almanac_picker_sel {
Line::from(vec![
Span::styled(text, Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD)),
Span::styled(" ■", Style::default().fg(Color::Rgb(r, g, b))),
])
} else if checked {
Line::from(Span::styled(text, Style::default().fg(Color::Rgb(r, g, b))))
} else {
Line::from(Span::styled(text, Style::default().fg(Color::DarkGray)))
}
}).collect();

lines.push(Line::from(""));
lines.push(Line::from(Span::styled(
" Space toggle · Esc close",
Style::default().fg(Color::DarkGray),
)));

let para = Paragraph::new(lines);
f.render_widget(para, inner);
}

fn render_almanac_legend(f: &mut Frame, app: &App, area: ratatui::layout::Rect) {
let mut text = vec![
Line::from(Span::styled(
Expand All @@ -811,18 +852,21 @@ fn render_almanac_legend(f: &mut Frame, app: &App, area: ratatui::layout::Rect)
)),
];

for track in &app.almanac.tracks {
for (i, track) in app.almanac.tracks.iter().enumerate() {
let visible = app.selected_bodies.get(i).copied().unwrap_or(true);
let alt = track.altitudes[app.almanac.current_step];
let (r, g, b) = track.color_rgb;
let label = if alt > 0.0 {
format!(" {} {} {:.1}°", track.symbol, track.name, alt)
} else {
format!(" {} {} below", track.symbol, track.name)
};
text.push(Line::from(Span::styled(
label,
Style::default().fg(Color::Rgb(r, g, b)),
)));
let style = if visible {
Style::default().fg(Color::Rgb(r, g, b))
} else {
Style::default().fg(Color::DarkGray).add_modifier(Modifier::DIM)
};
text.push(Line::from(Span::styled(label, style)));
}

let para =
Expand Down
Loading