use pixtuoid_core::sprite::format::RECOLOR_KEYS;
use pixtuoid_core::sprite::{Frame, Palette, Pixel, Rgb, RgbBuffer};
use pixtuoid_core::AgentSlot;
use crate::tui::pose;
#[derive(Clone, Copy)]
struct Outfit {
shirt: Rgb,
pants: Rgb,
}
const OUTFITS_WARM: &[Outfit] = &[
Outfit {
shirt: Rgb {
r: 0xee,
g: 0xe1,
b: 0xc6,
},
pants: Rgb {
r: 0x4a,
g: 0x2b,
b: 0x3d,
},
},
Outfit {
shirt: Rgb {
r: 0xc9,
g: 0x7b,
b: 0x5e,
},
pants: Rgb {
r: 0x6b,
g: 0x57,
b: 0x3d,
},
},
Outfit {
shirt: Rgb {
r: 0xc9,
g: 0xa2,
b: 0x4b,
},
pants: Rgb {
r: 0x4a,
g: 0x52,
b: 0x34,
},
},
Outfit {
shirt: Rgb {
r: 0x8a,
g: 0x2c,
b: 0x36,
},
pants: Rgb {
r: 0x5a,
g: 0x4e,
b: 0x42,
},
},
Outfit {
shirt: Rgb {
r: 0xd7,
g: 0x7a,
b: 0x61,
},
pants: Rgb {
r: 0x27,
g: 0x33,
b: 0x4a,
},
},
Outfit {
shirt: Rgb {
r: 0xb8,
g: 0x99,
b: 0x68,
},
pants: Rgb {
r: 0x3d,
g: 0x2a,
b: 0x1f,
},
},
Outfit {
shirt: Rgb {
r: 0xa5,
g: 0x4f,
b: 0x2c,
},
pants: Rgb {
r: 0xcd,
g: 0xc0,
b: 0xa3,
},
},
Outfit {
shirt: Rgb {
r: 0xe0,
g: 0x90,
b: 0x7c,
},
pants: Rgb {
r: 0x3a,
g: 0x32,
b: 0x2e,
},
},
];
const OUTFITS_COOL: &[Outfit] = &[
Outfit {
shirt: Rgb {
r: 0xa4,
g: 0xb5,
b: 0x95,
},
pants: Rgb {
r: 0x33,
g: 0x36,
b: 0x3d,
},
},
Outfit {
shirt: Rgb {
r: 0x9b,
g: 0xb5,
b: 0xc8,
},
pants: Rgb {
r: 0x3c,
g: 0x44,
b: 0x52,
},
},
Outfit {
shirt: Rgb {
r: 0xa2,
g: 0x90,
b: 0xb0,
},
pants: Rgb {
r: 0x3c,
g: 0x2a,
b: 0x1e,
},
},
Outfit {
shirt: Rgb {
r: 0x3f,
g: 0x61,
b: 0x4c,
},
pants: Rgb {
r: 0x7a,
g: 0x67,
b: 0x48,
},
},
Outfit {
shirt: Rgb {
r: 0x3e,
g: 0x7a,
b: 0x85,
},
pants: Rgb {
r: 0xc7,
g: 0xb6,
b: 0x96,
},
},
Outfit {
shirt: Rgb {
r: 0x3f,
g: 0x4a,
b: 0x75,
},
pants: Rgb {
r: 0x8a,
g: 0x84,
b: 0x7a,
},
},
Outfit {
shirt: Rgb {
r: 0x6b,
g: 0x84,
b: 0xa0,
},
pants: Rgb {
r: 0x2a,
g: 0x33,
b: 0x4a,
},
},
Outfit {
shirt: Rgb {
r: 0x47,
g: 0x69,
b: 0x5a,
},
pants: Rgb {
r: 0xb8,
g: 0xae,
b: 0x95,
},
},
];
const HAIR_PRESETS: &[Rgb] = &[
Rgb {
r: 0x14,
g: 0x0a,
b: 0x06,
}, Rgb {
r: 0x2a,
g: 0x1a,
b: 0x0e,
}, Rgb {
r: 0x52,
g: 0x32,
b: 0x10,
}, Rgb {
r: 0x8a,
g: 0x5a,
b: 0x36,
}, Rgb {
r: 0xc7,
g: 0xa3,
b: 0x4a,
}, Rgb {
r: 0xd8,
g: 0x68,
b: 0x32,
}, Rgb {
r: 0x7a,
g: 0x32,
b: 0x10,
}, Rgb {
r: 0xa8,
g: 0xa8,
b: 0xb0,
}, ];
const SKIN_PRESETS: &[Rgb] = &[
Rgb {
r: 0xf4,
g: 0xc7,
b: 0x9a,
}, Rgb {
r: 0xe0,
g: 0xa8,
b: 0x70,
}, Rgb {
r: 0xb8,
g: 0x80,
b: 0x50,
}, Rgb {
r: 0x8a,
g: 0x5a,
b: 0x36,
}, Rgb {
r: 0xc8,
g: 0x9a,
b: 0x64,
}, ];
pub(super) fn agent_palette(base: &Palette, agent: &AgentSlot, glow_tint: Option<Rgb>) -> Palette {
let seed = agent.agent_id.raw() as usize;
let p = pose::personality_for(agent.agent_id);
let outfits = if p.trip_chance_pct >= 30 {
OUTFITS_WARM
} else {
OUTFITS_COOL
};
let outfit = outfits[seed % outfits.len()];
let hair = HAIR_PRESETS[(seed / 7) % HAIR_PRESETS.len()];
let skin = SKIN_PRESETS[(seed / 13) % SKIN_PRESETS.len()];
let final_skin = if let Some(tint) = glow_tint {
blend_rgb(skin, tint, 0.18)
} else {
skin
};
base.with_override('B', Some(outfit.shirt))
.with_override('H', Some(hair))
.with_override('S', Some(final_skin))
.with_override('P', Some(outfit.pants))
}
pub(super) fn tool_glow_tint(
agent: &AgentSlot,
glow: &crate::tui::theme::ToolGlowColors,
) -> Option<Rgb> {
use pixtuoid_core::state::ActivityState;
let detail = match &agent.state {
ActivityState::Active { detail, .. } => detail.as_deref(),
_ => return None,
};
let token = detail
.and_then(|d| d.split(|c: char| !c.is_alphanumeric()).next())
.unwrap_or("");
Some(match token {
"Edit" | "Write" | "MultiEdit" => glow.edit,
"Read" => glow.read,
"Bash" => glow.bash,
"Agent" | "Task" | "Delegating" => glow.agent,
"Grep" | "Glob" => glow.grep,
_ => glow.default,
})
}
pub(super) fn recolor_frame(frame: &Frame, pal: &Palette, base_pal: &Palette) -> Frame {
let swaps: Vec<(Pixel, Pixel)> = RECOLOR_KEYS
.iter()
.map(|&k| (base_pal.get(k).flatten(), pal.get(k).flatten()))
.collect();
let pixels: Vec<Pixel> = frame
.pixels
.iter()
.map(|p| match p {
Some(rgb) => swaps
.iter()
.find(|(base, _)| *base == Some(*rgb))
.map_or(*p, |(_, agent)| *agent),
None => None,
})
.collect();
Frame {
width: frame.width,
height: frame.height,
pixels,
}
}
pub(super) fn degraded_pixel(c: Rgb) -> Rgb {
let lum = ((c.r as f32) * 0.30 + (c.g as f32) * 0.59 + (c.b as f32) * 0.11) as u8;
let gray = Rgb {
r: lum,
g: lum,
b: lum,
};
let desat = blend_rgb(c, gray, 0.55); let sick = Rgb {
r: 150,
g: 40,
b: 40,
};
let tinted = blend_rgb(desat, sick, 0.45); blend_rgb(
tinted,
Rgb { r: 0, g: 0, b: 0 },
0.18, )
}
pub(super) fn degraded_frame(frame: &Frame) -> Frame {
Frame {
width: frame.width,
height: frame.height,
pixels: frame
.pixels
.iter()
.map(|&p| p.map(degraded_pixel))
.collect(),
}
}
pub(super) fn lerp_rgb(a: Rgb, b: Rgb, t: f32) -> Rgb {
mix_lab(a, b, t)
}
pub(super) fn bell(x: f32, c: f32, w: f32) -> f32 {
let d = (x - c) / w;
(1.0 - d * d).max(0.0)
}
pub(super) fn blend(a: u8, b: u8, t: f32) -> u8 {
((a as f32) * (1.0 - t) + (b as f32) * t)
.round()
.clamp(0.0, 255.0) as u8
}
pub(super) fn blend_rgb(a: Rgb, b: Rgb, t: f32) -> Rgb {
Rgb {
r: blend(a.r, b.r, t),
g: blend(a.g, b.g, t),
b: blend(a.b, b.b, t),
}
}
pub(super) fn blend_over(buf: &RgbBuffer, x: u16, y: u16, tint: Rgb, t: f32) -> Rgb {
blend_rgb(buf.get(x, y), tint, t)
}
pub(super) fn mix_lab(a: Rgb, b: Rgb, t: f32) -> Rgb {
use palette::{FromColor, IntoColor, Lab, Mix, Srgb};
let sa = Srgb::new(a.r as f32 / 255.0, a.g as f32 / 255.0, a.b as f32 / 255.0);
let sb = Srgb::new(b.r as f32 / 255.0, b.g as f32 / 255.0, b.b as f32 / 255.0);
let la = Lab::from_color(sa);
let lb = Lab::from_color(sb);
let mixed: Srgb = la.mix(lb, t.clamp(0.0, 1.0)).into_color();
Rgb {
r: (mixed.red.clamp(0.0, 1.0) * 255.0).round() as u8,
g: (mixed.green.clamp(0.0, 1.0) * 255.0).round() as u8,
b: (mixed.blue.clamp(0.0, 1.0) * 255.0).round() as u8,
}
}