ai_code_buddy/widgets/
credits.rs1use bevy::prelude::*;
2use bevy_ratatui::terminal::RatatuiContext;
3use crossterm::event::{KeyCode, KeyEventKind, MouseEventKind};
4use ratatui::{
5 layout::{Alignment, Constraint, Direction, Layout},
6 style::{Modifier, Style},
7 text::{Line, Span},
8 widgets::{Block, Borders, Clear, Paragraph},
9};
10
11use crate::{
12 bevy_states::app::AppState,
13 core::credits::{get_library_dependencies, get_project_contributors},
14 events::{app::AppEvent, credits::CreditsEvent},
15 theme::THEME,
16 widget_states::credits::{CreditsComponent, CreditsWidgetState},
17};
18
19pub struct CreditsPlugin;
20
21impl Plugin for CreditsPlugin {
22 fn build(&self, app: &mut App) {
23 app.add_event::<CreditsEvent>()
24 .init_resource::<CreditsWidgetState>()
25 .add_systems(
26 PreUpdate,
27 credits_event_handler.run_if(in_state(AppState::Credits)),
28 )
29 .add_systems(Update, render_credits.run_if(in_state(AppState::Credits)));
30 }
31}
32
33pub fn credits_event_handler(
34 mut credits_events: EventReader<CreditsEvent>,
35 mut credits_state: ResMut<CreditsWidgetState>,
36 mut app_events: EventWriter<AppEvent>,
37) {
38 for event in credits_events.read() {
39 match event {
40 CreditsEvent::KeyEvent(key_event) => {
41 if key_event.kind == KeyEventKind::Release {
42 match key_event.code {
43 KeyCode::Up => {
44 credits_state.scroll_up();
45 }
46 KeyCode::Down => {
47 credits_state.scroll_down();
48 }
49 KeyCode::PageUp => {
50 credits_state.scroll_offset =
51 credits_state.scroll_offset.saturating_sub(20);
52 }
53 KeyCode::PageDown => {
54 credits_state.scroll_offset += 20;
55 }
56 KeyCode::Home => {
57 credits_state.scroll_offset = 0;
58 }
59 KeyCode::End => {
60 credits_state.scroll_offset =
61 credits_state.total_lines.saturating_sub(20);
62 }
63 KeyCode::Enter | KeyCode::Esc => {
64 app_events.send(AppEvent::SwitchTo(AppState::Overview));
65 }
66 _ => {}
67 }
68 }
69 }
70 CreditsEvent::MouseEvent(mouse_event) => match mouse_event.kind {
71 MouseEventKind::Up(_) => {
72 let x = mouse_event.column;
73 let y = mouse_event.row;
74
75 if credits_state.is_over(CreditsComponent::BackToOverview, x, y) {
76 app_events.send(AppEvent::SwitchTo(AppState::Overview));
77 } else if credits_state.is_over(CreditsComponent::ScrollUp, x, y) {
78 credits_state.scroll_up();
79 } else if credits_state.is_over(CreditsComponent::ScrollDown, x, y) {
80 credits_state.scroll_down();
81 }
82 }
83 MouseEventKind::ScrollUp => {
84 credits_state.scroll_up();
85 }
86 MouseEventKind::ScrollDown => {
87 credits_state.scroll_down();
88 }
89 _ => {}
90 },
91 }
92 }
93}
94
95pub fn render_credits(
96 mut ratatui: ResMut<RatatuiContext>,
97 mut credits_state: ResMut<CreditsWidgetState>,
98) {
99 let _ = ratatui.draw(|frame| {
100 let area = frame.area();
101
102 frame.render_widget(Clear, area);
104
105 let main_layout = Layout::default()
107 .direction(Direction::Vertical)
108 .constraints([
109 Constraint::Length(3), Constraint::Min(10), Constraint::Length(3), ])
113 .split(area);
114
115 let header = Paragraph::new(Line::from(vec![
117 Span::styled("🎉 AI Code Buddy", Style::default().fg(THEME.primary)),
118 Span::raw(" - Credits & Acknowledgments"),
119 ]))
120 .alignment(Alignment::Center)
121 .block(Block::default().borders(Borders::ALL).title("Credits"));
122
123 frame.render_widget(header, main_layout[0]);
124
125 let credits_content = generate_credits_content(&mut credits_state);
127 frame.render_widget(credits_content, main_layout[1]);
128
129 let footer = Paragraph::new(Line::from(vec![
131 Span::styled("↑/↓", Style::default().add_modifier(Modifier::BOLD)),
132 Span::raw(" Scroll • "),
133 Span::styled("Enter/Esc", Style::default().add_modifier(Modifier::BOLD)),
134 Span::raw(" Back to Overview • "),
135 Span::styled("Mouse", Style::default().add_modifier(Modifier::BOLD)),
136 Span::raw(" Click to navigate"),
137 ]))
138 .alignment(Alignment::Center)
139 .block(Block::default().borders(Borders::ALL));
140
141 frame.render_widget(footer, main_layout[2]);
142 });
143}
144
145fn generate_credits_content(state: &mut CreditsWidgetState) -> Paragraph {
146 let mut lines = vec![Line::from("")];
147 lines.push(Line::from(vec![Span::styled(
148 "📚 About AI Code Buddy",
149 Style::default().add_modifier(Modifier::BOLD),
150 )]));
151 lines.push(Line::from(
152 "An intelligent code analysis tool with elegant Bevy-powered TUI",
153 ));
154 lines.push(Line::from(
155 "that provides comprehensive code reviews with AI assistance.",
156 ));
157 lines.push(Line::from(
158 "Repository: https://github.com/edgarhsanchez/ai_code_buddy",
159 ));
160 lines.push(Line::from(""));
161
162 lines.push(Line::from(vec![Span::styled(
164 "👥 Project Contributors",
165 Style::default().add_modifier(Modifier::BOLD),
166 )]));
167 lines.push(Line::from("─────────────────────"));
168
169 let contributors = get_project_contributors();
170 for contributor in contributors {
171 lines.push(Line::from(format!(
172 " • {} <{}> ({} commits)",
173 contributor.name, contributor.email, contributor.contributions
174 )));
175 }
176 lines.push(Line::from(""));
177
178 lines.push(Line::from(vec![Span::styled(
180 "📦 Library Dependencies & Licenses",
181 Style::default().add_modifier(Modifier::BOLD),
182 )]));
183 lines.push(Line::from("──────────────────────────────────"));
184
185 let libraries = get_library_dependencies();
186 for library in libraries {
187 lines.push(Line::from(format!(
188 "🔧 {} v{}",
189 library.name, library.version
190 )));
191 lines.push(Line::from(format!(" 📄 License: {}", library.license)));
192 lines.push(Line::from(format!(
193 " 📖 Description: {}",
194 library.description
195 )));
196 lines.push(Line::from(format!(
197 " 🔗 Repository: {}",
198 library.repository
199 )));
200 lines.push(Line::from(" 👥 Key Contributors:"));
201
202 for contributor in &library.contributors {
203 lines.push(Line::from(format!(" • {contributor}")));
204 }
205 lines.push(Line::from(""));
206 }
207
208 lines.push(Line::from(vec![Span::styled(
210 "🙏 Special Thanks",
211 Style::default().add_modifier(Modifier::BOLD),
212 )]));
213 lines.push(Line::from("─────────────────"));
214 lines.push(Line::from(" • The Rust Programming Language team"));
215 lines.push(Line::from(" • All open source contributors"));
216 lines.push(Line::from(" • The Bevy game engine community"));
217 lines.push(Line::from(" • The broader Rust ecosystem"));
218 lines.push(Line::from(""));
219
220 lines.push(Line::from(vec![Span::styled(
222 "💡 Want to contribute?",
223 Style::default().add_modifier(Modifier::BOLD),
224 )]));
225 lines.push(Line::from(
226 "Visit: https://github.com/edgarhsanchez/ai_code_buddy",
227 ));
228 lines.push(Line::from(
229 "🐛 Found a bug? Report it: https://github.com/edgarhsanchez/ai_code_buddy/issues",
230 ));
231
232 state.total_lines = lines.len();
234
235 let scroll = (state.scroll_offset as u16, 0);
237 Paragraph::new(lines).scroll(scroll).block(
238 Block::default()
239 .borders(Borders::ALL)
240 .title("Credits (Scrollable)"),
241 )
242}