use serde::{Deserialize, Serialize};
use crate::i18n::Lang;
use crate::input::Button;
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct WindowInfo {
pub title: String,
pub app_name: String,
pub pid: u32,
pub x: i32,
pub y: i32,
pub width: u32,
pub height: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum Action {
Click {
button: Button,
#[serde(skip_serializing_if = "Option::is_none")]
x: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
y: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
rel_x: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
rel_y: Option<i32>,
},
Text { content: String },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Step {
pub index: usize,
pub timestamp_ms: u128,
pub elapsed_ms: u128,
pub action: Action,
#[serde(skip_serializing_if = "Option::is_none")]
pub window: Option<WindowInfo>,
#[serde(skip_serializing_if = "Option::is_none")]
pub screenshot: Option<String>,
pub description: String,
}
impl Step {
pub fn build_description(action: &Action, window: Option<&WindowInfo>, lang: Lang) -> String {
let target = window
.map(|w| {
if w.app_name.is_empty() {
format!("« {} »", w.title)
} else {
format!("« {} » ({})", w.title, w.app_name)
}
})
.unwrap_or_else(|| match lang {
Lang::Fr => "la fenêtre active".to_string(),
Lang::En => "the active window".to_string(),
});
match action {
Action::Click { button, .. } => {
let b = button.label(lang);
match lang {
Lang::Fr => format!("Clic {b} sur {target}"),
Lang::En => format!("{} click on {target}", capitalize(&b)),
}
}
Action::Text { content } => {
let shown = content.replace('\n', "⏎");
match lang {
Lang::Fr => format!("Saisie « {shown} » dans {target}"),
Lang::En => format!("Typed “{shown}” in {target}"),
}
}
}
}
}
fn capitalize(s: &str) -> String {
let mut c = s.chars();
match c.next() {
Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
None => String::new(),
}
}