Merge c4/tree-diff: screenshot diffs (C4)
This commit is contained in:
commit
7b3a9fc4d5
|
|
@ -2092,6 +2092,17 @@ fn box_look(script: Option<&str>, after: Option<String>, pointer: bool) -> Resul
|
||||||
} else {
|
} else {
|
||||||
text.push_str(&format!("\n可以操作的東西(圖上同樣的編號,用 ref 點):\n{}", lines.join("\n")));
|
text.push_str(&format!("\n可以操作的東西(圖上同樣的編號,用 ref 點):\n{}", lines.join("\n")));
|
||||||
}
|
}
|
||||||
|
// What moved since the last look (same signal the Mac path gives the quick model).
|
||||||
|
static LAST_ELS: Mutex<Vec<crate::marks::Element>> = Mutex::new(Vec::new());
|
||||||
|
{
|
||||||
|
let cur: Vec<crate::marks::Element> = numbered.iter().map(|(_, e)| e.clone()).collect();
|
||||||
|
let mut last = LAST_ELS.lock().unwrap_or_else(|e| e.into_inner());
|
||||||
|
let d = crate::marks::diff(&last, &cur);
|
||||||
|
*last = cur;
|
||||||
|
if !d.is_empty() {
|
||||||
|
text.push_str(&format!("\n跟上一張圖相比:\n{}", d.join("\n")));
|
||||||
|
}
|
||||||
|
}
|
||||||
// Step timing (for the 電腦 card): how long the box's side took vs. the whole step.
|
// Step timing (for the 電腦 card): how long the box's side took vs. the whole step.
|
||||||
{
|
{
|
||||||
let total_ms = t_all.elapsed().as_millis() as u64;
|
let total_ms = t_all.elapsed().as_millis() as u64;
|
||||||
|
|
|
||||||
|
|
@ -1169,6 +1169,17 @@ fn look(after: Option<String>, pointer: bool) -> Result<(String, Option<String>)
|
||||||
text.push_str(&format!("\n讀不到最前面「{front}」視窗裡的按鈕{why}:下面只有選單列。視窗裡的東西照圖上的位置點 x、y,或等一下再截一次圖。\n{}", lines.join("\n")));
|
text.push_str(&format!("\n讀不到最前面「{front}」視窗裡的按鈕{why}:下面只有選單列。視窗裡的東西照圖上的位置點 x、y,或等一下再截一次圖。\n{}", lines.join("\n")));
|
||||||
} else {
|
} else {
|
||||||
text.push_str(&format!("\n「{app}」上可以操作的東西(圖上同樣的編號,用 ref 點):\n{}", lines.join("\n")));
|
text.push_str(&format!("\n「{app}」上可以操作的東西(圖上同樣的編號,用 ref 點):\n{}", lines.join("\n")));
|
||||||
|
// What moved since the last look (the quick model's step-effect signal).
|
||||||
|
static LAST_ELS: Mutex<Vec<crate::marks::Element>> = Mutex::new(Vec::new());
|
||||||
|
{
|
||||||
|
let cur: Vec<crate::marks::Element> = numbered.iter().map(|(_, e)| e.clone()).collect();
|
||||||
|
let mut last = LAST_ELS.lock().unwrap_or_else(|e| e.into_inner());
|
||||||
|
let d = crate::marks::diff(&last, &cur);
|
||||||
|
*last = cur;
|
||||||
|
if !d.is_empty() {
|
||||||
|
text.push_str(&format!("\n跟上一張圖相比:\n{}", d.join("\n")));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
text.push_str(a11y_note);
|
text.push_str(a11y_note);
|
||||||
Ok((text, img_b64))
|
Ok((text, img_b64))
|
||||||
|
|
|
||||||
50
src/marks.rs
50
src/marks.rs
|
|
@ -243,6 +243,40 @@ fn describe(e: &Element) -> String {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A fingerprint of what the controls are and where (to say "nothing changed").
|
/// A fingerprint of what the controls are and where (to say "nothing changed").
|
||||||
|
/// What came and what went between two looks at the screen (multiset of (kind, name)).
|
||||||
|
/// The quick model reads this to know what its last step did, without re-reading the whole
|
||||||
|
/// numbered list (the list is long; the change is usually two lines).
|
||||||
|
pub fn diff(prev: &[Element], now: &[Element]) -> Vec<String> {
|
||||||
|
fn key(e: &Element) -> (&str, &str) {
|
||||||
|
(e.kind.as_str(), e.name.as_str())
|
||||||
|
}
|
||||||
|
let mut counts: HashMap<(&str, &str), i32> = HashMap::new();
|
||||||
|
for e in prev {
|
||||||
|
*counts.entry(key(e)).or_default() += 1;
|
||||||
|
}
|
||||||
|
for e in now {
|
||||||
|
*counts.entry(key(e)).or_default() -= 1;
|
||||||
|
}
|
||||||
|
let mut lines = Vec::new();
|
||||||
|
// Order: as they appear in the current picture (adds), then as they were listed (removes).
|
||||||
|
let mut shown: std::collections::HashSet<(&str, &str)> = std::collections::HashSet::new();
|
||||||
|
for e in now {
|
||||||
|
let k = key(e);
|
||||||
|
if counts.get(&k) == Some(&-1) && shown.insert(k) {
|
||||||
|
lines.push(format!("+ {}「{}」", e.kind, e.name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for e in prev {
|
||||||
|
let k = key(e);
|
||||||
|
if counts.get(&k) == Some(&1) && shown.insert(k) {
|
||||||
|
lines.push(format!("- {}「{}」", e.kind, e.name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// A dialog that appeared entirely is the case that matters: show it in full.
|
||||||
|
lines.truncate(12);
|
||||||
|
lines
|
||||||
|
}
|
||||||
|
|
||||||
pub fn fingerprint(elements: &[Element]) -> u64 {
|
pub fn fingerprint(elements: &[Element]) -> u64 {
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
let mut h = std::collections::hash_map::DefaultHasher::new();
|
let mut h = std::collections::hash_map::DefaultHasher::new();
|
||||||
|
|
@ -348,6 +382,22 @@ fn digit(img: &mut image::RgbImage, x: i64, y: i64, d: u8, c: [u8; 3]) {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
fn mel(kind: &str, name: &str) -> Element {
|
||||||
|
Element { kind: kind.into(), name: name.into(), value: String::new(), x: 0.0, y: 0.0, w: 10.0, h: 10.0, active: true, flags: vec![], window: String::new() }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn the_change_between_two_looks_is_two_lines() {
|
||||||
|
let before = vec![mel("按鈕", "Save"), mel("輸入框", "標題")];
|
||||||
|
let after = vec![mel("按鈕", "Save"), mel("按鈕", "OK"), mel("按鈕", "Cancel")];
|
||||||
|
assert_eq!(diff(&before, &after), ["+ 按鈕「OK」", "+ 按鈕「Cancel」", "- 輸入框「標題」"]);
|
||||||
|
// Same controls twice = a multiset: one of two identical buttons leaving is a change.
|
||||||
|
let two = vec![mel("按鈕", "x"), mel("按鈕", "x")];
|
||||||
|
assert_eq!(diff(&two, &[mel("按鈕", "x")]), ["- 按鈕「x」"]);
|
||||||
|
assert!(diff(&before, &before).is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fn el(kind: &str, name: &str, x: f64, y: f64, active: bool) -> Element {
|
fn el(kind: &str, name: &str, x: f64, y: f64, active: bool) -> Element {
|
||||||
Element { kind: kind.into(), name: name.into(), value: String::new(), x, y, w: 40.0, h: 20.0, active, flags: vec![], window: "W".into() }
|
Element { kind: kind.into(), name: name.into(), value: String::new(), x, y, w: 40.0, h: 20.0, active, flags: vec![], window: "W".into() }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue