mirror of
https://github.com/storytold/figcraft.git
synced 2026-11-02 06:46:50 -06:00
130 lines
5.8 KiB
Rust
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);
|
|
}
|