ai_code_buddy/widgets/
credits.rs

1use 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        // Clear the frame
103        frame.render_widget(Clear, area);
104
105        // Create main layout
106        let main_layout = Layout::default()
107            .direction(Direction::Vertical)
108            .constraints([
109                Constraint::Length(3), // Header
110                Constraint::Min(10),   // Credits content
111                Constraint::Length(3), // Footer
112            ])
113            .split(area);
114
115        // Render header
116        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        // Render credits content
126        let credits_content = generate_credits_content(&mut credits_state);
127        frame.render_widget(credits_content, main_layout[1]);
128
129        // Render footer
130        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    // Project Contributors
163    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    // Library Dependencies
179    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    // Special Thanks
209    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    // Call to Action
221    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    // Update total lines count
233    state.total_lines = lines.len();
234
235    // Create scrollable paragraph
236    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}