use crate::core::{Color, Position, Rect, Style};
use crate::ontology::*;
use crate::runtime::Frame;
use crate::widget::Widget;
pub struct Image {
source: ImageSource,
alt: String,
style: Style,
agent_id: String,
fit: ImageFit,
}
#[derive(Clone)]
pub enum ImageSource {
Uri(String),
Rgba {
width: u32,
height: u32,
pixels: Vec<u8>,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ImageFit {
Cover,
#[default]
Contain,
Fill,
Original,
}
impl Image {
#[must_use]
pub fn from_uri(uri: impl Into<String>) -> Self {
Self {
source: ImageSource::Uri(uri.into()),
alt: String::new(),
style: Style::default(),
agent_id: String::new(),
fit: ImageFit::default(),
}
}
#[must_use]
pub fn from_rgba(width: u32, height: u32, pixels: Vec<u8>) -> Self {
Self {
source: ImageSource::Rgba {
width,
height,
pixels,
},
alt: String::new(),
style: Style::default(),
agent_id: String::new(),
fit: ImageFit::default(),
}
}
pub fn alt(mut self, alt: impl Into<String>) -> Self {
self.alt = alt.into();
self
}
pub fn agent_id(mut self, id: impl Into<String>) -> Self {
self.agent_id = id.into();
self
}
pub fn fit(mut self, fit: ImageFit) -> Self {
self.fit = fit;
self
}
}
impl Discoverable for Image {
fn schema(&self) -> WidgetSchema {
WidgetSchema::new("Image", "An image display", SemanticRole::Media)
}
fn capabilities(&self) -> Vec<AgentCapability> {
vec![AgentCapability::Zoomable {
min_zoom: 0.1,
max_zoom: 10.0,
}]
}
fn actions(&self) -> Vec<AgentAction> {
vec![]
}
fn semantic_role(&self) -> SemanticRole {
SemanticRole::Media
}
fn agent_state(&self) -> serde_json::Value {
match &self.source {
ImageSource::Uri(uri) => {
serde_json::json!({ "source": "uri", "uri": uri, "alt": self.alt, "fit": format!("{:?}", self.fit) })
}
ImageSource::Rgba { width, height, .. } => {
serde_json::json!({ "source": "rgba", "width": width, "height": height, "alt": self.alt, "fit": format!("{:?}", self.fit) })
}
}
}
fn execute_action(
&mut self,
_action: &str,
_params: &serde_json::Value,
) -> Result<serde_json::Value, String> {
Err("Image has no actions".to_string())
}
fn agent_id(&self) -> Option<&str> {
if self.agent_id.is_empty() {
None
} else {
Some(&self.agent_id)
}
}
fn accessibility_label(&self) -> Option<String> {
if self.alt.is_empty() {
None
} else {
Some(self.alt.clone())
}
}
}
impl Widget for Image {
fn render(self, area: Rect, frame: &mut Frame<'_>) {
if !self.agent_id.is_empty() {
let node = UiNode::new("Image", SemanticRole::Media)
.with_id(&self.agent_id)
.with_bounds(area.into())
.with_property("alt", serde_json::json!(self.alt));
frame.register_widget(node);
}
frame.painter().stroke_rect(area, Color::GRAY, 1.0, 0.0);
let label = if self.alt.is_empty() {
"[image]"
} else {
&self.alt
};
let mut ts = self.style.resolved_text();
if ts.font_size == 14.0 {
ts.font_size = 12.0;
}
if ts.color == Color::WHITE {
ts.color = Color::GRAY;
}
let sz = frame.painter().measure_text(label, &ts);
let tx = area.x + (area.width - sz.width) * 0.5;
let ty = area.y + (area.height - sz.height) * 0.5;
frame.painter().text(Position::new(tx, ty), label, &ts);
}
}