1use ratatui::{prelude::*, widgets::*};
2
3use crate::format;
4use crate::game::state::GameState;
5use crate::i18n::t;
6
7#[derive(Clone, Copy, Default)]
18pub struct PrestigeRects {
19 pub reset: Rect,
20 pub yes: Rect,
21 pub no: Rect,
22}
23
24pub fn draw(
25 frame: &mut Frame,
26 area: Rect,
27 state: &GameState,
28 mouse_pos: Option<(u16, u16)>,
29 confirm_pending: bool,
30) -> PrestigeRects {
31 let lang = t();
32 let owned = state.prestige;
33 let available = state.prestige_available();
34 let bonus_pct = state.prestige as f64;
35 let next_threshold = owned
40 .saturating_add(1)
41 .saturating_mul(owned.saturating_add(1))
42 .saturating_mul(1_000_000);
43
44 let block = Block::bordered().title(lang.prestige_title);
50 let inner = block.inner(area);
51 frame.render_widget(block, area);
52
53 let mut rects = PrestigeRects::default();
54 if inner.width == 0 || inner.height == 0 {
55 return rects;
56 }
57
58 let mut info_lines: Vec<Line> = Vec::new();
60 info_lines.push(Line::from(vec![
61 Span::raw(format!(" {}: ", lang.prestige_owned_label)),
62 Span::styled(
63 format::big(owned as f64),
64 Style::default()
65 .fg(Color::Rgb(255, 215, 0))
66 .add_modifier(Modifier::BOLD),
67 ),
68 Span::raw(format!(" ({})", lang.prestige_currency)),
69 ]));
70 info_lines.push(Line::raw(""));
71 info_lines.push(Line::from(vec![
72 Span::raw(format!(" {}: ", lang.prestige_bonus_label)),
73 Span::styled(
74 format!("+{:.0}% {}", bonus_pct, lang.fps_unit),
75 Style::default().fg(Color::Rgb(120, 230, 120)),
76 ),
77 ]));
78 if available > 0 {
79 info_lines.push(Line::raw(""));
80 info_lines.push(Line::from(vec![
81 Span::raw(format!(" {}: ", lang.prestige_available_label)),
82 Span::styled(
83 format!("+{}", format::big(available as f64)),
84 Style::default()
85 .fg(Color::Rgb(255, 215, 0))
86 .add_modifier(Modifier::BOLD),
87 ),
88 ]));
89 } else {
90 info_lines.push(Line::raw(""));
91 for l in lang.prestige_not_enough.lines() {
92 info_lines.push(Line::from(Span::styled(
93 format!(" {l}"),
94 Style::default().fg(Color::DarkGray),
95 )));
96 }
97 info_lines.push(Line::raw(""));
98 info_lines.push(Line::from(vec![
99 Span::raw(format!(" {}: ", lang.prestige_lifetime_needed)),
100 Span::styled(
101 format::big(next_threshold as f64),
102 Style::default().fg(Color::Rgb(200, 180, 140)),
103 ),
104 ]));
105 }
106
107 let action_lines: Vec<(Line, Option<ActionTarget>)> = if available == 0 {
115 Vec::new()
116 } else if confirm_pending {
117 let mut v: Vec<(Line, Option<ActionTarget>)> = Vec::new();
118 v.push((
119 Line::from(Span::styled(
120 format!(" {}", lang.prestige_confirm_question),
121 Style::default()
122 .fg(Color::Rgb(255, 90, 90))
123 .add_modifier(Modifier::BOLD),
124 )),
125 None,
126 ));
127 for chunk in lang.prestige_confirm_warning.lines() {
128 v.push((
129 Line::from(Span::styled(
130 format!(" {chunk}"),
131 Style::default().fg(Color::Rgb(220, 180, 120)),
132 )),
133 None,
134 ));
135 }
136 v.push((Line::raw(""), None));
137 v.push((
138 Line::from(Span::styled(
139 format!(" {}", lang.prestige_confirm_yes),
140 Style::default()
141 .fg(Color::Rgb(255, 100, 100))
142 .add_modifier(Modifier::BOLD),
143 )),
144 Some(ActionTarget::Yes),
145 ));
146 v.push((Line::raw(""), None));
149 v.push((
150 Line::from(Span::styled(
151 format!(" {}", lang.prestige_confirm_no),
152 Style::default()
153 .fg(Color::Rgb(120, 220, 120))
154 .add_modifier(Modifier::BOLD),
155 )),
156 Some(ActionTarget::No),
157 ));
158 v
159 } else {
160 vec![(
161 Line::from(Span::styled(
162 format!(" {}", lang.prestige_confirm_hint),
163 Style::default().fg(Color::Rgb(220, 140, 255)),
164 )),
165 Some(ActionTarget::Reset),
166 )]
167 };
168
169 let action_h = action_lines.len() as u16;
170 let chunks = if action_h > 0 && action_h < inner.height {
171 Layout::vertical([Constraint::Min(1), Constraint::Length(action_h)]).split(inner)
172 } else {
173 Layout::vertical([Constraint::Min(1), Constraint::Length(0)]).split(inner)
177 };
178 let info_area = chunks[0];
179 let action_area = chunks[1];
180
181 let info_para = Paragraph::new(info_lines).wrap(Wrap { trim: false });
185 frame.render_widget(info_para, info_area);
186
187 if action_h == 0 || action_area.height == 0 {
188 return rects;
189 }
190 let mut action_para_lines: Vec<Line> = Vec::with_capacity(action_lines.len());
195 for (line, target) in action_lines.iter() {
196 let row_y = action_area.y + action_para_lines.len() as u16;
197 let rect = Rect {
198 x: action_area.x,
199 y: row_y,
200 width: action_area.width,
201 height: 1,
202 };
203 match target {
204 Some(ActionTarget::Reset) => rects.reset = rect,
205 Some(ActionTarget::Yes) => rects.yes = rect,
206 Some(ActionTarget::No) => rects.no = rect,
207 None => {}
208 }
209 action_para_lines.push(line.clone());
210 }
211 let action_para = Paragraph::new(action_para_lines);
212 frame.render_widget(action_para, action_area);
213
214 if let Some((mx, my)) = mouse_pos {
216 let buf = frame.buffer_mut();
217 for r in [rects.reset, rects.yes, rects.no] {
218 if r.width == 0 {
219 continue;
220 }
221 if !(mx >= r.x && mx < r.x + r.width && my == r.y) {
222 continue;
223 }
224 for dx in 0..r.width {
225 let cx = r.x + dx;
226 if cx >= buf.area.x + buf.area.width {
227 break;
228 }
229 let cell = &mut buf[(cx, r.y)];
230 cell.set_fg(Color::Rgb(255, 255, 255));
231 cell.set_bg(Color::Rgb(40, 30, 50));
232 cell.modifier.insert(Modifier::BOLD);
233 }
234 }
235 }
236 rects
237}
238
239#[derive(Copy, Clone)]
240enum ActionTarget {
241 Reset,
242 Yes,
243 No,
244}