feat(editor): tab-aware action strip + simpler tap handling
Reposition the per-tab action buttons under the TabBar so the operator's eye flows TabBar → context buttons → canvas. Graph keeps Add-Step + Save + Run, Text trims to Save + Run, Run hides them entirely (the Run tab has its own start button). Drop the outer wrapping GestureDetector that competed with the inner Positioned.fill detector inside the canvas Stack — two TapGestureRecognizers in the same arena meant the inner one sometimes lost edge-tap and background-tap. Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
parent
59aa8fe78e
commit
73736769a0
3 changed files with 106 additions and 69 deletions
|
|
@ -357,17 +357,10 @@ outputs:
|
|||
strings: _l,
|
||||
activeName: _controller.activeName,
|
||||
dirty: _controller.isDirty,
|
||||
saving: _controller.isSaving,
|
||||
running: _controller.isRunning,
|
||||
onBack: Navigator.of(context).canPop()
|
||||
? () => Navigator.of(context).maybePop()
|
||||
: null,
|
||||
onSave: _controller.activeName != null ? _save : null,
|
||||
onAddStep: _controller.activeName != null ? _addStep : null,
|
||||
onNew: _newFlow,
|
||||
onRun: _controller.activeName != null
|
||||
? () => _tabs.animateTo(2)
|
||||
: null,
|
||||
),
|
||||
const Divider(height: 1),
|
||||
Expanded(
|
||||
|
|
@ -421,6 +414,17 @@ outputs:
|
|||
],
|
||||
),
|
||||
),
|
||||
_TabActionStrip(
|
||||
strings: _l,
|
||||
tabs: _tabs,
|
||||
saving: _controller.isSaving,
|
||||
running: _controller.isRunning,
|
||||
onAddStep: _controller.activeName != null ? _addStep : null,
|
||||
onSave: _controller.activeName != null ? _save : null,
|
||||
onRun: _controller.activeName != null
|
||||
? () => _tabs.animateTo(2)
|
||||
: null,
|
||||
),
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
controller: _tabs,
|
||||
|
|
@ -688,24 +692,14 @@ class _Toolbar extends StatelessWidget {
|
|||
final FlowEditorStrings strings;
|
||||
final String? activeName;
|
||||
final bool dirty;
|
||||
final bool saving;
|
||||
final bool running;
|
||||
final VoidCallback? onBack;
|
||||
final VoidCallback? onSave;
|
||||
final VoidCallback? onAddStep;
|
||||
final VoidCallback onNew;
|
||||
final VoidCallback? onRun;
|
||||
const _Toolbar({
|
||||
required this.strings,
|
||||
required this.activeName,
|
||||
required this.dirty,
|
||||
required this.saving,
|
||||
required this.running,
|
||||
required this.onBack,
|
||||
required this.onSave,
|
||||
required this.onAddStep,
|
||||
required this.onNew,
|
||||
required this.onRun,
|
||||
});
|
||||
|
||||
@override
|
||||
|
|
@ -776,14 +770,73 @@ class _Toolbar extends StatelessWidget {
|
|||
icon: const Icon(Icons.add, size: 16),
|
||||
label: Text(strings.newFlow),
|
||||
),
|
||||
const SizedBox(width: FaiSpace.sm),
|
||||
if (onAddStep != null)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Tab-aware action strip — lives directly under the TabBar so
|
||||
// the operator's eye flows from TabBar → context buttons →
|
||||
// canvas. Each tab exposes a different set of actions:
|
||||
//
|
||||
// Graph: [+ Step] [Save] [Run]
|
||||
// Text: [Save] [Run]
|
||||
// Run: (no buttons — Run tab has its own start button)
|
||||
//
|
||||
// Rebuilds when the TabController index changes via the
|
||||
// passed-in animation.
|
||||
class _TabActionStrip extends StatelessWidget {
|
||||
final FlowEditorStrings strings;
|
||||
final TabController tabs;
|
||||
final bool saving;
|
||||
final bool running;
|
||||
final VoidCallback? onAddStep;
|
||||
final VoidCallback? onSave;
|
||||
final VoidCallback? onRun;
|
||||
const _TabActionStrip({
|
||||
required this.strings,
|
||||
required this.tabs,
|
||||
required this.saving,
|
||||
required this.running,
|
||||
required this.onAddStep,
|
||||
required this.onSave,
|
||||
required this.onRun,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return AnimatedBuilder(
|
||||
animation: tabs.animation ?? tabs,
|
||||
builder: (context, _) {
|
||||
final idx = tabs.index;
|
||||
final showAddStep = idx == 0;
|
||||
final showSaveRun = idx == 0 || idx == 1;
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: FaiSpace.md,
|
||||
vertical: FaiSpace.sm,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.surfaceContainerLow,
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: theme.colorScheme.outlineVariant.withValues(alpha: 0.4),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
if (showAddStep) ...[
|
||||
FilledButton.tonalIcon(
|
||||
onPressed: onAddStep,
|
||||
icon: const Icon(Icons.add_box_outlined, size: 16),
|
||||
label: Text(strings.addStep),
|
||||
),
|
||||
const SizedBox(width: FaiSpace.sm),
|
||||
],
|
||||
if (showSaveRun) ...[
|
||||
FilledButton.tonalIcon(
|
||||
onPressed: saving ? null : onSave,
|
||||
icon: saving
|
||||
|
|
@ -808,8 +861,12 @@ class _Toolbar extends StatelessWidget {
|
|||
label: Text(strings.run),
|
||||
),
|
||||
],
|
||||
const Spacer(),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -429,26 +429,6 @@ class _FlowCanvasState extends State<FlowCanvas>
|
|||
: BoxDecoration(color: theme.colorScheme.surface),
|
||||
child: Stack(
|
||||
children: [
|
||||
// Outer-most tap layer — catches clicks on the
|
||||
// dark area OUTSIDE the canvas grid (when the
|
||||
// operator has panned / zoomed so the grid
|
||||
// doesn't fill the viewport). Translucent so
|
||||
// InteractiveViewer below still handles pan +
|
||||
// zoom; the gesture arena gives the tap to
|
||||
// whichever child claims it first, and on
|
||||
// canvas-empty pointers nobody else does.
|
||||
Positioned.fill(
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () {
|
||||
// Deselect both step + edge so the
|
||||
// properties panel closes when the operator
|
||||
// clicks the empty backdrop.
|
||||
widget.controller.selectStep(null);
|
||||
widget.controller.selectEdge(null);
|
||||
},
|
||||
),
|
||||
),
|
||||
InteractiveViewer(
|
||||
transformationController: _transform,
|
||||
constrained: false,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
name: fai_studio_flow_editor
|
||||
description: Swappable inline YAML editor for F∆I Studio flows.
|
||||
version: 0.13.0
|
||||
version: 0.14.0
|
||||
publish_to: 'none'
|
||||
repository: https://git.flemming.ai/fai/studio-flow-editor
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue