diff --git a/lib/src/widgets/flow_canvas.dart b/lib/src/widgets/flow_canvas.dart index 213eff1..bf32ba8 100644 --- a/lib/src/widgets/flow_canvas.dart +++ b/lib/src/widgets/flow_canvas.dart @@ -746,16 +746,34 @@ class _FlowCanvasState extends State final spec = _specForStep(step); final inputLabels = _inputLabelsForStep(step); final outputLabels = _outputLabelsForStep(step); + // Tooltips: every visible label gets one. When the + // manifest carries a locale description, use it. Else + // fall back to `name (type)` for declared fields whose + // description is missing, or to the bare field name for + // implicit (YAML-only) fields. Operators always see + // something on hover — useful at least to confirm the + // exact field they're pointing at. final tooltips = {}; + final loc = widget.locale == FlowEditorLocale.de ? 'de' : 'en'; + final declaredFields = {}; if (spec != null) { - final loc = widget.locale == FlowEditorLocale.de ? 'de' : 'en'; - for (final f in spec.inputs) { - final d = f.descriptionFor(loc); - if (d != null) tooltips[f.name] = d; + for (final f in [...spec.inputs, ...spec.outputs]) { + declaredFields[f.name] = f; } - for (final f in spec.outputs) { + } + for (final label in [...inputLabels, ...outputLabels]) { + final f = declaredFields[label]; + if (f != null) { final d = f.descriptionFor(loc); - if (d != null) tooltips[f.name] = d; + if (d != null && d.isNotEmpty) { + tooltips[label] = d; + } else if (f.type.isNotEmpty) { + tooltips[label] = '$label · ${f.type}'; + } else { + tooltips[label] = label; + } + } else { + tooltips[label] = label; } } final cardHeight = NodeGeometry.heightFor( @@ -1714,50 +1732,32 @@ class _FlowCanvasState extends State onLongPressStart: onContextMenu == null ? null : (details) => onContextMenu(details.globalPosition), - child: Stack( - alignment: Alignment.center, - children: [ - // Outer ring — defines the port's footprint. - // Filled when connected (so the line "docks" - // into a visible target), surface-filled when - // dangling (a clear empty socket). Drop-target - // halo grows + glows. - Container( - decoration: BoxDecoration( - shape: BoxShape.circle, - color: filled ? accent : theme.colorScheme.surface, - border: Border.all( - color: accent, - width: isClosest ? 2.5 : (isHovered ? 2.2 : 1.8), - ), - boxShadow: (isClosest || isHovered) - ? [ - BoxShadow( - color: accent.withValues( - alpha: isClosest ? 0.55 : 0.35, - ), - blurRadius: isClosest ? 10 : 6, - ), - ] - : null, - ), + child: Container( + // Connected = solid filled circle in accent. + // Dangling = clear ring with surface fill so the + // empty socket is obvious. Drop-target halo + // glows in addition. No inner pin — Stefan + // explicitly wants the connected variant to read + // as one continuous filled disc, not a ring with + // a bullseye in it. + decoration: BoxDecoration( + shape: BoxShape.circle, + color: filled ? accent : theme.colorScheme.surface, + border: Border.all( + color: accent, + width: isClosest ? 2.5 : (isHovered ? 2.2 : 1.8), ), - // Inner pin — tiny surface-coloured dot at the - // centre when connected, giving the port the - // "socket with a pin in it" look that reads as - // an active electrical connector rather than a - // free-floating indicator. Skipped on empty - // ports so the hollow ring is unambiguous. - if (connected) - Container( - width: 4, - height: 4, - decoration: BoxDecoration( - color: theme.colorScheme.surface, - shape: BoxShape.circle, - ), - ), - ], + boxShadow: (isClosest || isHovered) + ? [ + BoxShadow( + color: accent.withValues( + alpha: isClosest ? 0.55 : 0.35, + ), + blurRadius: isClosest ? 10 : 6, + ), + ] + : null, + ), ), ), ), diff --git a/lib/src/widgets/flow_node.dart b/lib/src/widgets/flow_node.dart index 7d1414b..1628fc7 100644 --- a/lib/src/widgets/flow_node.dart +++ b/lib/src/widgets/flow_node.dart @@ -83,10 +83,13 @@ class NodeGeometry { /// without overlapping the label text. static const double portGutter = 18; - /// Diameter of the canvas-side port dot — sized to the - /// body's labelSmall row (~11 px text + 11 px breathing - /// room) so the dot reads as the port without crowding. - static const double portDotSize = 12; + /// Diameter of the canvas-side port dot. Sized so the + /// connected-vs-outlined state reads clearly at any zoom: + /// a 12 px outline tends to disappear into the background + /// at 50 % zoom; 14 px keeps the ring visible and gives + /// the filled (connected) variant enough mass to stand out + /// against the card shadow. + static const double portDotSize = 14; /// Total card height for a node carrying [inputCount] inputs /// on the left + [outputCount] outputs on the right. The diff --git a/pubspec.yaml b/pubspec.yaml index b2c9d04..7bed44a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: fai_studio_flow_editor description: Swappable inline YAML editor for F∆I Studio flows. -version: 0.10.2 +version: 0.10.3 publish_to: 'none' repository: https://git.flemming.ai/fai/studio-flow-editor