nandi/jolt-nativepublic Fork 0
c71fc59
Commits
Clone
git clone https://git.rickub.com/nandi/jolt-native.git
git clone ssh://git@rickub.com/nandi/jolt-native.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Let a reaction pill answer the pointer, as a face does

A chip carries a tally, and the one thing a tally leaves out is who is in
it. The two hover edges and a panel of children, exactly as the avatar
already had them, so the caller can hang a list of names off the pill the
pointer is resting on.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nandi committed 2026-08-31T18:14:22-07:00 Browse files
c71fc59 parent: 726f7c6
modified crates/jolt-vidya/src/tree.rs +57 -0
@@ -1337,6 +1337,19 @@ impl Tree {
13371337 if response.clicked() {
13381338 self.emit(id, "click", emoji, count as f64);
13391339 }
1340+ // A pill answers the pointer the way a face does: the two
1341+ // edges of a hover, and children painted beside the pointer
1342+ // while it rests. A reaction is a tally, and who is in it is
1343+ // the thing the tally leaves out.
1344+ self.track_hover(id, &response);
1345+ if response.hovered() && !self.in_hover_panel && self.has_children(id) {
1346+ self.in_hover_panel = true;
1347+ response.show_tooltip_ui(|ui| {
1348+ ui.set_max_width(320.0);
1349+ self.paint_children(id, ui, theme);
1350+ });
1351+ self.in_hover_panel = false;
1352+ }
13401353 }
13411354
13421355 Tag::Image => {
@@ -1519,6 +1532,50 @@ mod tests {
15191532 .unwrap_or_default()
15201533 }
15211534
1535+ /// The pointer resting on a reaction pill says so, the way it does on a
1536+ /// face: a chip is where a tally is, and who is in the tally is what a
1537+ /// hover is for.
1538+ #[test]
1539+ fn hovering_a_reaction_emits_hover() {
1540+ let mut tree = Tree::default();
1541+ let pill = tree.new_node("reaction");
1542+ tree.set(pill, "emoji", Value::Str("\u{1f44d}".to_owned()));
1543+ tree.set(pill, "count", Value::Num(2.0));
1544+ tree.append(tree.root(), pill);
1545+ let theme = Theme::dark();
1546+ let ctx = egui::Context::default();
1547+
1548+ let mut input = egui::RawInput::default();
1549+ input.events.push(egui::Event::PointerMoved(egui::pos2(20.0, 20.0)));
1550+ let _ = ctx.run(input.clone(), |ctx| {
1551+ egui::CentralPanel::default().show(ctx, |ui| tree.paint(ui, &theme));
1552+ });
1553+ let _ = ctx.run(input, |ctx| {
1554+ egui::CentralPanel::default().show(ctx, |ui| tree.paint(ui, &theme));
1555+ });
1556+
1557+ let mut names = Vec::new();
1558+ while tree.poll() {
1559+ names.push(tree.current().unwrap().name);
1560+ }
1561+ assert!(names.contains(&"hover"), "no hover from a pill: {names:?}");
1562+
1563+ // And a pill with children paints them beside the pointer. A window
1564+ // node is the probe: painting one writes its width back, so the prop
1565+ // appearing is the card having been drawn.
1566+ let card = tree.new_node("window");
1567+ tree.append(pill, card);
1568+ let mut input = egui::RawInput::default();
1569+ input.events.push(egui::Event::PointerMoved(egui::pos2(20.0, 20.0)));
1570+ let _ = ctx.run(input, |ctx| {
1571+ egui::CentralPanel::default().show(ctx, |ui| tree.paint(ui, &theme));
1572+ });
1573+ assert!(
1574+ tree.get(card, "window-width").is_some(),
1575+ "a hovered pill did not paint its card"
1576+ );
1577+ }
1578+
15221579 #[test]
15231580 fn root_exists_and_is_a_window() {
15241581 let tree = Tree::default();
@@ -1337,6 +1337,19 @@ impl Tree {
1337 if response.clicked() {1337 if response.clicked() {
1338 self.emit(id, "click", emoji, count as f64);1338 self.emit(id, "click", emoji, count as f64);
1339 }1339 }
1340+ // A pill answers the pointer the way a face does: the two
1341+ // edges of a hover, and children painted beside the pointer
1342+ // while it rests. A reaction is a tally, and who is in it is
1343+ // the thing the tally leaves out.
1344+ self.track_hover(id, &response);
1345+ if response.hovered() && !self.in_hover_panel && self.has_children(id) {
1346+ self.in_hover_panel = true;
1347+ response.show_tooltip_ui(|ui| {
1348+ ui.set_max_width(320.0);
1349+ self.paint_children(id, ui, theme);
1350+ });
1351+ self.in_hover_panel = false;
1352+ }
1340 }1353 }
1341 1354
1342 Tag::Image => {1355 Tag::Image => {
@@ -1519,6 +1532,50 @@ mod tests {
1519 .unwrap_or_default()1532 .unwrap_or_default()
1520 }1533 }
1521 1534
1535+ /// The pointer resting on a reaction pill says so, the way it does on a
1536+ /// face: a chip is where a tally is, and who is in the tally is what a
1537+ /// hover is for.
1538+ #[test]
1539+ fn hovering_a_reaction_emits_hover() {
1540+ let mut tree = Tree::default();
1541+ let pill = tree.new_node("reaction");
1542+ tree.set(pill, "emoji", Value::Str("\u{1f44d}".to_owned()));
1543+ tree.set(pill, "count", Value::Num(2.0));
1544+ tree.append(tree.root(), pill);
1545+ let theme = Theme::dark();
1546+ let ctx = egui::Context::default();
1547+
1548+ let mut input = egui::RawInput::default();
1549+ input.events.push(egui::Event::PointerMoved(egui::pos2(20.0, 20.0)));
1550+ let _ = ctx.run(input.clone(), |ctx| {
1551+ egui::CentralPanel::default().show(ctx, |ui| tree.paint(ui, &theme));
1552+ });
1553+ let _ = ctx.run(input, |ctx| {
1554+ egui::CentralPanel::default().show(ctx, |ui| tree.paint(ui, &theme));
1555+ });
1556+
1557+ let mut names = Vec::new();
1558+ while tree.poll() {
1559+ names.push(tree.current().unwrap().name);
1560+ }
1561+ assert!(names.contains(&"hover"), "no hover from a pill: {names:?}");
1562+
1563+ // And a pill with children paints them beside the pointer. A window
1564+ // node is the probe: painting one writes its width back, so the prop
1565+ // appearing is the card having been drawn.
1566+ let card = tree.new_node("window");
1567+ tree.append(pill, card);
1568+ let mut input = egui::RawInput::default();
1569+ input.events.push(egui::Event::PointerMoved(egui::pos2(20.0, 20.0)));
1570+ let _ = ctx.run(input, |ctx| {
1571+ egui::CentralPanel::default().show(ctx, |ui| tree.paint(ui, &theme));
1572+ });
1573+ assert!(
1574+ tree.get(card, "window-width").is_some(),
1575+ "a hovered pill did not paint its card"
1576+ );
1577+ }
1578+
1522 #[test]1579 #[test]
1523 fn root_exists_and_is_a_window() {1580 fn root_exists_and_is_a_window() {
1524 let tree = Tree::default();1581 let tree = Tree::default();