use presentar_core::{Canvas, Color, Point, TextStyle};
use presentar_terminal::direct::DirectTerminalCanvas;
use crate::analyzer::MudaType;
use crate::ptx::BugSeverity;
use super::TuiApp;
const COLOR_GREEN: Color = Color {
r: 0.3,
g: 1.0,
b: 0.3,
a: 1.0,
};
const COLOR_YELLOW: Color = Color {
r: 1.0,
g: 1.0,
b: 0.3,
a: 1.0,
};
const COLOR_RED: Color = Color {
r: 1.0,
g: 0.3,
b: 0.3,
a: 1.0,
};
const COLOR_BLUE: Color = Color {
r: 0.3,
g: 0.5,
b: 1.0,
a: 1.0,
};
const COLOR_TEXT: Color = Color {
r: 0.8,
g: 0.8,
b: 0.8,
a: 1.0,
};
fn draw_box_top(
canvas: &mut DirectTerminalCanvas<'_>,
title: &str,
x: f32,
y: f32,
width: u16,
color: Color,
) {
let style = TextStyle {
color,
..Default::default()
};
let inner = (width as usize).saturating_sub(2);
let title_part = format!("┌─ {} ", title);
let fill_len = inner.saturating_sub(title_part.len().saturating_sub(1));
let line = format!("{}{}┐", title_part, "─".repeat(fill_len));
canvas.draw_text(&line, Point::new(x, y), &style);
}
fn draw_box_bottom(
canvas: &mut DirectTerminalCanvas<'_>,
x: f32,
y: f32,
width: u16,
color: Color,
) {
let style = TextStyle {
color,
..Default::default()
};
let inner = (width as usize).saturating_sub(2);
let line = format!("└{}┘", "─".repeat(inner));
canvas.draw_text(&line, Point::new(x, y), &style);
}
fn draw_box_line(
canvas: &mut DirectTerminalCanvas<'_>,
text: &str,
x: f32,
y: f32,
width: u16,
text_color: Color,
border_color: Color,
) {
let border_style = TextStyle {
color: border_color,
..Default::default()
};
let text_style = TextStyle {
color: text_color,
..Default::default()
};
let inner = (width as usize).saturating_sub(4); let padded: String = if text.len() > inner {
text.chars().take(inner).collect()
} else {
format!("{:<width$}", text, width = inner)
};
canvas.draw_text("│ ", Point::new(x, y), &border_style);
canvas.draw_text(&padded, Point::new(x + 2.0, y), &text_style);
canvas.draw_text(" │", Point::new(x + 2.0 + inner as f32, y), &border_style);
}
pub(super) fn render_sidebar(
canvas: &mut DirectTerminalCanvas<'_>,
app: &TuiApp,
x: f32,
y: f32,
width: u16,
height: u16,
) {
let reg_h: u16 = 8;
let mem_h: u16 = 6;
let roof_h: u16 = 5;
let bug_h: u16 = 6;
let warn_h: u16 = height.saturating_sub(reg_h + mem_h + roof_h + bug_h);
let mut cy = y;
render_register_panel(canvas, app, x, cy, width, reg_h);
cy += f32::from(reg_h);
render_memory_panel(canvas, app, x, cy, width, mem_h);
cy += f32::from(mem_h);
render_roofline_panel(canvas, app, x, cy, width, roof_h);
cy += f32::from(roof_h);
render_bugs_panel(canvas, app, x, cy, width, bug_h);
cy += f32::from(bug_h);
render_warnings_panel(canvas, app, x, cy, width, warn_h);
}
fn render_register_panel(
canvas: &mut DirectTerminalCanvas<'_>,
app: &TuiApp,
x: f32,
y: f32,
width: u16,
_height: u16,
) {
let regs = &app.report.registers;
let total = regs.total();
let occupancy = app.report.estimated_occupancy;
let status_color = if total < 64 {
COLOR_GREEN
} else if total < 128 {
COLOR_YELLOW
} else {
COLOR_RED
};
draw_box_top(canvas, "Registers", x, y, width, status_color);
draw_box_line(
canvas,
&format!(".f32: {:3} / 255", regs.f32_regs),
x,
y + 1.0,
width,
COLOR_TEXT,
status_color,
);
draw_box_line(
canvas,
&format!(".b32: {:3} / 255", regs.b32_regs),
x,
y + 2.0,
width,
COLOR_TEXT,
status_color,
);
draw_box_line(
canvas,
&format!(".b64: {:3} / 255", regs.b64_regs),
x,
y + 3.0,
width,
COLOR_TEXT,
status_color,
);
draw_box_line(
canvas,
&format!(".pred: {:2} / 8", regs.pred_regs),
x,
y + 4.0,
width,
COLOR_TEXT,
status_color,
);
let occ_text = format!("Total: {} -> {:.0}% occ", total, occupancy * 100.0);
draw_box_line(
canvas,
&occ_text,
x,
y + 5.0,
width,
status_color,
status_color,
);
draw_box_line(canvas, "", x, y + 6.0, width, COLOR_TEXT, status_color);
draw_box_bottom(canvas, x, y + 7.0, width, status_color);
}
fn render_memory_panel(
canvas: &mut DirectTerminalCanvas<'_>,
app: &TuiApp,
x: f32,
y: f32,
width: u16,
_height: u16,
) {
let mem = &app.report.memory;
let coal_pct = mem.coalesced_ratio * 100.0;
let status_color = if coal_pct >= 90.0 {
COLOR_GREEN
} else if coal_pct >= 70.0 {
COLOR_YELLOW
} else {
COLOR_RED
};
draw_box_top(canvas, "Memory", x, y, width, status_color);
draw_box_line(
canvas,
&format!("Global ld: {}", mem.global_loads),
x,
y + 1.0,
width,
COLOR_TEXT,
status_color,
);
draw_box_line(
canvas,
&format!("Global st: {}", mem.global_stores),
x,
y + 2.0,
width,
COLOR_TEXT,
status_color,
);
let coal_text = format!("Coalesced: {:.1}%", coal_pct);
draw_box_line(
canvas,
&coal_text,
x,
y + 3.0,
width,
status_color,
status_color,
);
draw_box_line(canvas, "", x, y + 4.0, width, COLOR_TEXT, status_color);
draw_box_bottom(canvas, x, y + 5.0, width, status_color);
}
fn render_roofline_panel(
canvas: &mut DirectTerminalCanvas<'_>,
app: &TuiApp,
x: f32,
y: f32,
width: u16,
_height: u16,
) {
let roof = &app.report.roofline;
let (bound_text, bound_color) = if roof.memory_bound {
("Memory-bound", COLOR_YELLOW)
} else {
("Compute-bound", COLOR_GREEN)
};
draw_box_top(canvas, "Roofline", x, y, width, COLOR_BLUE);
draw_box_line(
canvas,
&format!("AI: {:.2} FLOP/B", roof.arithmetic_intensity),
x,
y + 1.0,
width,
COLOR_TEXT,
COLOR_BLUE,
);
draw_box_line(
canvas,
&format!("Bottleneck: {}", bound_text),
x,
y + 2.0,
width,
bound_color,
COLOR_BLUE,
);
draw_box_line(canvas, "", x, y + 3.0, width, COLOR_TEXT, COLOR_BLUE);
draw_box_bottom(canvas, x, y + 4.0, width, COLOR_BLUE);
}
fn render_bugs_panel(
canvas: &mut DirectTerminalCanvas<'_>,
app: &TuiApp,
x: f32,
y: f32,
width: u16,
_height: u16,
) {
let bug_report = &app.bug_report;
let critical = bug_report.count_by_severity(BugSeverity::Critical);
let high = bug_report.count_by_severity(BugSeverity::High);
let medium = bug_report.count_by_severity(BugSeverity::Medium);
let status_color = if critical > 0 {
COLOR_RED
} else if high > 0 {
COLOR_YELLOW
} else {
COLOR_GREEN
};
draw_box_top(canvas, "Bug Hunt", x, y, width, status_color);
if bug_report.bugs.is_empty() {
draw_box_line(
canvas,
"No bugs detected",
x,
y + 1.0,
width,
COLOR_GREEN,
status_color,
);
draw_box_line(canvas, "", x, y + 2.0, width, COLOR_TEXT, status_color);
draw_box_line(canvas, "", x, y + 3.0, width, COLOR_TEXT, status_color);
} else {
let c_color = if critical > 0 { COLOR_RED } else { COLOR_GREEN };
let h_color = if high > 0 { COLOR_YELLOW } else { COLOR_GREEN };
let m_color = if medium > 0 { COLOR_BLUE } else { COLOR_GREEN };
draw_box_line(
canvas,
&format!("P0 Critical: {}", critical),
x,
y + 1.0,
width,
c_color,
status_color,
);
draw_box_line(
canvas,
&format!("P1 High: {}", high),
x,
y + 2.0,
width,
h_color,
status_color,
);
draw_box_line(
canvas,
&format!("P2 Medium: {}", medium),
x,
y + 3.0,
width,
m_color,
status_color,
);
}
draw_box_line(canvas, "", x, y + 4.0, width, COLOR_TEXT, status_color);
draw_box_bottom(canvas, x, y + 5.0, width, status_color);
}
fn render_warnings_panel(
canvas: &mut DirectTerminalCanvas<'_>,
app: &TuiApp,
x: f32,
y: f32,
width: u16,
height: u16,
) {
let border_color = if app.report.warnings.is_empty() {
COLOR_GREEN
} else {
COLOR_YELLOW
};
draw_box_top(canvas, "Muda (Waste)", x, y, width, border_color);
if app.report.warnings.is_empty() {
draw_box_line(
canvas,
"No Muda detected",
x,
y + 1.0,
width,
COLOR_GREEN,
border_color,
);
for row in 2..height.saturating_sub(1) {
draw_box_line(
canvas,
"",
x,
y + f32::from(row),
width,
COLOR_TEXT,
border_color,
);
}
} else {
let max_items = (height as usize).saturating_sub(2);
for (i, w) in app.report.warnings.iter().take(max_items).enumerate() {
let icon = match w.muda_type {
MudaType::Transport => "! ",
MudaType::Waiting => "~ ",
MudaType::Overprocessing => "@ ",
};
let text = format!("{}{}", icon, w.description);
draw_box_line(
canvas,
&text,
x,
y + 1.0 + i as f32,
width,
COLOR_YELLOW,
border_color,
);
}
let used = app.report.warnings.len().min(max_items);
for row in (used + 1)..height.saturating_sub(1) as usize {
draw_box_line(
canvas,
"",
x,
y + row as f32,
width,
COLOR_TEXT,
border_color,
);
}
}
draw_box_bottom(canvas, x, y + f32::from(height - 1), width, border_color);
}