mirror of
https://github.com/storytold/figcraft.git
synced 2026-11-11 16:30:12 -06:00
M5: overlay positions relative to the layer that opens them
From the Figma forum request "Set overlay position relative to element, not to page": an open overlay action takes anchor: screen (default) | triggerInside | triggerOutside. With a trigger anchor the position preset is measured on the layer that opens the overlay instead of the screen. Outside puts top presets above it and bottom presets below it with the edges lined up (a dropdown is Bottom left + Outside); Center centres it on that layer. Manual is unchanged. The interaction editor gets Relative to (Whole screen / Trigger layer) and Inside / Outside. The player, the command (and so MCP and the control channel) carry it; the command's params doc lists it. Figma has no such setting, so .fig export writes it as a manual offset. Old files and JSON without anchor read as screen and write unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
committed by
Albert Nimtz
co-authored by
Claude Opus 5.5
parent
70227e30fc
commit
545cd630fd
@@ -448,6 +448,7 @@ struct Exporter<'d> {
|
||||
tracks: BTreeMap<NodeId, (Vec<u32>, Vec<u32>)>,
|
||||
aux: u32,
|
||||
overlays: BTreeMap<NodeId, Overlay>,
|
||||
trigger_size: [f64; 2],
|
||||
/// Flow starting points: layer → (order, name).
|
||||
starts: BTreeMap<NodeId, (usize, String)>,
|
||||
/// Shared styles, variable collections and variables → local id of their node's GUID (session
|
||||
@@ -486,6 +487,7 @@ impl<'d> Exporter<'d> {
|
||||
tracks: BTreeMap::new(),
|
||||
aux: 0,
|
||||
overlays: BTreeMap::new(),
|
||||
trigger_size: [0.0, 0.0],
|
||||
starts: BTreeMap::new(),
|
||||
style_ids: BTreeMap::new(),
|
||||
set_ids: BTreeMap::new(),
|
||||
@@ -574,8 +576,9 @@ impl<'d> Exporter<'d> {
|
||||
_ => {}
|
||||
}
|
||||
for a in n.interactions.iter().flat_map(|it| figcraft_doc::proto::walk(&it.actions)) {
|
||||
if let Action::OpenOverlay { dest, position, close_on_outside, background, .. } = a {
|
||||
let o = Overlay { position: *position, close_on_outside: *close_on_outside, background: *background };
|
||||
if let Action::OpenOverlay { dest, position, close_on_outside, background, anchor, .. } = a {
|
||||
let position = if !anchor.is_screen() && *position != OverlayPosition::Manual { OverlayPosition::Manual } else { *position };
|
||||
let o = Overlay { position, close_on_outside: *close_on_outside, background: *background };
|
||||
match self.overlays.get(dest) {
|
||||
Some(old) if (old.position, old.close_on_outside, old.background) != (o.position, o.close_on_outside, o.background) => {
|
||||
self.warn("overlays opened with different settings keep the first one's");
|
||||
@@ -2086,6 +2089,7 @@ impl<'d> Exporter<'d> {
|
||||
|
||||
/// Interactions, scrolling, flow starts and the overlay settings this layer is opened with.
|
||||
fn prototype(&mut self, n: &Node, f: &mut Fields) {
|
||||
self.trigger_size = [n.width, n.height];
|
||||
let list: Vec<Kv> = n.interactions.iter().filter_map(|it| self.interaction(it)).collect();
|
||||
if !list.is_empty() {
|
||||
f.push(("prototypeInteractions", arr(list)));
|
||||
@@ -2174,10 +2178,16 @@ impl<'d> Exporter<'d> {
|
||||
}
|
||||
Some(act)
|
||||
}
|
||||
Action::OpenOverlay { dest, position, offset, .. } => {
|
||||
Action::OpenOverlay { dest, position, offset, anchor, .. } => {
|
||||
let mut act = node(self, dest, "OVERLAY")?;
|
||||
if let (Kv::Object(f), OverlayPosition::Manual, Some([x, y])) = (&mut act, position, offset) {
|
||||
f.push(("overlayRelativePosition", o(vec![("x", fl(*x)), ("y", fl(*y))])));
|
||||
let size = self.doc.node(*dest).map_or([0.0, 0.0], |d| [d.width, d.height]);
|
||||
let offset = match anchor.offset(*position, self.trigger_size, size) {
|
||||
Some(o) => Some(o),
|
||||
None if *position == OverlayPosition::Manual => *offset,
|
||||
None => None,
|
||||
};
|
||||
if let (Kv::Object(f), Some([x, y])) = (&mut act, offset) {
|
||||
f.push(("overlayRelativePosition", o(vec![("x", fl(x)), ("y", fl(y))])));
|
||||
}
|
||||
Some(act)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,9 @@ use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use figcraft_doc::color::{BlendMode, Color, ColorStop, Gradient, GradientKind, ImageScaleMode, Paint, PaintKind};
|
||||
use figcraft_doc::geom::{Affine, Point};
|
||||
use figcraft_doc::proto::{Action, Direction, Easing, Flow, Interaction, KeyMods, Overflow, OverlayPosition, ScrollPosition, Transition, Trigger};
|
||||
use figcraft_doc::proto::{
|
||||
Action, Direction, Easing, Flow, Interaction, KeyMods, Overflow, OverlayAnchor, OverlayPosition, ScrollPosition, Transition, Trigger,
|
||||
};
|
||||
use figcraft_doc::*;
|
||||
use serde_json::Value;
|
||||
|
||||
@@ -511,6 +513,7 @@ impl Gen {
|
||||
close_on_outside: true,
|
||||
background: Some(Color::new(0.0, 0.0, 0.0, 0.5)),
|
||||
offset: None,
|
||||
anchor: OverlayAnchor::Screen,
|
||||
},
|
||||
3 => Action::SwapOverlay { dest },
|
||||
4 => Action::ChangeTo { dest },
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
use figcraft_doc::proto::{
|
||||
Action, ConditionalBlock, DeviceRotation, Direction, Easing, Expr, ExprOp, Interaction, KeyMods, MAX_ACTION_DEPTH, MAX_EXPR_DEPTH, Overflow,
|
||||
OverlayPosition, PrototypeSettings, ScrollPosition, Transition, Trigger, gamepad_button,
|
||||
OverlayAnchor, OverlayPosition, PrototypeSettings, ScrollPosition, Transition, Trigger, gamepad_button,
|
||||
};
|
||||
use figcraft_doc::{Node, NodeId, VariableType, VariableValue};
|
||||
use serde_json::{Value, json};
|
||||
@@ -322,7 +322,7 @@ impl Ctx<'_> {
|
||||
.and_then(|v| Some([num(v, "x")?, num(v, "y")?]))
|
||||
.filter(|o| o.iter().all(|c| c.is_finite()))
|
||||
.map(|o| o.map(|c| c.clamp(-MAX_OFFSET, MAX_OFFSET)));
|
||||
Action::OpenOverlay { dest, position, close_on_outside, background, offset }
|
||||
Action::OpenOverlay { dest, position, close_on_outside, background, offset, anchor: OverlayAnchor::Screen }
|
||||
}
|
||||
Some(_) => {
|
||||
self.warn("prototype navigations of unknown types were skipped");
|
||||
|
||||
Reference in New Issue
Block a user