Files
figcraft/crates/engine/src/widget_hover_tests.rs
T

130 lines
5.8 KiB
Rust

//! Widgets' hover styles: while the pointer is over a widget layer that has one, the canvas shows
//! it as a live preview (no undo step, nothing saved or synced); moving away, leaving the canvas or
//! any command puts the layer back, and a click on a hovered part acts on the document itself.
use serde_json::{Value, json};
use crate::components::tests::{ex, idv, node};
use crate::plugins_tests::{install, undo_len};
use crate::tools::{PointerEvent, PointerKind};
use crate::widgets_tests::{W, widget_manifest};
use crate::{Session, textpreview};
use figcraft_doc::NodeId;
use figcraft_geom::Point;
const BLUE: &str = "0D99FF";
const DARK: &str = "0B7FD6";
/// A widget whose button darkens and whose label turns white while hovered; the widget fades a
/// little. A click on the button counts.
fn button() -> Value {
json!({"root": {"type": "frame", "name": "Hover", "layout": "horizontal", "padding": 10, "fill": "#FFFFFF", "hover": {"opacity": 0.9}, "children": [
{"type": "rectangle", "name": "Button", "target": "go", "width": 40, "height": 20, "fill": format!("#{BLUE}"), "hover": {"fill": format!("#{DARK}"), "stroke": "#000000"}},
{"type": "text", "name": "Label", "text": "Go", "color": "#111111", "hover": {"color": "#FFFFFF"}}]}})
}
fn session() -> (Session, NodeId) {
let mut s = Session::new();
let mut w = W::default();
let body = w.render(&button());
install(&mut s, widget_manifest("org.test.hover", "read"), &w.build(&body));
let id = idv(&ex(&mut s, "widget.insert", json!({"plugin": "org.test.hover", "x": 50, "y": 60})));
ex(&mut s, "select.set", json!({"ids": []}));
(s, id)
}
fn fill(s: &Session, id: NodeId) -> String {
node(s, id).fills.first().and_then(|p| p.solid_color()).map(|c| c.to_hex()).unwrap_or_default()
}
fn hover(s: &mut Session, p: Point) {
s.pointer(&PointerEvent::at(PointerKind::Hover, p.x, p.y, 1.0)).unwrap();
}
fn hover_over(s: &mut Session, id: NodeId) {
let p = centre(s, id);
hover(s, p);
}
fn centre(s: &Session, id: NodeId) -> Point {
s.doc().unwrap().world_bounds(id).unwrap().center()
}
#[test]
fn hovering_a_widget_shows_its_hover_styles_without_an_edit() {
let (mut s, w) = session();
let kids = node(&s, w).children.clone();
let (btn, label) = (kids[0], kids[1]);
let undo = undo_len(&s);
hover_over(&mut s, btn);
assert_eq!(fill(&s, btn), DARK, "the hovered button");
assert!(!node(&s, btn).strokes.is_empty());
assert!((node(&s, w).opacity - 0.9).abs() < 1e-6, "the widget the pointer is in");
assert_eq!(fill(&s, label), "111111", "the label isn't under the pointer");
let itself = s.ds().unwrap().document_itself().clone();
assert_eq!(itself.node(btn).unwrap().fills[0].solid_color().unwrap().to_hex(), BLUE, "the document itself is unchanged");
assert_eq!(undo_len(&s), undo, "no undo step");
// Over the label: the label's style, not the button's.
hover_over(&mut s, label);
assert_eq!((fill(&s, btn), fill(&s, label)), (BLUE.into(), "FFFFFF".into()));
// Away from the widget, and leaving the canvas: as it was.
hover(&mut s, Point::new(-400.0, -400.0));
assert_eq!((fill(&s, btn), fill(&s, label)), (BLUE.into(), "111111".into()));
assert!((node(&s, w).opacity - 1.0).abs() < 1e-6);
hover_over(&mut s, btn);
s.pointer_left();
assert_eq!(fill(&s, btn), BLUE);
assert!(!textpreview::active(&s));
}
#[test]
fn a_click_on_a_hovered_part_edits_the_document_itself() {
let (mut s, w) = session();
let btn = node(&s, w).children[0];
let at = centre(&s, btn);
hover(&mut s, at);
assert_eq!(fill(&s, btn), DARK);
let undo = undo_len(&s);
s.pointer(&PointerEvent::at(PointerKind::Down, at.x, at.y, 1.0)).unwrap();
s.pointer(&PointerEvent::at(PointerKind::Up, at.x, at.y, 1.0)).unwrap();
assert_eq!(undo_len(&s), undo + 1, "the click is one undo step");
// The widget was redrawn from the document itself: no hover style was kept in it.
let btn = node(&s, w).children[0];
assert_eq!(fill(&s, btn), BLUE);
ex(&mut s, "edit.undo", json!({}));
let btn = node(&s, w).children[0];
assert_eq!(fill(&s, btn), BLUE, "undo brings back the document, not a hovered one");
// Hovering again shows it again.
hover(&mut s, at);
assert_eq!(fill(&s, btn), DARK);
}
#[test]
fn widget_hover_is_a_command_any_other_command_ends() {
let (mut s, w) = session();
let btn = node(&s, w).children[0];
assert_eq!(ex(&mut s, "widget.hover", json!({"ids": [btn]}))["hovering"], true);
assert_eq!(fill(&s, btn), DARK);
ex(&mut s, "select.set", json!({"ids": [w]}));
assert_eq!(fill(&s, btn), BLUE, "another command puts the document back first");
// Layers without a hover style, and layers outside widgets, show nothing.
let r = idv(&ex(&mut s, "shape.rectangle", json!({"x": 400, "y": 0, "width": 10, "height": 10})));
assert_eq!(ex(&mut s, "widget.hover", json!({"ids": [r]}))["hovering"], false);
assert_eq!(ex(&mut s, "widget.hover", json!({}))["hovering"], false);
// A hover style damaged in a file is ignored.
let doc = s.ds_mut().unwrap().doc_mut();
doc.get_mut(btn).unwrap().plugin_data.insert("figcraft.widget.hover".into(), "{\"fill\": \"not a colour\"}".into());
assert_eq!(ex(&mut s, "widget.hover", json!({"ids": [btn]}))["hovering"], false);
}
#[test]
fn a_widget_doesnt_replace_another_live_preview() {
let (mut s, w) = session();
let btn = node(&s, w).children[0];
let other = idv(&ex(&mut s, "shape.rectangle", json!({"x": 400, "y": 0, "width": 10, "height": 10})));
ex(&mut s, "text.preview", json!({"command": "node.set", "params": {"ids": [other], "props": {"opacity": 0.5}}}));
hover_over(&mut s, btn);
assert!((node(&s, other).opacity - 0.5).abs() < 1e-6, "the preview showing stays");
assert_eq!(fill(&s, btn), BLUE);
}