#![allow(dead_code)]
#[derive(Debug, Clone)]
pub struct OnscreenTextConfig {
pub default_size: f32,
pub max_labels: usize,
pub default_color: [u8; 4],
}
#[derive(Debug, Clone)]
pub struct TextLabel {
pub id: u32,
pub position: [f32; 2],
pub content: String,
pub size: f32,
pub color: [u8; 4],
pub visible: bool,
}
#[derive(Debug, Clone)]
pub struct OnscreenTextState {
pub labels: Vec<TextLabel>,
next_id: u32,
}
pub fn default_onscreen_text_config() -> OnscreenTextConfig {
OnscreenTextConfig {
default_size: 14.0,
max_labels: 128,
default_color: [255, 255, 255, 255],
}
}
pub fn new_onscreen_text() -> OnscreenTextState {
OnscreenTextState {
labels: Vec::new(),
next_id: 1,
}
}
pub fn text_add_label(
state: &mut OnscreenTextState,
cfg: &OnscreenTextConfig,
position: [f32; 2],
content: &str,
) -> u32 {
if state.labels.len() >= cfg.max_labels {
return 0;
}
let id = state.next_id;
state.next_id += 1;
state.labels.push(TextLabel {
id,
position,
content: content.to_string(),
size: cfg.default_size,
color: cfg.default_color,
visible: true,
});
id
}
pub fn text_remove_label(state: &mut OnscreenTextState, id: u32) -> bool {
if let Some(pos) = state.labels.iter().position(|l| l.id == id) {
state.labels.remove(pos);
true
} else {
false
}
}
pub fn text_update_label(
state: &mut OnscreenTextState,
id: u32,
position: [f32; 2],
content: &str,
) -> bool {
if let Some(label) = state.labels.iter_mut().find(|l| l.id == id) {
label.position = position;
label.content = content.to_string();
true
} else {
false
}
}
pub fn text_label_count(state: &OnscreenTextState) -> usize {
state.labels.len()
}
pub fn text_find_label(state: &OnscreenTextState, id: u32) -> Option<&TextLabel> {
state.labels.iter().find(|l| l.id == id)
}
pub fn text_set_visible(state: &mut OnscreenTextState, id: u32, visible: bool) -> bool {
if let Some(label) = state.labels.iter_mut().find(|l| l.id == id) {
label.visible = visible;
true
} else {
false
}
}
pub fn text_to_json(state: &OnscreenTextState, _cfg: &OnscreenTextConfig) -> String {
let mut out = String::from("{\"labels\":[");
let len = state.labels.len();
for (i, l) in state.labels.iter().enumerate() {
let comma = if i + 1 < len { "," } else { "" };
out.push_str(&format!(
"{{\"id\":{},\"x\":{:.2},\"y\":{:.2},\"content\":\"{}\",\
\"size\":{:.2},\"color\":[{},{},{},{}],\"visible\":{}}}{comma}",
l.id,
l.position[0], l.position[1],
l.content,
l.size,
l.color[0], l.color[1], l.color[2], l.color[3],
l.visible
));
}
out.push_str("]}");
out
}
pub fn text_clear(state: &mut OnscreenTextState) {
state.labels.clear();
state.next_id = 1;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_config_values() {
let cfg = default_onscreen_text_config();
assert!((cfg.default_size - 14.0).abs() < 1e-6);
assert_eq!(cfg.max_labels, 128);
assert_eq!(cfg.default_color, [255, 255, 255, 255]);
}
#[test]
fn new_state_is_empty() {
let s = new_onscreen_text();
assert_eq!(text_label_count(&s), 0);
}
#[test]
fn add_label_increments_count() {
let mut s = new_onscreen_text();
let cfg = default_onscreen_text_config();
text_add_label(&mut s, &cfg, [100.0, 200.0], "Hello");
assert_eq!(text_label_count(&s), 1);
}
#[test]
fn add_label_returns_unique_ids() {
let mut s = new_onscreen_text();
let cfg = default_onscreen_text_config();
let id1 = text_add_label(&mut s, &cfg, [0.0, 0.0], "A");
let id2 = text_add_label(&mut s, &cfg, [0.0, 0.0], "B");
assert_ne!(id1, id2);
assert!(id1 > 0);
assert!(id2 > 0);
}
#[test]
fn remove_label_decrements_count() {
let mut s = new_onscreen_text();
let cfg = default_onscreen_text_config();
let id = text_add_label(&mut s, &cfg, [10.0, 20.0], "Test");
assert!(text_remove_label(&mut s, id));
assert_eq!(text_label_count(&s), 0);
}
#[test]
fn remove_nonexistent_returns_false() {
let mut s = new_onscreen_text();
assert!(!text_remove_label(&mut s, 999));
}
#[test]
fn update_label_changes_content() {
let mut s = new_onscreen_text();
let cfg = default_onscreen_text_config();
let id = text_add_label(&mut s, &cfg, [0.0, 0.0], "Old");
text_update_label(&mut s, id, [5.0, 5.0], "New");
let found = text_find_label(&s, id).expect("should succeed");
assert_eq!(found.content, "New");
assert!((found.position[0] - 5.0).abs() < 1e-6);
}
#[test]
fn set_visible_false() {
let mut s = new_onscreen_text();
let cfg = default_onscreen_text_config();
let id = text_add_label(&mut s, &cfg, [0.0, 0.0], "Label");
text_set_visible(&mut s, id, false);
assert!(!text_find_label(&s, id).expect("should succeed").visible);
}
#[test]
fn json_contains_labels_key() {
let mut s = new_onscreen_text();
let cfg = default_onscreen_text_config();
text_add_label(&mut s, &cfg, [100.0, 50.0], "FPS: 60");
let json = text_to_json(&s, &cfg);
assert!(json.contains("\"labels\""));
assert!(json.contains("FPS: 60"));
}
#[test]
fn max_labels_cap() {
let mut s = new_onscreen_text();
let mut cfg = default_onscreen_text_config();
cfg.max_labels = 2;
text_add_label(&mut s, &cfg, [0.0, 0.0], "a");
text_add_label(&mut s, &cfg, [0.0, 0.0], "b");
let id = text_add_label(&mut s, &cfg, [0.0, 0.0], "c");
assert_eq!(id, 0);
assert_eq!(text_label_count(&s), 2);
}
#[test]
fn clear_resets_state() {
let mut s = new_onscreen_text();
let cfg = default_onscreen_text_config();
text_add_label(&mut s, &cfg, [0.0, 0.0], "x");
text_clear(&mut s);
assert_eq!(text_label_count(&s), 0);
}
}