diff --git a/lib/components/visual_component.dart b/lib/components/visual_component.dart index 91b9940..443d188 100644 --- a/lib/components/visual_component.dart +++ b/lib/components/visual_component.dart @@ -140,7 +140,7 @@ class VisualComponent extends HookWidget { } static double getHeightOfIO(BuildContext context, List options, int index, [TextStyle? textStyle]) { - assert(index < options.length); + assert(index <= options.length); getHeightOfText(String text) { final textPainter = TextPainter( text: TextSpan( @@ -163,7 +163,9 @@ class VisualComponent extends HookWidget { for (var i = 0; i < index; i++) { result += 5.0 + getHeightOfText(options[i]) + 5.0; } - result += 5.0 + getHeightOfText(options[index]); + if (index < options.length) { + result += 5.0 + getHeightOfText(options[index]); + } return result; } } diff --git a/lib/pages/design_component.dart b/lib/pages/design_component.dart index 06ea245..ef98627 100644 --- a/lib/pages/design_component.dart +++ b/lib/pages/design_component.dart @@ -12,7 +12,9 @@ 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/provider_hook.dart'; import 'package:logic_circuits_simulator/utils/stack_canvas_controller_hook.dart'; +import 'package:provider/provider.dart'; import 'package:stack_canvas/stack_canvas.dart'; +import 'package:tuple/tuple.dart'; import 'package:uuid/uuid.dart'; Key canvasKey = GlobalKey(); @@ -56,6 +58,9 @@ class DesignComponentPage extends HookWidget { final cs = componentState; // First remove all connected wires if (w is DesignComponent) { + // Get project state to be able to remove dependency + final projectState = Provider.of(context, listen: false); + final wires = cs.wiringDraft.wires .where( (wire) => wire.input.startsWith('${w.instanceId}/') || wire.output.startsWith('${w.instanceId}/') @@ -63,6 +68,9 @@ class DesignComponentPage extends HookWidget { .map((wire) => wire.wireId) .toList(); + // Get component id before removing + final componentId = cs.wiringDraft.instances.where((inst) => inst.instanceId == w.instanceId).first.componentId; + await cs.updateDesign(cs.designDraft.copyWith( wires: cs.designDraft.wires .where((wire) => !wires.contains(wire.wireId)) @@ -83,6 +91,12 @@ class DesignComponentPage extends HookWidget { .where((comp) => comp.instanceId != w.instanceId) .toList(), )); + + // Remove dependency if it's the last of its kind + if (!cs.wiringDraft.instances.map((inst) => inst.componentId).contains(componentId)) { + componentState.removeDependency(componentId, modifyCurrentComponent: true); + await projectState.editComponent(componentState.currentComponent!); + } } else if (w is DesignInput) { final wires = cs.wiringDraft.wires @@ -534,7 +548,7 @@ class DesignComponentPage extends HookWidget { hw(update.delta.dx, update.delta.dy); } }, - onTapUp: (update) { + onTapUp: (update) async { final canvasCenterLocation = canvasController.canvasSize / 2; final canvasCenterLocationOffset = Offset(canvasCenterLocation.width, canvasCenterLocation.height); final canvasLocation = update.localPosition - canvasCenterLocationOffset + canvasController.offset; @@ -603,7 +617,62 @@ class DesignComponentPage extends HookWidget { designSelection.value = null; } else { + final currentProjectState = Provider.of(context, listen: false); + // Add subcomponent + final splitted = ds.split('/'); + var projectId = splitted[0]; + final componentId = splitted[1]; + + if (Provider.of(context, listen: false).currentProject!.projectId == projectId) { + projectId = 'self'; + } + + final depId = '$projectId/$componentId'; + final project = projectId == 'self' + ? Provider.of(context, listen: false).currentProject! + : Provider.of(context, listen: false).index.projects.where((p) => p.projectId == projectId).first; + final projectState = ProjectState(); + await projectState.setCurrentProject(project); + final component = projectState.index.components.where((c) => c.componentId == componentId).first; + + // Add dependency + if (!componentState.hasDependency(depId)) { + componentState.addDependency( + depId, + Tuple2( + project, + component, + ), + modifyCurrentComponent: true, + ); + await currentProjectState.editComponent(componentState.currentComponent!); + } + + // Create component instance + final instanceId = const Uuid().v4(); + await componentState.updateWiring(componentState.wiringDraft.copyWith( + instances: componentState.wiringDraft.instances + [ + WiringInstance( + componentId: depId, + instanceId: instanceId, + ), + ], + )); + await componentState.updateDesign(componentState.designDraft.copyWith( + components: componentState.designDraft.components + [ + DesignComponent( + instanceId: instanceId, + x: canvasLocation.dx, + y: canvasLocation.dy, + ), + ], + )); + + // Recreate simulation with new subcomponent + await componentState.recreatePartialSimulation(); + + designSelection.value = null; } }, child: Stack( diff --git a/lib/state/component.dart b/lib/state/component.dart index 6e85261..9e6c728 100644 --- a/lib/state/component.dart +++ b/lib/state/component.dart @@ -115,7 +115,7 @@ class ComponentState extends ChangeNotifier { unsatisfiedDependencies.add(depId); } else { - _dependenciesMap[depId] = maybeDep; + addDependency(depId, maybeDep); } } if (unsatisfiedDependencies.isNotEmpty) { @@ -124,10 +124,34 @@ class ComponentState extends ChangeNotifier { await _loadComponentFiles(); - if (component.visualDesigned) { + await recreatePartialSimulation(); + + notifyListeners(); + } + + void addDependency(String depId, Tuple2 dependency, {bool modifyCurrentComponent = false}) { + _dependenciesMap[depId] = dependency; + if (modifyCurrentComponent && _currentComponent?.dependencies.contains(depId) == false) { + _currentComponent = _currentComponent?.copyWith( + dependencies: (_currentComponent?.dependencies ?? []) + [depId], + ); + } + } + + void removeDependency(String depId, {bool modifyCurrentComponent = false}) { + _dependenciesMap.remove(depId); + if (modifyCurrentComponent && _currentComponent?.dependencies.contains(depId) == true) { + _currentComponent = _currentComponent?.copyWith( + dependencies: _currentComponent?.dependencies.where((dep) => dep != depId).toList() ?? [], + ); + } + } + + Future recreatePartialSimulation() async { + if (_currentComponent!.visualDesigned) { _partialVisualSimulation = await PartialVisualSimulation.init( - project: project, - component: component, + project: _currentProject!, + component: _currentComponent!, state: this, onRequiredDependency: _onRequiredDependency, ); @@ -136,6 +160,8 @@ class ComponentState extends ChangeNotifier { notifyListeners(); } + bool hasDependency(String depId) => _dependenciesMap.containsKey(depId); + void noComponent() { _dependenciesMap.clear(); _currentProject = null; diff --git a/lib/state/project.dart b/lib/state/project.dart index c29ff85..bac5af7 100644 --- a/lib/state/project.dart +++ b/lib/state/project.dart @@ -90,8 +90,8 @@ class ProjectState extends ChangeNotifier { await _updateIndex( index.copyWith( components: index.components - .where((c) => c.componentId != component.componentId) - .toList() + [component], + .map((c) => c.componentId == component.componentId ? component : c) + .toList(), ) ); } diff --git a/lib/state/projects.dart b/lib/state/projects.dart index 13a0256..eafbdaa 100644 --- a/lib/state/projects.dart +++ b/lib/state/projects.dart @@ -73,8 +73,7 @@ class ProjectsState extends ChangeNotifier { await _updateIndex( index.copyWith( projects: index.projects - .where((p) => p.projectId != project.projectId) - .followedBy([project]) + .map((p) => p.projectId == project.projectId ? project : p) .toList() ) );