use crate::tui::ecs::components::{Label, Position, Sprite, TermColor, ZIndex};
use crate::tui::ecs::world::*;
use crate::tui::group::EntityGroup;
pub fn word_wrap(text: &str, max_width: usize) -> Vec<String> {
if max_width == 0 {
return Vec::new();
}
let mut lines = Vec::new();
for paragraph in text.split('\n') {
if paragraph.is_empty() {
lines.push(String::new());
continue;
}
let words: Vec<&str> = paragraph.split_whitespace().collect();
if words.is_empty() {
lines.push(String::new());
continue;
}
let mut current_line = String::new();
for word in words {
if current_line.is_empty() {
if word.len() > max_width {
let mut remaining = word;
while remaining.len() > max_width {
lines.push(remaining[..max_width].to_string());
remaining = &remaining[max_width..];
}
current_line = remaining.to_string();
} else {
current_line = word.to_string();
}
} else if current_line.len() + 1 + word.len() <= max_width {
current_line.push(' ');
current_line.push_str(word);
} else {
lines.push(current_line);
if word.len() > max_width {
let mut remaining = word;
while remaining.len() > max_width {
lines.push(remaining[..max_width].to_string());
remaining = &remaining[max_width..];
}
current_line = remaining.to_string();
} else {
current_line = word.to_string();
}
}
}
if !current_line.is_empty() {
lines.push(current_line);
}
}
lines
}
pub struct MenuColors {
pub normal_foreground: TermColor,
pub normal_background: TermColor,
pub selected_foreground: TermColor,
pub selected_background: TermColor,
}
impl Default for MenuColors {
fn default() -> Self {
Self {
normal_foreground: TermColor::White,
normal_background: TermColor::Black,
selected_foreground: TermColor::Black,
selected_background: TermColor::White,
}
}
}
pub struct Menu {
items: Vec<String>,
selected_index: usize,
position_column: f64,
position_row: f64,
colors: MenuColors,
z_index: i32,
entities: EntityGroup,
}
impl Menu {
pub fn new(
items: Vec<String>,
position_column: f64,
position_row: f64,
colors: MenuColors,
z_index: i32,
) -> Self {
Self {
items,
selected_index: 0,
position_column,
position_row,
colors,
z_index,
entities: EntityGroup::new(),
}
}
pub fn up(&mut self) {
if self.selected_index > 0 {
self.selected_index -= 1;
}
}
pub fn down(&mut self) {
if self.selected_index + 1 < self.items.len() {
self.selected_index += 1;
}
}
pub fn select_at(&mut self, index: usize) {
if index < self.items.len() {
self.selected_index = index;
}
}
pub fn selected_index(&self) -> usize {
self.selected_index
}
pub fn selected(&self) -> &str {
&self.items[self.selected_index]
}
pub fn render(&mut self, world: &mut World) {
self.entities.despawn_all(world);
for (item_index, item) in self.items.iter().enumerate() {
let is_selected = item_index == self.selected_index;
let foreground = if is_selected {
self.colors.selected_foreground
} else {
self.colors.normal_foreground
};
let background = if is_selected {
self.colors.selected_background
} else {
self.colors.normal_background
};
let display = if is_selected {
format!("> {}", item)
} else {
format!(" {}", item)
};
let entity = self.entities.spawn_one(world, POSITION | LABEL | Z_INDEX);
world.set_position(
entity,
Position {
column: self.position_column,
row: self.position_row + item_index as f64,
},
);
world.set_label(
entity,
Label {
text: display,
foreground,
background,
},
);
world.set_z_index(entity, ZIndex(self.z_index));
}
}
pub fn despawn(&mut self, world: &mut World) {
self.entities.despawn_all(world);
}
}
pub struct ProgressBarColors {
pub filled_foreground: TermColor,
pub filled_background: TermColor,
pub empty_foreground: TermColor,
pub empty_background: TermColor,
}
pub struct ProgressBar {
width: usize,
position_column: f64,
position_row: f64,
colors: ProgressBarColors,
z_index: i32,
entities: EntityGroup,
}
impl ProgressBar {
pub fn new(
width: usize,
position_column: f64,
position_row: f64,
colors: ProgressBarColors,
z_index: i32,
) -> Self {
Self {
width,
position_column,
position_row,
colors,
z_index,
entities: EntityGroup::new(),
}
}
pub fn render(&mut self, world: &mut World, fraction: f64) {
self.entities.despawn_all(world);
let clamped = fraction.clamp(0.0, 1.0);
let filled_count = (clamped * self.width as f64).round() as usize;
for cell_index in 0..self.width {
let is_filled = cell_index < filled_count;
let character = if is_filled { '█' } else { '░' };
let foreground = if is_filled {
self.colors.filled_foreground
} else {
self.colors.empty_foreground
};
let background = if is_filled {
self.colors.filled_background
} else {
self.colors.empty_background
};
let entity = self.entities.spawn_one(world, POSITION | SPRITE | Z_INDEX);
world.set_position(
entity,
Position {
column: self.position_column + cell_index as f64,
row: self.position_row,
},
);
world.set_sprite(
entity,
Sprite {
character,
foreground,
background,
},
);
world.set_z_index(entity, ZIndex(self.z_index));
}
}
pub fn despawn(&mut self, world: &mut World) {
self.entities.despawn_all(world);
}
}