Compare commits

..

No commits in common. "a86a5e6aec20a14d888c8d08fc1265d3eac2f640" and "8abd6b3ca8d620267384fd419a4e1aff9b287c93" have entirely different histories.

4 changed files with 116 additions and 569 deletions

View file

@ -11,28 +11,10 @@ class VisualComponent extends HookWidget {
final Map<String, Color?> inputColors; final Map<String, Color?> inputColors;
final Map<String, Color?> outputColors; final Map<String, Color?> outputColors;
final bool isNextToSimulate; final bool isNextToSimulate;
final void Function(String)? onInputHovered;
final void Function(String)? onInputUnhovered;
final void Function(String)? onOutputHovered;
final void Function(String)? onOutputUnhovered;
VisualComponent({ VisualComponent({super.key, required this.name, required this.inputs, required this.outputs, Map<String, Color?>? inputColors, Map<String, Color?>? outputColors, this.isNextToSimulate = false})
super.key,
required this.name,
required this.inputs,
required this.outputs,
Map<String, Color?>? inputColors,
Map<String, Color?>? outputColors,
this.isNextToSimulate = false,
this.onInputHovered,
this.onInputUnhovered,
this.onOutputHovered,
this.onOutputUnhovered,
})
: inputColors = inputColors ?? {} : inputColors = inputColors ?? {}
, outputColors = outputColors ?? {} , outputColors = outputColors ?? {};
, assert((onInputHovered == null) == (onInputUnhovered == null))
, assert((onOutputHovered == null) == (onOutputUnhovered == null));
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -58,20 +40,12 @@ class VisualComponent extends HookWidget {
final hovered = useState(false); final hovered = useState(false);
useEffect(() {
if (onInputHovered != null || onOutputHovered != null) {
hovered.value = false;
}
return null;
}, [onInputHovered, onOutputHovered]);
final inputsWidth = inputs.map((input) => IOLabel.getNeededWidth(context, input)).fold<double>(0, (previousValue, element) => max(previousValue, element)); final inputsWidth = inputs.map((input) => IOLabel.getNeededWidth(context, input)).fold<double>(0, (previousValue, element) => max(previousValue, element));
final outputsWidth = outputs.map((output) => IOLabel.getNeededWidth(context, output)).fold<double>(0, (previousValue, element) => max(previousValue, element)); final outputsWidth = outputs.map((output) => IOLabel.getNeededWidth(context, output)).fold<double>(0, (previousValue, element) => max(previousValue, element));
return MouseRegion( return MouseRegion(
onEnter: onInputHovered == null && onOutputHovered == null ? (event) => hovered.value = true : null, onEnter: (event) => hovered.value = true,
onExit: onInputUnhovered == null && onOutputUnhovered== null ? (event) => hovered.value = false : null, onExit: (event) => hovered.value = false,
child: Row( child: Row(
children: [ children: [
Column( Column(
@ -85,9 +59,6 @@ class VisualComponent extends HookWidget {
? Theme.of(context).colorScheme.primary ? Theme.of(context).colorScheme.primary
: inputColors[input] ?? Colors.black, : inputColors[input] ?? Colors.black,
width: inputsWidth, width: inputsWidth,
onHovered: onInputHovered == null ? null : () => onInputHovered!(input),
onUnhovered: onInputUnhovered == null ? null : () => onInputUnhovered!(input),
flashing: onInputHovered != null,
), ),
), ),
], ],
@ -121,9 +92,6 @@ class VisualComponent extends HookWidget {
? Theme.of(context).colorScheme.primary ? Theme.of(context).colorScheme.primary
: outputColors[output] ?? Colors.black, : outputColors[output] ?? Colors.black,
width: outputsWidth, width: outputsWidth,
onHovered: onOutputHovered == null ? null : () => onOutputHovered!(output),
onUnhovered: onOutputUnhovered == null ? null : () => onOutputUnhovered!(output),
flashing: onOutputHovered != null,
), ),
), ),
], ],
@ -175,71 +143,22 @@ class IOComponent extends HookWidget {
final double circleDiameter; final double circleDiameter;
final Color? color; final Color? color;
final void Function()? onTap; final void Function()? onTap;
final void Function()? onHovered;
final void Function()? onUnhovered;
final bool flashing;
const IOComponent({ const IOComponent({super.key, required this.name, required this.input, this.width = 100, this.circleDiameter = 20, this.color, this.onTap});
super.key,
required this.name,
required this.input,
this.width = 100,
this.circleDiameter = 20,
this.color,
this.onTap,
this.onHovered,
this.onUnhovered,
this.flashing = false,
}) : assert((onHovered == null) == (onUnhovered == null));
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final flashingAnimation = useAnimationController(
duration: const Duration(milliseconds: 500),
initialValue: 0.0,
lowerBound: 0.0,
upperBound: 1.0,
);
useEffect(() {
if (flashing) {
flashingAnimation.repeat(
reverse: true,
);
}
else {
flashingAnimation.reset();
}
return null;
}, [flashing]);
final flashingAnimProgress = useAnimation(flashingAnimation);
final hovered = useState(false); final hovered = useState(false);
useEffect(() {
if (onHovered != null) {
hovered.value = false;
}
return null;
}, [onHovered]);
return MouseRegion( return MouseRegion(
onEnter: (event) => onHovered != null ? onHovered!() : hovered.value = true, onEnter: (event) => hovered.value = true,
onExit: (event) => onUnhovered != null ? onUnhovered!() : hovered.value = false, onExit: (event) => hovered.value = false,
hitTestBehavior: HitTestBehavior.translucent, hitTestBehavior: HitTestBehavior.translucent,
opaque: false,
child: GestureDetector( child: GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: onTap, onTap: onTap,
child: Builder( child: Builder(
builder: (context) { builder: (context) {
final lineColor = hovered.value ? Theme.of(context).colorScheme.primary : color ?? Colors.black; final lineColor = hovered.value ? Theme.of(context).colorScheme.primary : color ?? Colors.black;
final animLineColor = Color.lerp(
lineColor,
Colors.blue,
flashingAnimProgress,
)!;
return Row( return Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
@ -248,7 +167,7 @@ class IOComponent extends HookWidget {
width: circleDiameter, width: circleDiameter,
height: circleDiameter, height: circleDiameter,
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all(color: animLineColor), border: Border.all(color: lineColor),
shape: BoxShape.circle, shape: BoxShape.circle,
color: color, color: color,
), ),
@ -258,7 +177,7 @@ class IOComponent extends HookWidget {
child: IOLabel( child: IOLabel(
label: name, label: name,
input: !input, input: !input,
lineColor: animLineColor, lineColor: lineColor,
width: width - circleDiameter, width: width - circleDiameter,
), ),
), ),
@ -266,7 +185,7 @@ class IOComponent extends HookWidget {
width: circleDiameter, width: circleDiameter,
height: circleDiameter, height: circleDiameter,
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all(color: animLineColor), border: Border.all(color: lineColor),
shape: BoxShape.circle, shape: BoxShape.circle,
color: color, color: color,
), ),
@ -284,78 +203,35 @@ class IOComponent extends HookWidget {
} }
} }
class IOLabel extends HookWidget { class IOLabel extends StatelessWidget {
final bool input; final bool input;
final String label; final String label;
final Color lineColor; final Color lineColor;
final double width; final double width;
final void Function()? onHovered;
final void Function()? onUnhovered;
final bool flashing;
const IOLabel({super.key, required this.lineColor, required this.label, required this.input, this.width = 80, this.onHovered, this.onUnhovered, this.flashing = false,}) const IOLabel({super.key, required this.lineColor, required this.label, required this.input, this.width = 80});
: assert((onHovered == null) == (onUnhovered == null));
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final flashingAnimation = useAnimationController( return Container(
duration: const Duration(milliseconds: 500), width: width,
initialValue: 0.0, height: 20,
lowerBound: 0.0, decoration: BoxDecoration(
upperBound: 1.0, border: Border(
); bottom: BorderSide(color: lineColor),
useEffect(() {
if (flashing) {
flashingAnimation.repeat(
reverse: true,
);
}
else {
flashingAnimation.reset();
}
return null;
}, [flashing]);
final flashingAnimProgress = useAnimation(flashingAnimation);
final hovered = useState(false);
return MouseRegion(
hitTestBehavior: onHovered != null ? HitTestBehavior.translucent : HitTestBehavior.deferToChild,
onEnter: onHovered == null ? null : (_) {
hovered.value = true;
onHovered!();
},
onExit: onUnhovered == null ? null : (_) {
hovered.value = false;
onUnhovered!();
},
child: Container(
width: width,
height: 20,
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Color.lerp(
lineColor,
Colors.blue,
flashingAnimProgress,
)!,
),
),
), ),
child: Align( ),
alignment: input ? Alignment.bottomRight : Alignment.bottomLeft, child: Align(
child: Padding( alignment: input ? Alignment.bottomRight : Alignment.bottomLeft,
padding: const EdgeInsets.symmetric(horizontal: 4.0), child: Padding(
child: Text( padding: const EdgeInsets.symmetric(horizontal: 4.0),
label, child: Text(
style: const TextStyle( label,
inherit: true, style: const TextStyle(
fontFeatures: [ inherit: true,
FontFeature.tabularFigures(), fontFeatures: [
], FontFeature.tabularFigures(),
), ],
), ),
), ),
), ),

View file

@ -6,17 +6,13 @@ import 'package:logic_circuits_simulator/components/visual_component.dart';
import 'package:logic_circuits_simulator/models.dart'; import 'package:logic_circuits_simulator/models.dart';
import 'package:logic_circuits_simulator/pages_arguments/design_component.dart'; import 'package:logic_circuits_simulator/pages_arguments/design_component.dart';
import 'package:logic_circuits_simulator/state/component.dart'; import 'package:logic_circuits_simulator/state/component.dart';
import 'package:logic_circuits_simulator/state/project.dart';
import 'package:logic_circuits_simulator/state/projects.dart';
import 'package:logic_circuits_simulator/utils/future_call_debounce.dart'; import 'package:logic_circuits_simulator/utils/future_call_debounce.dart';
import 'package:logic_circuits_simulator/utils/iterable_extension.dart'; import 'package:logic_circuits_simulator/utils/iterable_extension.dart';
import 'package:logic_circuits_simulator/utils/provider_hook.dart'; import 'package:logic_circuits_simulator/utils/provider_hook.dart';
import 'package:logic_circuits_simulator/utils/stack_canvas_controller_hook.dart'; import 'package:logic_circuits_simulator/utils/stack_canvas_controller_hook.dart';
import 'package:stack_canvas/stack_canvas.dart'; import 'package:stack_canvas/stack_canvas.dart';
import 'package:uuid/uuid.dart';
Key canvasKey = GlobalKey(); Key canvasKey = GlobalKey();
Key pickerKey = GlobalKey();
class DesignComponentPage extends HookWidget { class DesignComponentPage extends HookWidget {
final ComponentEntry component; final ComponentEntry component;
@ -42,12 +38,6 @@ class DesignComponentPage extends HookWidget {
final movingWidgetUpdater = useState<void Function(double dx, double dy)?>(null); final movingWidgetUpdater = useState<void Function(double dx, double dy)?>(null);
final movingWidget = useState<dynamic>(null); final movingWidget = useState<dynamic>(null);
final deleteOnDrop = useState<bool>(false);
final designSelection = useState<String?>(null);
final wireToDelete = useState<String?>(null);
final sourceToConnect = useState<String?>(null);
final hoveredIO = useState<String?>(null);
final widgets = useMemoized(() => [ final widgets = useMemoized(() => [
for (final subcomponent in componentState.designDraft.components) for (final subcomponent in componentState.designDraft.components)
CanvasObject( CanvasObject(
@ -100,8 +90,6 @@ class DesignComponentPage extends HookWidget {
movingWidget.value = null; movingWidget.value = null;
}, },
child: MouseRegion( child: MouseRegion(
opaque: false,
hitTestBehavior: HitTestBehavior.translucent,
cursor: movingWidget.value == subcomponent ? SystemMouseCursors.move : MouseCursor.defer, cursor: movingWidget.value == subcomponent ? SystemMouseCursors.move : MouseCursor.defer,
child: VisualComponent( child: VisualComponent(
name: componentState.getMetaByInstance(subcomponent.instanceId).item2.componentName, name: componentState.getMetaByInstance(subcomponent.instanceId).item2.componentName,
@ -120,18 +108,6 @@ class DesignComponentPage extends HookWidget {
: Colors.black, : Colors.black,
} : null, } : null,
isNextToSimulate: isSimulating.value && componentState.partialVisualSimulation!.nextToSimulate.contains(subcomponent.instanceId), isNextToSimulate: isSimulating.value && componentState.partialVisualSimulation!.nextToSimulate.contains(subcomponent.instanceId),
onInputHovered: designSelection.value == 'wiring' && sourceToConnect.value != null ? (input) {
hoveredIO.value = '${subcomponent.instanceId}/$input';
} : null,
onInputUnhovered: designSelection.value == 'wiring' && sourceToConnect.value != null ? (input) {
hoveredIO.value = null;
} : null,
onOutputHovered: designSelection.value == 'wiring' && sourceToConnect.value == null ? (output) {
hoveredIO.value = '${subcomponent.instanceId}/$output';
} : null,
onOutputUnhovered: designSelection.value == 'wiring' && sourceToConnect.value == null ? (output) {
hoveredIO.value = null;
} : null,
), ),
), ),
), ),
@ -165,12 +141,10 @@ class DesignComponentPage extends HookWidget {
movingWidgetUpdater.value = (dx, dy) { movingWidgetUpdater.value = (dx, dy) {
debouncer.call([dx, dy]); debouncer.call([dx, dy]);
}; };
movingWidget.value = input;
}, },
onPointerUp: (event) { onPointerUp: (event) {
componentState.updateDesign(componentState.designDraft); componentState.updateDesign(componentState.designDraft);
movingWidgetUpdater.value = null; movingWidgetUpdater.value = null;
movingWidget.value = null;
}, },
child: IOComponent( child: IOComponent(
input: true, input: true,
@ -184,13 +158,6 @@ class DesignComponentPage extends HookWidget {
onTap: isSimulating.value ? () { onTap: isSimulating.value ? () {
componentState.partialVisualSimulation!.toggleInput(input.name); componentState.partialVisualSimulation!.toggleInput(input.name);
} : null, } : null,
onHovered: designSelection.value == 'wiring' && sourceToConnect.value == null ? () {
hoveredIO.value = 'self/${input.name}';
} : null,
onUnhovered: designSelection.value == 'wiring' && sourceToConnect.value == null ? () {
hoveredIO.value = null;
} : null,
flashing: designSelection.value == 'wiring' && sourceToConnect.value == null,
), ),
), ),
), ),
@ -223,12 +190,10 @@ class DesignComponentPage extends HookWidget {
movingWidgetUpdater.value = (dx, dy) { movingWidgetUpdater.value = (dx, dy) {
debouncer.call([dx, dy]); debouncer.call([dx, dy]);
}; };
movingWidget.value = output;
}, },
onPointerUp: (event) { onPointerUp: (event) {
componentState.updateDesign(componentState.designDraft); componentState.updateDesign(componentState.designDraft);
movingWidgetUpdater.value = null; movingWidgetUpdater.value = null;
movingWidget.value = null;
}, },
child: IOComponent( child: IOComponent(
input: false, input: false,
@ -239,13 +204,6 @@ class DesignComponentPage extends HookWidget {
: componentState.partialVisualSimulation!.inputsValues['self/${output.name}'] == false ? Colors.red : componentState.partialVisualSimulation!.inputsValues['self/${output.name}'] == false ? Colors.red
: null) : null)
: null, : null,
onHovered: designSelection.value == 'wiring' && sourceToConnect.value != null ? () {
hoveredIO.value = 'self/${output.name}';
} : null,
onUnhovered: designSelection.value == 'wiring' && sourceToConnect.value != null ? () {
hoveredIO.value = null;
} : null,
flashing: designSelection.value == 'wiring' && sourceToConnect.value != null,
), ),
), ),
), ),
@ -352,46 +310,16 @@ class DesignComponentPage extends HookWidget {
dy: min(from.dy, to.dy), dy: min(from.dy, to.dy),
width: (to - from).dx.abs(), width: (to - from).dx.abs(),
height: (to - from).dy.abs(), height: (to - from).dy.abs(),
child: MouseRegion( child: IgnorePointer(
hitTestBehavior: HitTestBehavior.translucent, child: WireWidget(
opaque: false, from: from,
onEnter: (_) { to: to,
if (designSelection.value == 'wiring') { color: wireColor,
wireToDelete.value = wire.wireId;
}
},
onExit: (_) {
wireToDelete.value = null;
},
child: GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () async {
if (designSelection.value == 'wiring') {
if (wireToDelete.value != wire.wireId) {
wireToDelete.value = wire.wireId;
}
else {
// Delete the wire
await componentState.updateDesign(componentState.designDraft.copyWith(
wires: componentState.designDraft.wires.where((w) => w.wireId != wireToDelete.value).toList(),
));
await componentState.updateWiring(componentState.wiringDraft.copyWith(
wires: componentState.wiringDraft.wires.where((w) => w.wireId != wireToDelete.value).toList(),
));
wireToDelete.value = null;
}
}
},
child: WireWidget(
from: from,
to: to,
color: wireToDelete.value == wire.wireId ? Colors.red : wireColor,
),
), ),
), ),
); );
})(), })(),
], [componentState.designDraft, isSimulating.value, componentState.partialVisualSimulation!.outputsValues, designSelection.value, sourceToConnect.value]); ], [componentState.designDraft, isSimulating.value, componentState.partialVisualSimulation!.outputsValues]);
useEffect(() { useEffect(() {
final wList = widgets; final wList = widgets;
canvasController.addCanvasObjects(wList); canvasController.addCanvasObjects(wList);
@ -416,173 +344,95 @@ class DesignComponentPage extends HookWidget {
title: Text('${isSimulating.value ? 'Simulation' : 'Design'} - ${component.componentName}'), title: Text('${isSimulating.value ? 'Simulation' : 'Design'} - ${component.componentName}'),
actions: [ actions: [
IconButton( IconButton(
icon: Icon(isSimulating.value ? Icons.stop : Icons.play_arrow), icon: Icon(isSimulating.value ? Icons.stop : Icons.start),
tooltip: isSimulating.value ? 'Stop Simulation' : 'Start Simulation', tooltip: isSimulating.value ? 'Stop Simulation' : 'Start Simulation',
onPressed: () { onPressed: () {
isSimulating.value = !isSimulating.value; isSimulating.value = !isSimulating.value;
designSelection.value = null;
}, },
), ),
], ],
), ),
body: OrientationBuilder( body: GestureDetector(
builder: (context, orientation) { onPanUpdate: (update) {
final stackCanvas = GestureDetector( final hw = movingWidgetUpdater.value;
behavior: HitTestBehavior.translucent, if (hw == null || isSimulating.value) {
onPanUpdate: (update) { canvasController.offset = canvasController.offset.translate(update.delta.dx, update.delta.dy);
final hw = movingWidgetUpdater.value;
if (hw == null || isSimulating.value) {
canvasController.offset = canvasController.offset.translate(update.delta.dx, update.delta.dy);
}
else {
hw(update.delta.dx, update.delta.dy);
}
},
onTap: () {
if (designSelection.value == 'wiring') {
// Handle wire creation
if (hoveredIO.value == null) {
// If clicking on something not hovered, ignore
return;
}
else if (sourceToConnect.value == null) {
sourceToConnect.value = hoveredIO.value;
hoveredIO.value = null;
} else {
// Create wire only if sink is not already connected
if (componentState.wiringDraft.wires.where((w) => w.input == hoveredIO.value).isNotEmpty) {
// Sink already connected
final sinkType = hoveredIO.value!.startsWith('self/') ? 'component output' : 'subcomponent input';
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('Wire already connected to that $sinkType.'),
));
return;
}
componentState.updateWiring(componentState.wiringDraft.copyWith(
wires: componentState.wiringDraft.wires + [
WiringWire(
wireId: const Uuid().v4(),
output: sourceToConnect.value!,
input: hoveredIO.value!,
),
],
));
sourceToConnect.value = null;
hoveredIO.value = null;
}
}
},
child: Stack(
children: [
StackCanvas(
key: canvasKey,
canvasController: canvasController,
animationDuration: const Duration(milliseconds: 50),
// disposeController: false,
backgroundColor: Theme.of(context).colorScheme.background,
),
if (!isSimulating.value)
Positioned(
bottom: 0,
right: 0,
child: MouseRegion(
hitTestBehavior: HitTestBehavior.translucent,
opaque: false,
onEnter: (_) {
deleteOnDrop.value = true;
},
onExit: (_) {
deleteOnDrop.value = false;
},
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Icon(
Icons.delete,
color: movingWidget.value != null && deleteOnDrop.value ? Colors.red : null,
),
),
),
),
],
),
);
final debuggingButtons = DebuggingButtons(
partialSimulation: simulatePartially.value,
onPartialSimulationToggle: () {
simulatePartially.value = !simulatePartially.value;
},
onReset: simulatePartially.value ? () {
componentState.partialVisualSimulation!.restart();
} : null,
onNextStep: simulatePartially.value && componentState.partialVisualSimulation!.nextToSimulate.isNotEmpty ? () {
componentState.partialVisualSimulation!.nextStep();
} : null,
);
final componentPicker = ComponentPicker(
key: pickerKey,
onSeletionUpdate: (selection) {
designSelection.value = selection;
if (selection != 'wiring') {
wireToDelete.value = null;
sourceToConnect.value = null;
}
},
);
if (orientation == Orientation.portrait) {
return Column(
mainAxisSize: MainAxisSize.max,
children: [
Expanded(
child: Stack(
children: [
stackCanvas,
if (isSimulating.value)
Positioned(
top: 8,
left: 0,
right: 0,
child: Center(
child: debuggingButtons,
),
),
],
),
),
if (!isSimulating.value)
componentPicker,
],
);
} }
else { else {
return Row( hw(update.delta.dx, update.delta.dy);
mainAxisSize: MainAxisSize.max,
children: [
Expanded(
child: Stack(
children: [
stackCanvas,
if (isSimulating.value)
Positioned(
top: 8,
left: 0,
right: 0,
child: Center(
child: debuggingButtons,
),
),
],
),
),
if (!isSimulating.value)
componentPicker,
],
);
} }
} },
child: OrientationBuilder(
builder: (context, orientation) {
final stackCanvas = StackCanvas(
key: canvasKey,
canvasController: canvasController,
animationDuration: const Duration(milliseconds: 50),
// disposeController: false,
backgroundColor: Theme.of(context).colorScheme.background,
);
final debuggingButtons = DebuggingButtons(
partialSimulation: simulatePartially.value,
onPartialSimulationToggle: () {
simulatePartially.value = !simulatePartially.value;
},
onReset: simulatePartially.value ? () {
componentState.partialVisualSimulation!.restart();
} : null,
onNextStep: simulatePartially.value && componentState.partialVisualSimulation!.nextToSimulate.isNotEmpty ? () {
componentState.partialVisualSimulation!.nextStep();
} : null,
);
if (orientation == Orientation.portrait) {
return Column(
mainAxisSize: MainAxisSize.max,
children: [
Expanded(
child: Stack(
children: [
stackCanvas,
if (isSimulating.value)
Positioned(
top: 8,
left: 0,
right: 0,
child: Center(
child: debuggingButtons,
),
),
],
),
),
],
);
}
else {
return Row(
mainAxisSize: MainAxisSize.max,
children: [
Expanded(
child: Stack(
children: [
stackCanvas,
if (isSimulating.value)
Positioned(
top: 8,
left: 0,
right: 0,
child: Center(
child: debuggingButtons,
),
),
],
),
),
],
);
}
}
),
), ),
); );
} }
@ -627,168 +477,4 @@ class DebuggingButtons extends StatelessWidget {
), ),
); );
} }
} }
class ComponentPicker extends HookWidget {
const ComponentPicker({required this.onSeletionUpdate, super.key});
final void Function(String? selection) onSeletionUpdate;
@override
Widget build(BuildContext context) {
final projectsState = useProvider<ProjectsState>();
final tickerProvider = useSingleTickerProvider();
final selection = useState<String?>(null);
final tabBarControllerState = useState<TabController?>(null );
useEffect(() {
selection.addListener(() {
onSeletionUpdate(selection.value);
});
tabBarControllerState.value = TabController(
length: 1 + projectsState.projects.length,
vsync: tickerProvider,
initialIndex: 1,
);
tabBarControllerState.value!.addListener(() {
if (tabBarControllerState.value!.index == 0) {
selection.value = 'wiring';
}
else {
selection.value = null;
}
});
return () {
tabBarControllerState.value?.dispose();
};
}, []);
final tabBarController = tabBarControllerState.value!;
return OrientationBuilder(
builder: (context, orientation) {
return SizedBox(
height: orientation == Orientation.portrait ? 200 : null,
width: orientation == Orientation.landscape ? 300 : null,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TabBar(
controller: tabBarController,
tabs: [
const Tab(
text: 'Wiring',
),
for (final project in projectsState.projects)
Tab(
text: project.projectName,
),
],
isScrollable: true,
),
Expanded(
child: TabBarView(
controller: tabBarController,
children: [
Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: const [
Padding(
padding: EdgeInsets.all(8.0),
child: Text(
'To create wires, click a source and then click a sink to link them.',
),
),
Padding(
padding: EdgeInsets.all(8.0),
child: Text(
'To remove wires, click them or tap them twice.',
),
),
],
),
),
for (final project in projectsState.projects)
HookBuilder(
builder: (context) {
final scrollController = useScrollController();
final projectState = useFuture(() async {
final projectState = ProjectState();
await projectState.setCurrentProject(project);
return projectState;
}());
if (projectState.data == null) {
return Container();
}
final components = projectState.data!.index.components;
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Padding(
padding: EdgeInsets.all(8.0),
child: Text('To add a component, select it below and then click on the canvas to place it.'),
),
Expanded(
child: Scrollbar(
controller: scrollController,
scrollbarOrientation: orientation == Orientation.portrait ? ScrollbarOrientation.bottom : ScrollbarOrientation.right,
child: SingleChildScrollView(
controller: scrollController,
scrollDirection: orientation == Orientation.portrait ? Axis.horizontal : Axis.vertical,
child: Wrap(
direction: orientation == Orientation.portrait ? Axis.vertical : Axis.horizontal,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
for (final component in components)
IntrinsicWidth(
child: Card(
color: selection.value == '${project.projectId}/${component.componentId}' ? Theme.of(context).colorScheme.primaryContainer : null,
child: InkWell(
onTap: () {
if (selection.value != '${project.projectId}/${component.componentId}') {
selection.value = '${project.projectId}/${component.componentId}';
}
else {
selection.value = null;
}
},
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
component.componentName,
style: selection.value == '${project.projectId}/${component.componentId}'
? TextStyle(
inherit: true,
color: Theme.of(context).colorScheme.onPrimaryContainer,
)
: null,
),
),
),
),
),
],
),
),
),
),
],
);
}
),
],
),
),
],
),
);
},
);
}
}

View file

@ -275,13 +275,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.0" version: "2.1.0"
hetu_script:
dependency: "direct main"
description:
name: hetu_script
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.12"
http_multi_server: http_multi_server:
dependency: transitive dependency: transitive
description: description:
@ -499,13 +492,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.1.0" version: "3.1.0"
recase:
dependency: transitive
description:
name: recase
url: "https://pub.dartlang.org"
source: hosted
version: "4.0.0"
share_plus: share_plus:
dependency: "direct main" dependency: "direct main"
description: description:

View file

@ -41,7 +41,6 @@ dependencies:
stack_canvas: stack_canvas:
git: https://github.com/dancojocaru2000/stack_canvas.git git: https://github.com/dancojocaru2000/stack_canvas.git
tuple: ^2.0.0 tuple: ^2.0.0
hetu_script: ^0.3.12
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: