mirror of
				https://github.com/dancojocaru2000/logic-circuits-simulator.git
				synced 2025-10-31 06:56:31 +02:00 
			
		
		
		
	Implemented partial simulation and design*
- Implemented partial (step by step) simulation of visually designed components - Implemented moving components in design mode and simulating components in simulation mode (click inputs to toggle) TODO: - add/remove subcomponents, wires via GUI - add GUI for step by step simulation
This commit is contained in:
		
							parent
							
								
									4a6caee702
								
							
						
					
					
						commit
						c2d5d86554
					
				
					 11 changed files with 2035 additions and 99 deletions
				
			
		
							
								
								
									
										296
									
								
								lib/components/visual_component.dart
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										296
									
								
								lib/components/visual_component.dart
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,296 @@ | |||
| import 'dart:math'; | ||||
| import 'dart:ui'; | ||||
| 
 | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | ||||
| 
 | ||||
| class VisualComponent extends HookWidget { | ||||
|   final String name; | ||||
|   final List<String> inputs; | ||||
|   final List<String> outputs; | ||||
|   final Map<String, Color?> inputColors; | ||||
|   final Map<String, Color?> outputColors; | ||||
| 
 | ||||
|   VisualComponent({super.key, required this.name, required this.inputs, required this.outputs, Map<String, Color?>? inputColors, Map<String, Color?>? outputColors})  | ||||
|     : inputColors = inputColors ?? {} | ||||
|     , outputColors = outputColors ?? {}; | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     final hovered = useState(false); | ||||
| 
 | ||||
|     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)); | ||||
| 
 | ||||
|     return MouseRegion( | ||||
|       onEnter: (event) => hovered.value = true, | ||||
|       onExit: (event) => hovered.value = false, | ||||
|       child: Row( | ||||
|         children: [ | ||||
|           Column( | ||||
|             children: [ | ||||
|               for (final input in inputs) Padding( | ||||
|                 padding: const EdgeInsets.symmetric(vertical: 5.0), | ||||
|                   child: IOLabel( | ||||
|                     label: input, | ||||
|                     input: true, | ||||
|                     lineColor: hovered.value  | ||||
|                       ? Theme.of(context).colorScheme.primary  | ||||
|                       : inputColors[input] ?? Colors.black, | ||||
|                     width: inputsWidth, | ||||
|                   ), | ||||
|               ), | ||||
|             ], | ||||
|           ), | ||||
|           Container( | ||||
|             width: 100, | ||||
|             decoration: BoxDecoration( | ||||
|               border: Border.all( | ||||
|                 color: hovered.value ? Theme.of(context).colorScheme.primary : Colors.black, | ||||
|               ), | ||||
|             ), | ||||
|             child: Center( | ||||
|               child: Text( | ||||
|                 name, | ||||
|                 softWrap: true, | ||||
|               ), | ||||
|             ), | ||||
|           ), | ||||
|           Column( | ||||
|             children: [ | ||||
|               for (final output in outputs) Padding( | ||||
|                 padding: const EdgeInsets.symmetric(vertical: 5.0), | ||||
|                   child: IOLabel( | ||||
|                     label: output, | ||||
|                     input: false, | ||||
|                     lineColor: hovered.value  | ||||
|                       ? Theme.of(context).colorScheme.primary  | ||||
|                       : outputColors[output] ?? Colors.black, | ||||
|                     width: outputsWidth, | ||||
|                   ), | ||||
|               ), | ||||
|             ], | ||||
|           ), | ||||
|         ], | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   static double getNeededWidth(BuildContext context, List<String> inputs, List<String> outputs, [TextStyle? textStyle]) { | ||||
|     final inputsWidth = inputs.map((input) => IOLabel.getNeededWidth(context, input, textStyle)).fold<double>(0, (previousValue, element) => max(previousValue, element)); | ||||
|     final outputsWidth = outputs.map((output) => IOLabel.getNeededWidth(context, output, textStyle)).fold<double>(0, (previousValue, element) => max(previousValue, element)); | ||||
|     return inputsWidth + outputsWidth + 100; | ||||
|   } | ||||
| 
 | ||||
|   static double getHeightOfIO(BuildContext context, List<String> options, int index, [TextStyle? textStyle]) { | ||||
|     assert(index < options.length); | ||||
|     getHeightOfText(String text) { | ||||
|       final textPainter = TextPainter( | ||||
|         text: TextSpan( | ||||
|           text: text, | ||||
|           style: (textStyle ?? DefaultTextStyle.of(context).style).merge( | ||||
|             const TextStyle( | ||||
|               inherit: true, | ||||
|               fontFeatures: [ | ||||
|                 FontFeature.tabularFigures(), | ||||
|               ], | ||||
|             ), | ||||
|           ), | ||||
|         ), | ||||
|         textDirection: TextDirection.ltr, | ||||
|         maxLines: 1, | ||||
|       )..layout(); | ||||
|       return textPainter.height; | ||||
|     } | ||||
|     var result = 0.0; | ||||
|     for (var i = 0; i < index; i++) { | ||||
|       result += 5.0 + getHeightOfText(options[i]) + 5.0; | ||||
|     } | ||||
|     result += 5.0 + getHeightOfText(options[index]); | ||||
|     return result; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| class IOComponent extends HookWidget { | ||||
|   final bool input; | ||||
|   final String name; | ||||
|   final double width; | ||||
|   final double circleDiameter; | ||||
|   final Color? color; | ||||
|   final void Function()? onTap; | ||||
| 
 | ||||
|   const IOComponent({super.key, required this.name, required this.input, this.width = 100, this.circleDiameter = 20, this.color, this.onTap}); | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     final hovered = useState(false); | ||||
| 
 | ||||
|     return MouseRegion( | ||||
|       onEnter: (event) => hovered.value = true, | ||||
|       onExit: (event) => hovered.value = false, | ||||
|       hitTestBehavior: HitTestBehavior.translucent, | ||||
|       child: GestureDetector( | ||||
|         onTap: onTap, | ||||
|         child: Builder( | ||||
|           builder: (context) { | ||||
|             final lineColor = hovered.value ? Theme.of(context).colorScheme.primary : color ?? Colors.black; | ||||
|        | ||||
|             return Row( | ||||
|               crossAxisAlignment: CrossAxisAlignment.center, | ||||
|               children: [ | ||||
|                 if (input) Container( | ||||
|                   width: circleDiameter, | ||||
|                   height: circleDiameter, | ||||
|                   decoration: BoxDecoration( | ||||
|                     border: Border.all(color: lineColor), | ||||
|                     shape: BoxShape.circle, | ||||
|                     color: color, | ||||
|                   ), | ||||
|                 ), | ||||
|                 Padding( | ||||
|                   padding: EdgeInsets.only(bottom: circleDiameter - 2), | ||||
|                   child: IOLabel( | ||||
|                     label: name, | ||||
|                     input: !input, | ||||
|                     lineColor: lineColor, | ||||
|                     width: width - circleDiameter, | ||||
|                   ), | ||||
|                 ), | ||||
|                 if (!input) Container( | ||||
|                   width: circleDiameter, | ||||
|                   height: circleDiameter, | ||||
|                   decoration: BoxDecoration( | ||||
|                     border: Border.all(color: lineColor), | ||||
|                     shape: BoxShape.circle, | ||||
|                     color: color, | ||||
|                   ), | ||||
|                 ), | ||||
|               ], | ||||
|             ); | ||||
|           } | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   static double getNeededWidth(BuildContext context, String name, {double circleDiameter = 20, TextStyle? textStyle}) { | ||||
|     return circleDiameter + IOLabel.getNeededWidth(context, name, textStyle); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| class IOLabel extends StatelessWidget { | ||||
|   final bool input; | ||||
|   final String label; | ||||
|   final Color lineColor; | ||||
|   final double width; | ||||
| 
 | ||||
|   const IOLabel({super.key, required this.lineColor, required this.label, required this.input, this.width = 80}); | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return Container( | ||||
|       width: width, | ||||
|       height: 20, | ||||
|       decoration: BoxDecoration( | ||||
|         border: Border( | ||||
|           bottom: BorderSide(color: lineColor), | ||||
|         ), | ||||
|       ), | ||||
|       child: Align( | ||||
|         alignment: input ? Alignment.bottomRight : Alignment.bottomLeft, | ||||
|         child: Padding( | ||||
|           padding: const EdgeInsets.symmetric(horizontal: 4.0), | ||||
|           child: Text( | ||||
|             label, | ||||
|             style: const TextStyle( | ||||
|               inherit: true, | ||||
|               fontFeatures: [ | ||||
|                 FontFeature.tabularFigures(), | ||||
|               ], | ||||
|             ), | ||||
|           ), | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   static double getNeededWidth(BuildContext context, String text, [TextStyle? textStyle]) { | ||||
|     final textPainter = TextPainter( | ||||
|       text: TextSpan( | ||||
|         text: text, | ||||
|         style: (textStyle ?? DefaultTextStyle.of(context).style).merge( | ||||
|           const TextStyle( | ||||
|             inherit: true, | ||||
|             fontFeatures: [ | ||||
|               FontFeature.tabularFigures(), | ||||
|             ], | ||||
|           ), | ||||
|         ), | ||||
|       ), | ||||
|       textDirection: TextDirection.ltr, | ||||
|       maxLines: 1, | ||||
|     )..layout(); | ||||
|     return textPainter.width + 10; | ||||
|   }   | ||||
| } | ||||
| 
 | ||||
| class WireWidget extends StatelessWidget { | ||||
|   final Offset from; | ||||
|   final Offset to; | ||||
|   final Color color; | ||||
| 
 | ||||
|   const WireWidget({ | ||||
|     required this.from, | ||||
|     required this.to, | ||||
|     this.color = Colors.black, | ||||
|     super.key, | ||||
|   }); | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return CustomPaint( | ||||
|       painter: _WireCustomPainter( | ||||
|         color: color,  | ||||
|         primaryDiagonal:  | ||||
|           (from.dx < to.dx && from.dy < to.dy) ||  | ||||
|           (from.dx > to.dx && from.dy > to.dy), | ||||
|       ), | ||||
|       child: SizedBox( | ||||
|         height: (to - from).dy.abs(), | ||||
|         width: (to - from).dx.abs(), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| class _WireCustomPainter extends CustomPainter { | ||||
|   final Color color; | ||||
|   final bool primaryDiagonal; | ||||
| 
 | ||||
|   const _WireCustomPainter({required this.color, required this.primaryDiagonal}); | ||||
| 
 | ||||
|   @override | ||||
|   void paint(Canvas canvas, Size size) { | ||||
|     final paint = Paint() | ||||
|       ..color = color; | ||||
|     if (primaryDiagonal) { | ||||
|       canvas.drawLine( | ||||
|         Offset.zero, | ||||
|         Offset(size.width, size.height), | ||||
|         paint, | ||||
|       ); | ||||
|     } | ||||
|     else { | ||||
|       canvas.drawLine( | ||||
|         Offset(size.width, 0), | ||||
|         Offset(0, size.height), | ||||
|         paint, | ||||
|       ); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   bool shouldRepaint(covariant CustomPainter oldDelegate) { | ||||
|     return true; | ||||
|   } | ||||
| } | ||||
|  | @ -1,4 +1,5 @@ | |||
| export 'package:logic_circuits_simulator/models/projects.dart'; | ||||
| export 'package:logic_circuits_simulator/models/project.dart'; | ||||
| export 'package:logic_circuits_simulator/models/component.dart'; | ||||
| export 'package:logic_circuits_simulator/models/design.dart'; | ||||
| export 'package:logic_circuits_simulator/models/project.dart'; | ||||
| export 'package:logic_circuits_simulator/models/projects.dart'; | ||||
| export 'package:logic_circuits_simulator/models/wiring.dart'; | ||||
|  |  | |||
							
								
								
									
										60
									
								
								lib/models/design.dart
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								lib/models/design.dart
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,60 @@ | |||
| import 'package:freezed_annotation/freezed_annotation.dart'; | ||||
| 
 | ||||
| part 'design.freezed.dart'; | ||||
| part 'design.g.dart'; | ||||
| 
 | ||||
| @freezed | ||||
| class Design with _$Design { | ||||
|   const factory Design({ | ||||
|     required List<DesignComponent> components, | ||||
|     required List<DesignWire> wires, | ||||
|     required List<DesignInput> inputs, | ||||
|     required List<DesignOutput> outputs, | ||||
|   }) = _Design; | ||||
| 
 | ||||
|   factory Design.fromJson(Map<String, dynamic> json) => _$DesignFromJson(json); | ||||
| } | ||||
| 
 | ||||
| @freezed | ||||
| class DesignComponent with _$DesignComponent { | ||||
|   const factory DesignComponent({ | ||||
|     required String instanceId, | ||||
|     required double x, | ||||
|     required double y, | ||||
|   }) = _DesignComponent; | ||||
| 
 | ||||
|   factory DesignComponent.fromJson(Map<String, dynamic> json) => _$DesignComponentFromJson(json); | ||||
| } | ||||
| 
 | ||||
| @freezed | ||||
| class DesignWire with _$DesignWire { | ||||
|   const factory DesignWire({ | ||||
|     required String wireId, | ||||
|     required double x, | ||||
|     required double y, | ||||
|   }) = _DesignWire; | ||||
| 
 | ||||
|   factory DesignWire.fromJson(Map<String, dynamic> json) => _$DesignWireFromJson(json); | ||||
| } | ||||
| 
 | ||||
| @freezed | ||||
| class DesignInput with _$DesignInput { | ||||
|   const factory DesignInput({ | ||||
|     required String name, | ||||
|     required double x, | ||||
|     required double y, | ||||
|   }) = _DesignInput; | ||||
| 
 | ||||
|   factory DesignInput.fromJson(Map<String, dynamic> json) => _$DesignInputFromJson(json); | ||||
| } | ||||
| 
 | ||||
| @freezed | ||||
| class DesignOutput with _$DesignOutput { | ||||
|   const factory DesignOutput({ | ||||
|     required String name, | ||||
|     required double x, | ||||
|     required double y, | ||||
|   }) = _DesignOutput; | ||||
| 
 | ||||
|   factory DesignOutput.fromJson(Map<String, dynamic> json) => _$DesignOutputFromJson(json); | ||||
| } | ||||
							
								
								
									
										908
									
								
								lib/models/design.freezed.dart
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										908
									
								
								lib/models/design.freezed.dart
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,908 @@ | |||
| // coverage:ignore-file | ||||
| // GENERATED CODE - DO NOT MODIFY BY HAND | ||||
| // ignore_for_file: type=lint | ||||
| // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target | ||||
| 
 | ||||
| part of 'design.dart'; | ||||
| 
 | ||||
| // ************************************************************************** | ||||
| // FreezedGenerator | ||||
| // ************************************************************************** | ||||
| 
 | ||||
| T _$identity<T>(T value) => value; | ||||
| 
 | ||||
| final _privateConstructorUsedError = UnsupportedError( | ||||
|     'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); | ||||
| 
 | ||||
| Design _$DesignFromJson(Map<String, dynamic> json) { | ||||
|   return _Design.fromJson(json); | ||||
| } | ||||
| 
 | ||||
| /// @nodoc | ||||
| mixin _$Design { | ||||
|   List<DesignComponent> get components => throw _privateConstructorUsedError; | ||||
|   List<DesignWire> get wires => throw _privateConstructorUsedError; | ||||
|   List<DesignInput> get inputs => throw _privateConstructorUsedError; | ||||
|   List<DesignOutput> get outputs => throw _privateConstructorUsedError; | ||||
| 
 | ||||
|   Map<String, dynamic> toJson() => throw _privateConstructorUsedError; | ||||
|   @JsonKey(ignore: true) | ||||
|   $DesignCopyWith<Design> get copyWith => throw _privateConstructorUsedError; | ||||
| } | ||||
| 
 | ||||
| /// @nodoc | ||||
| abstract class $DesignCopyWith<$Res> { | ||||
|   factory $DesignCopyWith(Design value, $Res Function(Design) then) = | ||||
|       _$DesignCopyWithImpl<$Res>; | ||||
|   $Res call( | ||||
|       {List<DesignComponent> components, | ||||
|       List<DesignWire> wires, | ||||
|       List<DesignInput> inputs, | ||||
|       List<DesignOutput> outputs}); | ||||
| } | ||||
| 
 | ||||
| /// @nodoc | ||||
| class _$DesignCopyWithImpl<$Res> implements $DesignCopyWith<$Res> { | ||||
|   _$DesignCopyWithImpl(this._value, this._then); | ||||
| 
 | ||||
|   final Design _value; | ||||
|   // ignore: unused_field | ||||
|   final $Res Function(Design) _then; | ||||
| 
 | ||||
|   @override | ||||
|   $Res call({ | ||||
|     Object? components = freezed, | ||||
|     Object? wires = freezed, | ||||
|     Object? inputs = freezed, | ||||
|     Object? outputs = freezed, | ||||
|   }) { | ||||
|     return _then(_value.copyWith( | ||||
|       components: components == freezed | ||||
|           ? _value.components | ||||
|           : components // ignore: cast_nullable_to_non_nullable | ||||
|               as List<DesignComponent>, | ||||
|       wires: wires == freezed | ||||
|           ? _value.wires | ||||
|           : wires // ignore: cast_nullable_to_non_nullable | ||||
|               as List<DesignWire>, | ||||
|       inputs: inputs == freezed | ||||
|           ? _value.inputs | ||||
|           : inputs // ignore: cast_nullable_to_non_nullable | ||||
|               as List<DesignInput>, | ||||
|       outputs: outputs == freezed | ||||
|           ? _value.outputs | ||||
|           : outputs // ignore: cast_nullable_to_non_nullable | ||||
|               as List<DesignOutput>, | ||||
|     )); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /// @nodoc | ||||
| abstract class _$$_DesignCopyWith<$Res> implements $DesignCopyWith<$Res> { | ||||
|   factory _$$_DesignCopyWith(_$_Design value, $Res Function(_$_Design) then) = | ||||
|       __$$_DesignCopyWithImpl<$Res>; | ||||
|   @override | ||||
|   $Res call( | ||||
|       {List<DesignComponent> components, | ||||
|       List<DesignWire> wires, | ||||
|       List<DesignInput> inputs, | ||||
|       List<DesignOutput> outputs}); | ||||
| } | ||||
| 
 | ||||
| /// @nodoc | ||||
| class __$$_DesignCopyWithImpl<$Res> extends _$DesignCopyWithImpl<$Res> | ||||
|     implements _$$_DesignCopyWith<$Res> { | ||||
|   __$$_DesignCopyWithImpl(_$_Design _value, $Res Function(_$_Design) _then) | ||||
|       : super(_value, (v) => _then(v as _$_Design)); | ||||
| 
 | ||||
|   @override | ||||
|   _$_Design get _value => super._value as _$_Design; | ||||
| 
 | ||||
|   @override | ||||
|   $Res call({ | ||||
|     Object? components = freezed, | ||||
|     Object? wires = freezed, | ||||
|     Object? inputs = freezed, | ||||
|     Object? outputs = freezed, | ||||
|   }) { | ||||
|     return _then(_$_Design( | ||||
|       components: components == freezed | ||||
|           ? _value._components | ||||
|           : components // ignore: cast_nullable_to_non_nullable | ||||
|               as List<DesignComponent>, | ||||
|       wires: wires == freezed | ||||
|           ? _value._wires | ||||
|           : wires // ignore: cast_nullable_to_non_nullable | ||||
|               as List<DesignWire>, | ||||
|       inputs: inputs == freezed | ||||
|           ? _value._inputs | ||||
|           : inputs // ignore: cast_nullable_to_non_nullable | ||||
|               as List<DesignInput>, | ||||
|       outputs: outputs == freezed | ||||
|           ? _value._outputs | ||||
|           : outputs // ignore: cast_nullable_to_non_nullable | ||||
|               as List<DesignOutput>, | ||||
|     )); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /// @nodoc | ||||
| @JsonSerializable() | ||||
| class _$_Design implements _Design { | ||||
|   const _$_Design( | ||||
|       {required final List<DesignComponent> components, | ||||
|       required final List<DesignWire> wires, | ||||
|       required final List<DesignInput> inputs, | ||||
|       required final List<DesignOutput> outputs}) | ||||
|       : _components = components, | ||||
|         _wires = wires, | ||||
|         _inputs = inputs, | ||||
|         _outputs = outputs; | ||||
| 
 | ||||
|   factory _$_Design.fromJson(Map<String, dynamic> json) => | ||||
|       _$$_DesignFromJson(json); | ||||
| 
 | ||||
|   final List<DesignComponent> _components; | ||||
|   @override | ||||
|   List<DesignComponent> get components { | ||||
|     // ignore: implicit_dynamic_type | ||||
|     return EqualUnmodifiableListView(_components); | ||||
|   } | ||||
| 
 | ||||
|   final List<DesignWire> _wires; | ||||
|   @override | ||||
|   List<DesignWire> get wires { | ||||
|     // ignore: implicit_dynamic_type | ||||
|     return EqualUnmodifiableListView(_wires); | ||||
|   } | ||||
| 
 | ||||
|   final List<DesignInput> _inputs; | ||||
|   @override | ||||
|   List<DesignInput> get inputs { | ||||
|     // ignore: implicit_dynamic_type | ||||
|     return EqualUnmodifiableListView(_inputs); | ||||
|   } | ||||
| 
 | ||||
|   final List<DesignOutput> _outputs; | ||||
|   @override | ||||
|   List<DesignOutput> get outputs { | ||||
|     // ignore: implicit_dynamic_type | ||||
|     return EqualUnmodifiableListView(_outputs); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   String toString() { | ||||
|     return 'Design(components: $components, wires: $wires, inputs: $inputs, outputs: $outputs)'; | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   bool operator ==(dynamic other) { | ||||
|     return identical(this, other) || | ||||
|         (other.runtimeType == runtimeType && | ||||
|             other is _$_Design && | ||||
|             const DeepCollectionEquality() | ||||
|                 .equals(other._components, _components) && | ||||
|             const DeepCollectionEquality().equals(other._wires, _wires) && | ||||
|             const DeepCollectionEquality().equals(other._inputs, _inputs) && | ||||
|             const DeepCollectionEquality().equals(other._outputs, _outputs)); | ||||
|   } | ||||
| 
 | ||||
|   @JsonKey(ignore: true) | ||||
|   @override | ||||
|   int get hashCode => Object.hash( | ||||
|       runtimeType, | ||||
|       const DeepCollectionEquality().hash(_components), | ||||
|       const DeepCollectionEquality().hash(_wires), | ||||
|       const DeepCollectionEquality().hash(_inputs), | ||||
|       const DeepCollectionEquality().hash(_outputs)); | ||||
| 
 | ||||
|   @JsonKey(ignore: true) | ||||
|   @override | ||||
|   _$$_DesignCopyWith<_$_Design> get copyWith => | ||||
|       __$$_DesignCopyWithImpl<_$_Design>(this, _$identity); | ||||
| 
 | ||||
|   @override | ||||
|   Map<String, dynamic> toJson() { | ||||
|     return _$$_DesignToJson(this); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| abstract class _Design implements Design { | ||||
|   const factory _Design( | ||||
|       {required final List<DesignComponent> components, | ||||
|       required final List<DesignWire> wires, | ||||
|       required final List<DesignInput> inputs, | ||||
|       required final List<DesignOutput> outputs}) = _$_Design; | ||||
| 
 | ||||
|   factory _Design.fromJson(Map<String, dynamic> json) = _$_Design.fromJson; | ||||
| 
 | ||||
|   @override | ||||
|   List<DesignComponent> get components => throw _privateConstructorUsedError; | ||||
|   @override | ||||
|   List<DesignWire> get wires => throw _privateConstructorUsedError; | ||||
|   @override | ||||
|   List<DesignInput> get inputs => throw _privateConstructorUsedError; | ||||
|   @override | ||||
|   List<DesignOutput> get outputs => throw _privateConstructorUsedError; | ||||
|   @override | ||||
|   @JsonKey(ignore: true) | ||||
|   _$$_DesignCopyWith<_$_Design> get copyWith => | ||||
|       throw _privateConstructorUsedError; | ||||
| } | ||||
| 
 | ||||
| DesignComponent _$DesignComponentFromJson(Map<String, dynamic> json) { | ||||
|   return _DesignComponent.fromJson(json); | ||||
| } | ||||
| 
 | ||||
| /// @nodoc | ||||
| mixin _$DesignComponent { | ||||
|   String get instanceId => throw _privateConstructorUsedError; | ||||
|   double get x => throw _privateConstructorUsedError; | ||||
|   double get y => throw _privateConstructorUsedError; | ||||
| 
 | ||||
|   Map<String, dynamic> toJson() => throw _privateConstructorUsedError; | ||||
|   @JsonKey(ignore: true) | ||||
|   $DesignComponentCopyWith<DesignComponent> get copyWith => | ||||
|       throw _privateConstructorUsedError; | ||||
| } | ||||
| 
 | ||||
| /// @nodoc | ||||
| abstract class $DesignComponentCopyWith<$Res> { | ||||
|   factory $DesignComponentCopyWith( | ||||
|           DesignComponent value, $Res Function(DesignComponent) then) = | ||||
|       _$DesignComponentCopyWithImpl<$Res>; | ||||
|   $Res call({String instanceId, double x, double y}); | ||||
| } | ||||
| 
 | ||||
| /// @nodoc | ||||
| class _$DesignComponentCopyWithImpl<$Res> | ||||
|     implements $DesignComponentCopyWith<$Res> { | ||||
|   _$DesignComponentCopyWithImpl(this._value, this._then); | ||||
| 
 | ||||
|   final DesignComponent _value; | ||||
|   // ignore: unused_field | ||||
|   final $Res Function(DesignComponent) _then; | ||||
| 
 | ||||
|   @override | ||||
|   $Res call({ | ||||
|     Object? instanceId = freezed, | ||||
|     Object? x = freezed, | ||||
|     Object? y = freezed, | ||||
|   }) { | ||||
|     return _then(_value.copyWith( | ||||
|       instanceId: instanceId == freezed | ||||
|           ? _value.instanceId | ||||
|           : instanceId // ignore: cast_nullable_to_non_nullable | ||||
|               as String, | ||||
|       x: x == freezed | ||||
|           ? _value.x | ||||
|           : x // ignore: cast_nullable_to_non_nullable | ||||
|               as double, | ||||
|       y: y == freezed | ||||
|           ? _value.y | ||||
|           : y // ignore: cast_nullable_to_non_nullable | ||||
|               as double, | ||||
|     )); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /// @nodoc | ||||
| abstract class _$$_DesignComponentCopyWith<$Res> | ||||
|     implements $DesignComponentCopyWith<$Res> { | ||||
|   factory _$$_DesignComponentCopyWith( | ||||
|           _$_DesignComponent value, $Res Function(_$_DesignComponent) then) = | ||||
|       __$$_DesignComponentCopyWithImpl<$Res>; | ||||
|   @override | ||||
|   $Res call({String instanceId, double x, double y}); | ||||
| } | ||||
| 
 | ||||
| /// @nodoc | ||||
| class __$$_DesignComponentCopyWithImpl<$Res> | ||||
|     extends _$DesignComponentCopyWithImpl<$Res> | ||||
|     implements _$$_DesignComponentCopyWith<$Res> { | ||||
|   __$$_DesignComponentCopyWithImpl( | ||||
|       _$_DesignComponent _value, $Res Function(_$_DesignComponent) _then) | ||||
|       : super(_value, (v) => _then(v as _$_DesignComponent)); | ||||
| 
 | ||||
|   @override | ||||
|   _$_DesignComponent get _value => super._value as _$_DesignComponent; | ||||
| 
 | ||||
|   @override | ||||
|   $Res call({ | ||||
|     Object? instanceId = freezed, | ||||
|     Object? x = freezed, | ||||
|     Object? y = freezed, | ||||
|   }) { | ||||
|     return _then(_$_DesignComponent( | ||||
|       instanceId: instanceId == freezed | ||||
|           ? _value.instanceId | ||||
|           : instanceId // ignore: cast_nullable_to_non_nullable | ||||
|               as String, | ||||
|       x: x == freezed | ||||
|           ? _value.x | ||||
|           : x // ignore: cast_nullable_to_non_nullable | ||||
|               as double, | ||||
|       y: y == freezed | ||||
|           ? _value.y | ||||
|           : y // ignore: cast_nullable_to_non_nullable | ||||
|               as double, | ||||
|     )); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /// @nodoc | ||||
| @JsonSerializable() | ||||
| class _$_DesignComponent implements _DesignComponent { | ||||
|   const _$_DesignComponent( | ||||
|       {required this.instanceId, required this.x, required this.y}); | ||||
| 
 | ||||
|   factory _$_DesignComponent.fromJson(Map<String, dynamic> json) => | ||||
|       _$$_DesignComponentFromJson(json); | ||||
| 
 | ||||
|   @override | ||||
|   final String instanceId; | ||||
|   @override | ||||
|   final double x; | ||||
|   @override | ||||
|   final double y; | ||||
| 
 | ||||
|   @override | ||||
|   String toString() { | ||||
|     return 'DesignComponent(instanceId: $instanceId, x: $x, y: $y)'; | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   bool operator ==(dynamic other) { | ||||
|     return identical(this, other) || | ||||
|         (other.runtimeType == runtimeType && | ||||
|             other is _$_DesignComponent && | ||||
|             const DeepCollectionEquality() | ||||
|                 .equals(other.instanceId, instanceId) && | ||||
|             const DeepCollectionEquality().equals(other.x, x) && | ||||
|             const DeepCollectionEquality().equals(other.y, y)); | ||||
|   } | ||||
| 
 | ||||
|   @JsonKey(ignore: true) | ||||
|   @override | ||||
|   int get hashCode => Object.hash( | ||||
|       runtimeType, | ||||
|       const DeepCollectionEquality().hash(instanceId), | ||||
|       const DeepCollectionEquality().hash(x), | ||||
|       const DeepCollectionEquality().hash(y)); | ||||
| 
 | ||||
|   @JsonKey(ignore: true) | ||||
|   @override | ||||
|   _$$_DesignComponentCopyWith<_$_DesignComponent> get copyWith => | ||||
|       __$$_DesignComponentCopyWithImpl<_$_DesignComponent>(this, _$identity); | ||||
| 
 | ||||
|   @override | ||||
|   Map<String, dynamic> toJson() { | ||||
|     return _$$_DesignComponentToJson(this); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| abstract class _DesignComponent implements DesignComponent { | ||||
|   const factory _DesignComponent( | ||||
|       {required final String instanceId, | ||||
|       required final double x, | ||||
|       required final double y}) = _$_DesignComponent; | ||||
| 
 | ||||
|   factory _DesignComponent.fromJson(Map<String, dynamic> json) = | ||||
|       _$_DesignComponent.fromJson; | ||||
| 
 | ||||
|   @override | ||||
|   String get instanceId => throw _privateConstructorUsedError; | ||||
|   @override | ||||
|   double get x => throw _privateConstructorUsedError; | ||||
|   @override | ||||
|   double get y => throw _privateConstructorUsedError; | ||||
|   @override | ||||
|   @JsonKey(ignore: true) | ||||
|   _$$_DesignComponentCopyWith<_$_DesignComponent> get copyWith => | ||||
|       throw _privateConstructorUsedError; | ||||
| } | ||||
| 
 | ||||
| DesignWire _$DesignWireFromJson(Map<String, dynamic> json) { | ||||
|   return _DesignWire.fromJson(json); | ||||
| } | ||||
| 
 | ||||
| /// @nodoc | ||||
| mixin _$DesignWire { | ||||
|   String get wireId => throw _privateConstructorUsedError; | ||||
|   double get x => throw _privateConstructorUsedError; | ||||
|   double get y => throw _privateConstructorUsedError; | ||||
| 
 | ||||
|   Map<String, dynamic> toJson() => throw _privateConstructorUsedError; | ||||
|   @JsonKey(ignore: true) | ||||
|   $DesignWireCopyWith<DesignWire> get copyWith => | ||||
|       throw _privateConstructorUsedError; | ||||
| } | ||||
| 
 | ||||
| /// @nodoc | ||||
| abstract class $DesignWireCopyWith<$Res> { | ||||
|   factory $DesignWireCopyWith( | ||||
|           DesignWire value, $Res Function(DesignWire) then) = | ||||
|       _$DesignWireCopyWithImpl<$Res>; | ||||
|   $Res call({String wireId, double x, double y}); | ||||
| } | ||||
| 
 | ||||
| /// @nodoc | ||||
| class _$DesignWireCopyWithImpl<$Res> implements $DesignWireCopyWith<$Res> { | ||||
|   _$DesignWireCopyWithImpl(this._value, this._then); | ||||
| 
 | ||||
|   final DesignWire _value; | ||||
|   // ignore: unused_field | ||||
|   final $Res Function(DesignWire) _then; | ||||
| 
 | ||||
|   @override | ||||
|   $Res call({ | ||||
|     Object? wireId = freezed, | ||||
|     Object? x = freezed, | ||||
|     Object? y = freezed, | ||||
|   }) { | ||||
|     return _then(_value.copyWith( | ||||
|       wireId: wireId == freezed | ||||
|           ? _value.wireId | ||||
|           : wireId // ignore: cast_nullable_to_non_nullable | ||||
|               as String, | ||||
|       x: x == freezed | ||||
|           ? _value.x | ||||
|           : x // ignore: cast_nullable_to_non_nullable | ||||
|               as double, | ||||
|       y: y == freezed | ||||
|           ? _value.y | ||||
|           : y // ignore: cast_nullable_to_non_nullable | ||||
|               as double, | ||||
|     )); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /// @nodoc | ||||
| abstract class _$$_DesignWireCopyWith<$Res> | ||||
|     implements $DesignWireCopyWith<$Res> { | ||||
|   factory _$$_DesignWireCopyWith( | ||||
|           _$_DesignWire value, $Res Function(_$_DesignWire) then) = | ||||
|       __$$_DesignWireCopyWithImpl<$Res>; | ||||
|   @override | ||||
|   $Res call({String wireId, double x, double y}); | ||||
| } | ||||
| 
 | ||||
| /// @nodoc | ||||
| class __$$_DesignWireCopyWithImpl<$Res> extends _$DesignWireCopyWithImpl<$Res> | ||||
|     implements _$$_DesignWireCopyWith<$Res> { | ||||
|   __$$_DesignWireCopyWithImpl( | ||||
|       _$_DesignWire _value, $Res Function(_$_DesignWire) _then) | ||||
|       : super(_value, (v) => _then(v as _$_DesignWire)); | ||||
| 
 | ||||
|   @override | ||||
|   _$_DesignWire get _value => super._value as _$_DesignWire; | ||||
| 
 | ||||
|   @override | ||||
|   $Res call({ | ||||
|     Object? wireId = freezed, | ||||
|     Object? x = freezed, | ||||
|     Object? y = freezed, | ||||
|   }) { | ||||
|     return _then(_$_DesignWire( | ||||
|       wireId: wireId == freezed | ||||
|           ? _value.wireId | ||||
|           : wireId // ignore: cast_nullable_to_non_nullable | ||||
|               as String, | ||||
|       x: x == freezed | ||||
|           ? _value.x | ||||
|           : x // ignore: cast_nullable_to_non_nullable | ||||
|               as double, | ||||
|       y: y == freezed | ||||
|           ? _value.y | ||||
|           : y // ignore: cast_nullable_to_non_nullable | ||||
|               as double, | ||||
|     )); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /// @nodoc | ||||
| @JsonSerializable() | ||||
| class _$_DesignWire implements _DesignWire { | ||||
|   const _$_DesignWire({required this.wireId, required this.x, required this.y}); | ||||
| 
 | ||||
|   factory _$_DesignWire.fromJson(Map<String, dynamic> json) => | ||||
|       _$$_DesignWireFromJson(json); | ||||
| 
 | ||||
|   @override | ||||
|   final String wireId; | ||||
|   @override | ||||
|   final double x; | ||||
|   @override | ||||
|   final double y; | ||||
| 
 | ||||
|   @override | ||||
|   String toString() { | ||||
|     return 'DesignWire(wireId: $wireId, x: $x, y: $y)'; | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   bool operator ==(dynamic other) { | ||||
|     return identical(this, other) || | ||||
|         (other.runtimeType == runtimeType && | ||||
|             other is _$_DesignWire && | ||||
|             const DeepCollectionEquality().equals(other.wireId, wireId) && | ||||
|             const DeepCollectionEquality().equals(other.x, x) && | ||||
|             const DeepCollectionEquality().equals(other.y, y)); | ||||
|   } | ||||
| 
 | ||||
|   @JsonKey(ignore: true) | ||||
|   @override | ||||
|   int get hashCode => Object.hash( | ||||
|       runtimeType, | ||||
|       const DeepCollectionEquality().hash(wireId), | ||||
|       const DeepCollectionEquality().hash(x), | ||||
|       const DeepCollectionEquality().hash(y)); | ||||
| 
 | ||||
|   @JsonKey(ignore: true) | ||||
|   @override | ||||
|   _$$_DesignWireCopyWith<_$_DesignWire> get copyWith => | ||||
|       __$$_DesignWireCopyWithImpl<_$_DesignWire>(this, _$identity); | ||||
| 
 | ||||
|   @override | ||||
|   Map<String, dynamic> toJson() { | ||||
|     return _$$_DesignWireToJson(this); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| abstract class _DesignWire implements DesignWire { | ||||
|   const factory _DesignWire( | ||||
|       {required final String wireId, | ||||
|       required final double x, | ||||
|       required final double y}) = _$_DesignWire; | ||||
| 
 | ||||
|   factory _DesignWire.fromJson(Map<String, dynamic> json) = | ||||
|       _$_DesignWire.fromJson; | ||||
| 
 | ||||
|   @override | ||||
|   String get wireId => throw _privateConstructorUsedError; | ||||
|   @override | ||||
|   double get x => throw _privateConstructorUsedError; | ||||
|   @override | ||||
|   double get y => throw _privateConstructorUsedError; | ||||
|   @override | ||||
|   @JsonKey(ignore: true) | ||||
|   _$$_DesignWireCopyWith<_$_DesignWire> get copyWith => | ||||
|       throw _privateConstructorUsedError; | ||||
| } | ||||
| 
 | ||||
| DesignInput _$DesignInputFromJson(Map<String, dynamic> json) { | ||||
|   return _DesignInput.fromJson(json); | ||||
| } | ||||
| 
 | ||||
| /// @nodoc | ||||
| mixin _$DesignInput { | ||||
|   String get name => throw _privateConstructorUsedError; | ||||
|   double get x => throw _privateConstructorUsedError; | ||||
|   double get y => throw _privateConstructorUsedError; | ||||
| 
 | ||||
|   Map<String, dynamic> toJson() => throw _privateConstructorUsedError; | ||||
|   @JsonKey(ignore: true) | ||||
|   $DesignInputCopyWith<DesignInput> get copyWith => | ||||
|       throw _privateConstructorUsedError; | ||||
| } | ||||
| 
 | ||||
| /// @nodoc | ||||
| abstract class $DesignInputCopyWith<$Res> { | ||||
|   factory $DesignInputCopyWith( | ||||
|           DesignInput value, $Res Function(DesignInput) then) = | ||||
|       _$DesignInputCopyWithImpl<$Res>; | ||||
|   $Res call({String name, double x, double y}); | ||||
| } | ||||
| 
 | ||||
| /// @nodoc | ||||
| class _$DesignInputCopyWithImpl<$Res> implements $DesignInputCopyWith<$Res> { | ||||
|   _$DesignInputCopyWithImpl(this._value, this._then); | ||||
| 
 | ||||
|   final DesignInput _value; | ||||
|   // ignore: unused_field | ||||
|   final $Res Function(DesignInput) _then; | ||||
| 
 | ||||
|   @override | ||||
|   $Res call({ | ||||
|     Object? name = freezed, | ||||
|     Object? x = freezed, | ||||
|     Object? y = freezed, | ||||
|   }) { | ||||
|     return _then(_value.copyWith( | ||||
|       name: name == freezed | ||||
|           ? _value.name | ||||
|           : name // ignore: cast_nullable_to_non_nullable | ||||
|               as String, | ||||
|       x: x == freezed | ||||
|           ? _value.x | ||||
|           : x // ignore: cast_nullable_to_non_nullable | ||||
|               as double, | ||||
|       y: y == freezed | ||||
|           ? _value.y | ||||
|           : y // ignore: cast_nullable_to_non_nullable | ||||
|               as double, | ||||
|     )); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /// @nodoc | ||||
| abstract class _$$_DesignInputCopyWith<$Res> | ||||
|     implements $DesignInputCopyWith<$Res> { | ||||
|   factory _$$_DesignInputCopyWith( | ||||
|           _$_DesignInput value, $Res Function(_$_DesignInput) then) = | ||||
|       __$$_DesignInputCopyWithImpl<$Res>; | ||||
|   @override | ||||
|   $Res call({String name, double x, double y}); | ||||
| } | ||||
| 
 | ||||
| /// @nodoc | ||||
| class __$$_DesignInputCopyWithImpl<$Res> extends _$DesignInputCopyWithImpl<$Res> | ||||
|     implements _$$_DesignInputCopyWith<$Res> { | ||||
|   __$$_DesignInputCopyWithImpl( | ||||
|       _$_DesignInput _value, $Res Function(_$_DesignInput) _then) | ||||
|       : super(_value, (v) => _then(v as _$_DesignInput)); | ||||
| 
 | ||||
|   @override | ||||
|   _$_DesignInput get _value => super._value as _$_DesignInput; | ||||
| 
 | ||||
|   @override | ||||
|   $Res call({ | ||||
|     Object? name = freezed, | ||||
|     Object? x = freezed, | ||||
|     Object? y = freezed, | ||||
|   }) { | ||||
|     return _then(_$_DesignInput( | ||||
|       name: name == freezed | ||||
|           ? _value.name | ||||
|           : name // ignore: cast_nullable_to_non_nullable | ||||
|               as String, | ||||
|       x: x == freezed | ||||
|           ? _value.x | ||||
|           : x // ignore: cast_nullable_to_non_nullable | ||||
|               as double, | ||||
|       y: y == freezed | ||||
|           ? _value.y | ||||
|           : y // ignore: cast_nullable_to_non_nullable | ||||
|               as double, | ||||
|     )); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /// @nodoc | ||||
| @JsonSerializable() | ||||
| class _$_DesignInput implements _DesignInput { | ||||
|   const _$_DesignInput({required this.name, required this.x, required this.y}); | ||||
| 
 | ||||
|   factory _$_DesignInput.fromJson(Map<String, dynamic> json) => | ||||
|       _$$_DesignInputFromJson(json); | ||||
| 
 | ||||
|   @override | ||||
|   final String name; | ||||
|   @override | ||||
|   final double x; | ||||
|   @override | ||||
|   final double y; | ||||
| 
 | ||||
|   @override | ||||
|   String toString() { | ||||
|     return 'DesignInput(name: $name, x: $x, y: $y)'; | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   bool operator ==(dynamic other) { | ||||
|     return identical(this, other) || | ||||
|         (other.runtimeType == runtimeType && | ||||
|             other is _$_DesignInput && | ||||
|             const DeepCollectionEquality().equals(other.name, name) && | ||||
|             const DeepCollectionEquality().equals(other.x, x) && | ||||
|             const DeepCollectionEquality().equals(other.y, y)); | ||||
|   } | ||||
| 
 | ||||
|   @JsonKey(ignore: true) | ||||
|   @override | ||||
|   int get hashCode => Object.hash( | ||||
|       runtimeType, | ||||
|       const DeepCollectionEquality().hash(name), | ||||
|       const DeepCollectionEquality().hash(x), | ||||
|       const DeepCollectionEquality().hash(y)); | ||||
| 
 | ||||
|   @JsonKey(ignore: true) | ||||
|   @override | ||||
|   _$$_DesignInputCopyWith<_$_DesignInput> get copyWith => | ||||
|       __$$_DesignInputCopyWithImpl<_$_DesignInput>(this, _$identity); | ||||
| 
 | ||||
|   @override | ||||
|   Map<String, dynamic> toJson() { | ||||
|     return _$$_DesignInputToJson(this); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| abstract class _DesignInput implements DesignInput { | ||||
|   const factory _DesignInput( | ||||
|       {required final String name, | ||||
|       required final double x, | ||||
|       required final double y}) = _$_DesignInput; | ||||
| 
 | ||||
|   factory _DesignInput.fromJson(Map<String, dynamic> json) = | ||||
|       _$_DesignInput.fromJson; | ||||
| 
 | ||||
|   @override | ||||
|   String get name => throw _privateConstructorUsedError; | ||||
|   @override | ||||
|   double get x => throw _privateConstructorUsedError; | ||||
|   @override | ||||
|   double get y => throw _privateConstructorUsedError; | ||||
|   @override | ||||
|   @JsonKey(ignore: true) | ||||
|   _$$_DesignInputCopyWith<_$_DesignInput> get copyWith => | ||||
|       throw _privateConstructorUsedError; | ||||
| } | ||||
| 
 | ||||
| DesignOutput _$DesignOutputFromJson(Map<String, dynamic> json) { | ||||
|   return _DesignOutput.fromJson(json); | ||||
| } | ||||
| 
 | ||||
| /// @nodoc | ||||
| mixin _$DesignOutput { | ||||
|   String get name => throw _privateConstructorUsedError; | ||||
|   double get x => throw _privateConstructorUsedError; | ||||
|   double get y => throw _privateConstructorUsedError; | ||||
| 
 | ||||
|   Map<String, dynamic> toJson() => throw _privateConstructorUsedError; | ||||
|   @JsonKey(ignore: true) | ||||
|   $DesignOutputCopyWith<DesignOutput> get copyWith => | ||||
|       throw _privateConstructorUsedError; | ||||
| } | ||||
| 
 | ||||
| /// @nodoc | ||||
| abstract class $DesignOutputCopyWith<$Res> { | ||||
|   factory $DesignOutputCopyWith( | ||||
|           DesignOutput value, $Res Function(DesignOutput) then) = | ||||
|       _$DesignOutputCopyWithImpl<$Res>; | ||||
|   $Res call({String name, double x, double y}); | ||||
| } | ||||
| 
 | ||||
| /// @nodoc | ||||
| class _$DesignOutputCopyWithImpl<$Res> implements $DesignOutputCopyWith<$Res> { | ||||
|   _$DesignOutputCopyWithImpl(this._value, this._then); | ||||
| 
 | ||||
|   final DesignOutput _value; | ||||
|   // ignore: unused_field | ||||
|   final $Res Function(DesignOutput) _then; | ||||
| 
 | ||||
|   @override | ||||
|   $Res call({ | ||||
|     Object? name = freezed, | ||||
|     Object? x = freezed, | ||||
|     Object? y = freezed, | ||||
|   }) { | ||||
|     return _then(_value.copyWith( | ||||
|       name: name == freezed | ||||
|           ? _value.name | ||||
|           : name // ignore: cast_nullable_to_non_nullable | ||||
|               as String, | ||||
|       x: x == freezed | ||||
|           ? _value.x | ||||
|           : x // ignore: cast_nullable_to_non_nullable | ||||
|               as double, | ||||
|       y: y == freezed | ||||
|           ? _value.y | ||||
|           : y // ignore: cast_nullable_to_non_nullable | ||||
|               as double, | ||||
|     )); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /// @nodoc | ||||
| abstract class _$$_DesignOutputCopyWith<$Res> | ||||
|     implements $DesignOutputCopyWith<$Res> { | ||||
|   factory _$$_DesignOutputCopyWith( | ||||
|           _$_DesignOutput value, $Res Function(_$_DesignOutput) then) = | ||||
|       __$$_DesignOutputCopyWithImpl<$Res>; | ||||
|   @override | ||||
|   $Res call({String name, double x, double y}); | ||||
| } | ||||
| 
 | ||||
| /// @nodoc | ||||
| class __$$_DesignOutputCopyWithImpl<$Res> | ||||
|     extends _$DesignOutputCopyWithImpl<$Res> | ||||
|     implements _$$_DesignOutputCopyWith<$Res> { | ||||
|   __$$_DesignOutputCopyWithImpl( | ||||
|       _$_DesignOutput _value, $Res Function(_$_DesignOutput) _then) | ||||
|       : super(_value, (v) => _then(v as _$_DesignOutput)); | ||||
| 
 | ||||
|   @override | ||||
|   _$_DesignOutput get _value => super._value as _$_DesignOutput; | ||||
| 
 | ||||
|   @override | ||||
|   $Res call({ | ||||
|     Object? name = freezed, | ||||
|     Object? x = freezed, | ||||
|     Object? y = freezed, | ||||
|   }) { | ||||
|     return _then(_$_DesignOutput( | ||||
|       name: name == freezed | ||||
|           ? _value.name | ||||
|           : name // ignore: cast_nullable_to_non_nullable | ||||
|               as String, | ||||
|       x: x == freezed | ||||
|           ? _value.x | ||||
|           : x // ignore: cast_nullable_to_non_nullable | ||||
|               as double, | ||||
|       y: y == freezed | ||||
|           ? _value.y | ||||
|           : y // ignore: cast_nullable_to_non_nullable | ||||
|               as double, | ||||
|     )); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /// @nodoc | ||||
| @JsonSerializable() | ||||
| class _$_DesignOutput implements _DesignOutput { | ||||
|   const _$_DesignOutput({required this.name, required this.x, required this.y}); | ||||
| 
 | ||||
|   factory _$_DesignOutput.fromJson(Map<String, dynamic> json) => | ||||
|       _$$_DesignOutputFromJson(json); | ||||
| 
 | ||||
|   @override | ||||
|   final String name; | ||||
|   @override | ||||
|   final double x; | ||||
|   @override | ||||
|   final double y; | ||||
| 
 | ||||
|   @override | ||||
|   String toString() { | ||||
|     return 'DesignOutput(name: $name, x: $x, y: $y)'; | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   bool operator ==(dynamic other) { | ||||
|     return identical(this, other) || | ||||
|         (other.runtimeType == runtimeType && | ||||
|             other is _$_DesignOutput && | ||||
|             const DeepCollectionEquality().equals(other.name, name) && | ||||
|             const DeepCollectionEquality().equals(other.x, x) && | ||||
|             const DeepCollectionEquality().equals(other.y, y)); | ||||
|   } | ||||
| 
 | ||||
|   @JsonKey(ignore: true) | ||||
|   @override | ||||
|   int get hashCode => Object.hash( | ||||
|       runtimeType, | ||||
|       const DeepCollectionEquality().hash(name), | ||||
|       const DeepCollectionEquality().hash(x), | ||||
|       const DeepCollectionEquality().hash(y)); | ||||
| 
 | ||||
|   @JsonKey(ignore: true) | ||||
|   @override | ||||
|   _$$_DesignOutputCopyWith<_$_DesignOutput> get copyWith => | ||||
|       __$$_DesignOutputCopyWithImpl<_$_DesignOutput>(this, _$identity); | ||||
| 
 | ||||
|   @override | ||||
|   Map<String, dynamic> toJson() { | ||||
|     return _$$_DesignOutputToJson(this); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| abstract class _DesignOutput implements DesignOutput { | ||||
|   const factory _DesignOutput( | ||||
|       {required final String name, | ||||
|       required final double x, | ||||
|       required final double y}) = _$_DesignOutput; | ||||
| 
 | ||||
|   factory _DesignOutput.fromJson(Map<String, dynamic> json) = | ||||
|       _$_DesignOutput.fromJson; | ||||
| 
 | ||||
|   @override | ||||
|   String get name => throw _privateConstructorUsedError; | ||||
|   @override | ||||
|   double get x => throw _privateConstructorUsedError; | ||||
|   @override | ||||
|   double get y => throw _privateConstructorUsedError; | ||||
|   @override | ||||
|   @JsonKey(ignore: true) | ||||
|   _$$_DesignOutputCopyWith<_$_DesignOutput> get copyWith => | ||||
|       throw _privateConstructorUsedError; | ||||
| } | ||||
							
								
								
									
										85
									
								
								lib/models/design.g.dart
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								lib/models/design.g.dart
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,85 @@ | |||
| // GENERATED CODE - DO NOT MODIFY BY HAND | ||||
| 
 | ||||
| part of 'design.dart'; | ||||
| 
 | ||||
| // ************************************************************************** | ||||
| // JsonSerializableGenerator | ||||
| // ************************************************************************** | ||||
| 
 | ||||
| _$_Design _$$_DesignFromJson(Map<String, dynamic> json) => _$_Design( | ||||
|       components: (json['components'] as List<dynamic>) | ||||
|           .map((e) => DesignComponent.fromJson(e as Map<String, dynamic>)) | ||||
|           .toList(), | ||||
|       wires: (json['wires'] as List<dynamic>) | ||||
|           .map((e) => DesignWire.fromJson(e as Map<String, dynamic>)) | ||||
|           .toList(), | ||||
|       inputs: (json['inputs'] as List<dynamic>) | ||||
|           .map((e) => DesignInput.fromJson(e as Map<String, dynamic>)) | ||||
|           .toList(), | ||||
|       outputs: (json['outputs'] as List<dynamic>) | ||||
|           .map((e) => DesignOutput.fromJson(e as Map<String, dynamic>)) | ||||
|           .toList(), | ||||
|     ); | ||||
| 
 | ||||
| Map<String, dynamic> _$$_DesignToJson(_$_Design instance) => <String, dynamic>{ | ||||
|       'components': instance.components, | ||||
|       'wires': instance.wires, | ||||
|       'inputs': instance.inputs, | ||||
|       'outputs': instance.outputs, | ||||
|     }; | ||||
| 
 | ||||
| _$_DesignComponent _$$_DesignComponentFromJson(Map<String, dynamic> json) => | ||||
|     _$_DesignComponent( | ||||
|       instanceId: json['instanceId'] as String, | ||||
|       x: (json['x'] as num).toDouble(), | ||||
|       y: (json['y'] as num).toDouble(), | ||||
|     ); | ||||
| 
 | ||||
| Map<String, dynamic> _$$_DesignComponentToJson(_$_DesignComponent instance) => | ||||
|     <String, dynamic>{ | ||||
|       'instanceId': instance.instanceId, | ||||
|       'x': instance.x, | ||||
|       'y': instance.y, | ||||
|     }; | ||||
| 
 | ||||
| _$_DesignWire _$$_DesignWireFromJson(Map<String, dynamic> json) => | ||||
|     _$_DesignWire( | ||||
|       wireId: json['wireId'] as String, | ||||
|       x: (json['x'] as num).toDouble(), | ||||
|       y: (json['y'] as num).toDouble(), | ||||
|     ); | ||||
| 
 | ||||
| Map<String, dynamic> _$$_DesignWireToJson(_$_DesignWire instance) => | ||||
|     <String, dynamic>{ | ||||
|       'wireId': instance.wireId, | ||||
|       'x': instance.x, | ||||
|       'y': instance.y, | ||||
|     }; | ||||
| 
 | ||||
| _$_DesignInput _$$_DesignInputFromJson(Map<String, dynamic> json) => | ||||
|     _$_DesignInput( | ||||
|       name: json['name'] as String, | ||||
|       x: (json['x'] as num).toDouble(), | ||||
|       y: (json['y'] as num).toDouble(), | ||||
|     ); | ||||
| 
 | ||||
| Map<String, dynamic> _$$_DesignInputToJson(_$_DesignInput instance) => | ||||
|     <String, dynamic>{ | ||||
|       'name': instance.name, | ||||
|       'x': instance.x, | ||||
|       'y': instance.y, | ||||
|     }; | ||||
| 
 | ||||
| _$_DesignOutput _$$_DesignOutputFromJson(Map<String, dynamic> json) => | ||||
|     _$_DesignOutput( | ||||
|       name: json['name'] as String, | ||||
|       x: (json['x'] as num).toDouble(), | ||||
|       y: (json['y'] as num).toDouble(), | ||||
|     ); | ||||
| 
 | ||||
| Map<String, dynamic> _$$_DesignOutputToJson(_$_DesignOutput instance) => | ||||
|     <String, dynamic>{ | ||||
|       'name': instance.name, | ||||
|       'x': instance.x, | ||||
|       'y': instance.y, | ||||
|     }; | ||||
|  | @ -1,12 +1,19 @@ | |||
| import 'dart:math'; | ||||
| 
 | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | ||||
| import 'package:logic_circuits_simulator/components/visual_component.dart'; | ||||
| import 'package:logic_circuits_simulator/models.dart'; | ||||
| import 'package:logic_circuits_simulator/pages_arguments/design_component.dart'; | ||||
| import 'package:logic_circuits_simulator/state/component.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/provider_hook.dart'; | ||||
| import 'package:logic_circuits_simulator/utils/stack_canvas_controller_hook.dart'; | ||||
| import 'package:stack_canvas/stack_canvas.dart'; | ||||
| 
 | ||||
| Key canvasKey = GlobalKey(); | ||||
| 
 | ||||
| class DesignComponentPage extends HookWidget { | ||||
|   final ComponentEntry component; | ||||
| 
 | ||||
|  | @ -19,51 +26,378 @@ class DesignComponentPage extends HookWidget { | |||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     final componentState = useProvider<ComponentState>(); | ||||
|     final canvasController = useStackCanvasController(); | ||||
|     final widgets = useState(<CanvasObject<Widget>>[]); | ||||
|     final canvasController = useStackCanvasController( | ||||
|       offsetReference: Reference.Center, | ||||
|     ); | ||||
| 
 | ||||
|     // Simulation vars | ||||
|     final isSimulating = useState(false); | ||||
|     final simulatePartially = useState(false); | ||||
|      | ||||
|     useListenable(componentState.partialVisualSimulation!); | ||||
| 
 | ||||
|     final movingWidgetUpdater = useState<void Function(double dx, double dy)?>(null); | ||||
|     final movingWidget = useState<dynamic>(null); | ||||
|     final widgets = useMemoized(() => [ | ||||
|       for (final subcomponent in componentState.designDraft.components) | ||||
|         CanvasObject( | ||||
|           dx: subcomponent.x,  | ||||
|           dy: subcomponent.y,  | ||||
|           width: VisualComponent.getNeededWidth( | ||||
|             context, | ||||
|             componentState | ||||
|                 .getMetaByInstance(subcomponent.instanceId) | ||||
|                 .item2 | ||||
|                 .inputs, | ||||
|             componentState | ||||
|                 .getMetaByInstance(subcomponent.instanceId) | ||||
|                 .item2 | ||||
|                 .outputs, | ||||
|             Theme.of(context).textTheme.bodyMedium, | ||||
|           ),  | ||||
|           height: max( | ||||
|             componentState.getMetaByInstance(subcomponent.instanceId).item2.inputs.length,  | ||||
|             componentState.getMetaByInstance(subcomponent.instanceId).item2.outputs.length,  | ||||
|           ) * 30 + 10,  | ||||
|           child: Listener( | ||||
|             behavior: HitTestBehavior.translucent, | ||||
|             onPointerDown: (event) { | ||||
|               final debouncer = FutureCallDebounce<List<double>>( | ||||
|                 futureCall: (xyList) { | ||||
|                   final dx = xyList[0]; | ||||
|                   final dy = xyList[1]; | ||||
|                   return componentState.updateDesign(componentState.designDraft.copyWith( | ||||
|                     components: componentState.designDraft.components.map( | ||||
|                       (e) => e.instanceId == subcomponent.instanceId ? e.copyWith( | ||||
|                         x: subcomponent.x + dx, | ||||
|                         y: subcomponent.y + dy, | ||||
|                       ) : e, | ||||
|                     ).toList(), | ||||
|                   ), commit: false); | ||||
|                 },  | ||||
|                 combiner: (oldParams, newParams) { | ||||
|                   return oldParams.zipWith([newParams], (deltas) => deltas.fold<double>(0.0, (prev, elem) => prev + elem)).toList(); | ||||
|                 }, | ||||
|               ); | ||||
|               movingWidgetUpdater.value = (dx, dy) { | ||||
|                 debouncer.call([dx, dy]); | ||||
|               }; | ||||
|               movingWidget.value = subcomponent; | ||||
|             }, | ||||
|             onPointerUp: (event) { | ||||
|               componentState.updateDesign(componentState.designDraft); | ||||
|               movingWidgetUpdater.value = null; | ||||
|               movingWidget.value = null; | ||||
|             }, | ||||
|             child: MouseRegion( | ||||
|               cursor: movingWidget.value == subcomponent ? SystemMouseCursors.move : MouseCursor.defer, | ||||
|               child: VisualComponent( | ||||
|                 name: componentState.getMetaByInstance(subcomponent.instanceId).item2.componentName,  | ||||
|                 inputs: componentState.getMetaByInstance(subcomponent.instanceId).item2.inputs,  | ||||
|                 outputs: componentState.getMetaByInstance(subcomponent.instanceId).item2.outputs, | ||||
|                 inputColors: isSimulating.value ? { | ||||
|                   for (final input in componentState.getMetaByInstance(subcomponent.instanceId).item2.inputs) | ||||
|                     input: componentState.partialVisualSimulation!.inputsValues['${subcomponent.instanceId}/$input'] == true ? Colors.green  | ||||
|                       : componentState.partialVisualSimulation!.inputsValues['${subcomponent.instanceId}/$input'] == false ? Colors.red  | ||||
|                       : Colors.black, | ||||
|                 } : null, | ||||
|                 outputColors: isSimulating.value ? { | ||||
|                   for (final output in componentState.getMetaByInstance(subcomponent.instanceId).item2.outputs) | ||||
|                     output: componentState.partialVisualSimulation!.outputsValues['${subcomponent.instanceId}/$output'] == true ? Colors.green  | ||||
|                       : componentState.partialVisualSimulation!.outputsValues['${subcomponent.instanceId}/$output'] == false ? Colors.red  | ||||
|                       : Colors.black, | ||||
|                 } : null, | ||||
|               ), | ||||
|             ), | ||||
|           ), | ||||
|         ), | ||||
|       for (final input in componentState.designDraft.inputs) | ||||
|         CanvasObject( | ||||
|           dx: input.x,  | ||||
|           dy: input.y,  | ||||
|           width: IOComponent.getNeededWidth(context, input.name, textStyle: Theme.of(context).textTheme.bodyMedium),  | ||||
|           height: 40,  | ||||
|           child: Listener( | ||||
|             behavior: HitTestBehavior.translucent, | ||||
|             onPointerDown: (event) { | ||||
|               final debouncer = FutureCallDebounce<List<double>>( | ||||
|                 futureCall: (xyList) { | ||||
|                   final dx = xyList[0]; | ||||
|                   final dy = xyList[1]; | ||||
|                   return componentState.updateDesign(componentState.designDraft.copyWith( | ||||
|                     inputs: componentState.designDraft.inputs.map( | ||||
|                       (e) => e.name == input.name ? e.copyWith( | ||||
|                         x: input.x + dx, | ||||
|                         y: input.y + dy, | ||||
|                       ) : e, | ||||
|                     ).toList(), | ||||
|                   ), commit: false); | ||||
|                 },  | ||||
|                 combiner: (oldParams, newParams) { | ||||
|                   return oldParams.zipWith([newParams], (deltas) => deltas.fold<double>(0.0, (prev, elem) => prev + elem)).toList(); | ||||
|                 }, | ||||
|               ); | ||||
|               movingWidgetUpdater.value = (dx, dy) { | ||||
|                 debouncer.call([dx, dy]); | ||||
|               }; | ||||
|             }, | ||||
|             onPointerUp: (event) { | ||||
|               componentState.updateDesign(componentState.designDraft); | ||||
|               movingWidgetUpdater.value = null; | ||||
|             }, | ||||
|             child: IOComponent( | ||||
|               input: true, | ||||
|               name: input.name, | ||||
|               width: IOComponent.getNeededWidth(context, input.name, textStyle: Theme.of(context).textTheme.bodyMedium), | ||||
|               color: isSimulating.value  | ||||
|                 ? (componentState.partialVisualSimulation!.outputsValues['self/${input.name}']!  | ||||
|                   ? Colors.green | ||||
|                   : Colors.red)  | ||||
|                 : null, | ||||
|               onTap: isSimulating.value ? () { | ||||
|                 componentState.partialVisualSimulation!.toggleInput(input.name); | ||||
|               } : null, | ||||
|             ), | ||||
|           ), | ||||
|         ), | ||||
|       for (final output in componentState.designDraft.outputs) | ||||
|         CanvasObject( | ||||
|           dx: output.x,  | ||||
|           dy: output.y,  | ||||
|           width: IOComponent.getNeededWidth(context, output.name, textStyle: Theme.of(context).textTheme.bodyMedium),  | ||||
|           height: 40,  | ||||
|           child: Listener( | ||||
|             behavior: HitTestBehavior.translucent, | ||||
|             onPointerDown: (event) { | ||||
|               final debouncer = FutureCallDebounce<List<double>>( | ||||
|                 futureCall: (xyList) { | ||||
|                   final dx = xyList[0]; | ||||
|                   final dy = xyList[1]; | ||||
|                   return componentState.updateDesign(componentState.designDraft.copyWith( | ||||
|                     outputs: componentState.designDraft.outputs.map( | ||||
|                       (e) => e.name == output.name ? e.copyWith( | ||||
|                         x: output.x + dx, | ||||
|                         y: output.y + dy, | ||||
|                       ) : e, | ||||
|                     ).toList(), | ||||
|                   ), commit: false); | ||||
|                 },  | ||||
|                 combiner: (oldParams, newParams) { | ||||
|                   return oldParams.zipWith([newParams], (deltas) => deltas.fold<double>(0.0, (prev, elem) => prev + elem)).toList(); | ||||
|                 }, | ||||
|               ); | ||||
|               movingWidgetUpdater.value = (dx, dy) { | ||||
|                 debouncer.call([dx, dy]); | ||||
|               }; | ||||
|             }, | ||||
|             onPointerUp: (event) { | ||||
|               componentState.updateDesign(componentState.designDraft); | ||||
|               movingWidgetUpdater.value = null; | ||||
|             }, | ||||
|             child: IOComponent( | ||||
|               input: false, | ||||
|               name: output.name, | ||||
|               width: IOComponent.getNeededWidth(context, output.name, textStyle: Theme.of(context).textTheme.bodyMedium), | ||||
|               color: isSimulating.value  | ||||
|                 ? (componentState.partialVisualSimulation!.inputsValues['self/${output.name}'] == true ? Colors.green | ||||
|                   : componentState.partialVisualSimulation!.inputsValues['self/${output.name}'] == false ? Colors.red | ||||
|                   : null)  | ||||
|                 : null, | ||||
|             ), | ||||
|           ), | ||||
|         ), | ||||
|       for (final wire in componentState.wiring.wires) | ||||
|         (() { | ||||
|           const ioCircleDiameter = 20; | ||||
| 
 | ||||
|           Offset from, to; | ||||
|           if (wire.output.split('/')[0] == 'self') { | ||||
|             // It's a component input | ||||
|              | ||||
|             // Find input | ||||
|             final inputName = wire.output.split('/')[1]; | ||||
|             final design = componentState.designDraft.inputs.where((i) => i.name == inputName).first; | ||||
| 
 | ||||
|             from = Offset( | ||||
|               // Take into account widget length | ||||
|               design.x + | ||||
|                   IOComponent.getNeededWidth( | ||||
|                     context, | ||||
|                     inputName, | ||||
|                     textStyle: Theme.of(context).textTheme.bodyMedium, | ||||
|                   ), | ||||
|               design.y + ioCircleDiameter + 1, | ||||
|             ); | ||||
|           } | ||||
|           else { | ||||
|             // It's a subcomponent output | ||||
| 
 | ||||
|             // Find subcomponent | ||||
|             final split = wire.output.split('/'); | ||||
|             final subcomponentId = split[0]; | ||||
|             final outputName = split[1]; | ||||
|             final design = componentState.designDraft.components.where((c) => c.instanceId == subcomponentId).first; | ||||
|             final subcomponent = componentState.getMetaByInstance(subcomponentId).item2; | ||||
| 
 | ||||
|             from = Offset( | ||||
|               // Take into account widget length | ||||
|               design.x + | ||||
|                 VisualComponent.getNeededWidth( | ||||
|                   context, | ||||
|                   subcomponent.inputs, | ||||
|                   subcomponent.outputs, | ||||
|                   Theme.of(context).textTheme.bodyMedium, | ||||
|                 ), | ||||
|               design.y + | ||||
|                 VisualComponent.getHeightOfIO( | ||||
|                   context, | ||||
|                   subcomponent.outputs, | ||||
|                   subcomponent.outputs.indexOf(outputName), | ||||
|                   Theme.of(context).textTheme.bodyMedium, | ||||
|                 ), | ||||
|             ); | ||||
|           } | ||||
| 
 | ||||
|           if (wire.input.split('/')[0] == 'self') { | ||||
|             // It's a component output | ||||
|              | ||||
|             // Find output | ||||
|             final outputName = wire.input.split('/')[1]; | ||||
|             final design = componentState.designDraft.outputs.where((o) => o.name == outputName).first; | ||||
| 
 | ||||
|             to = Offset( | ||||
|               design.x, | ||||
|               design.y + ioCircleDiameter + 1, | ||||
|             ); | ||||
|           } | ||||
|           else { | ||||
|             // It's a subcomponent input | ||||
| 
 | ||||
|             // Find subcomponent | ||||
|             final split = wire.input.split('/'); | ||||
|             final subcomponentId = split[0]; | ||||
|             final inputName = split[1]; | ||||
|             final design = componentState.designDraft.components.where((c) => c.instanceId == subcomponentId).first; | ||||
|             final subcomponent = componentState.getMetaByInstance(subcomponentId).item2; | ||||
| 
 | ||||
|             to = Offset( | ||||
|               // Take into account widget length | ||||
|               design.x, | ||||
|               design.y + | ||||
|                 VisualComponent.getHeightOfIO( | ||||
|                   context, | ||||
|                   subcomponent.inputs, | ||||
|                   subcomponent.inputs.indexOf(inputName), | ||||
|                   Theme.of(context).textTheme.bodyMedium, | ||||
|                 ), | ||||
|             ); | ||||
|           } | ||||
| 
 | ||||
|           var wireColor = Colors.black; | ||||
|           if (isSimulating.value) { | ||||
|             final wireValue = componentState.partialVisualSimulation!.outputsValues[wire.output]; | ||||
|             if (wireValue == true) { | ||||
|               wireColor = Colors.green; | ||||
|             } | ||||
|             else if (wireValue == false) { | ||||
|               wireColor = Colors.red; | ||||
|             } | ||||
|           } | ||||
| 
 | ||||
|           return CanvasObject( | ||||
|             dx: min(from.dx, to.dx), | ||||
|             dy: min(from.dy, to.dy), | ||||
|             width: (to - from).dx.abs(), | ||||
|             height: (to - from).dy.abs(), | ||||
|             child: IgnorePointer( | ||||
|               child: WireWidget( | ||||
|                 from: from,  | ||||
|                 to: to, | ||||
|                 color: wireColor, | ||||
|               ), | ||||
|             ), | ||||
|           ); | ||||
|         })(), | ||||
|     ], [componentState.designDraft, isSimulating.value, componentState.partialVisualSimulation!.outputsValues]); | ||||
|     useEffect(() { | ||||
|       canvasController.addCanvasObjects(widgets.value); | ||||
|       final wList = widgets; | ||||
|       canvasController.addCanvasObjects(wList); | ||||
| 
 | ||||
|       return () { | ||||
|         // Cleanup | ||||
|         canvasController.clearCanvas(); | ||||
|         for (final obj in wList) { | ||||
|           canvasController.removeCanvasObject(obj); | ||||
|         } | ||||
|       }; | ||||
|     }, [widgets]); | ||||
|     useEffect(() { | ||||
|       if (isSimulating.value && !simulatePartially.value && componentState.partialVisualSimulation!.nextToSimulate.isNotEmpty) { | ||||
|         componentState.partialVisualSimulation!.nextStep(); | ||||
|       } | ||||
|       return null; | ||||
|     }, [componentState.partialVisualSimulation!.outputsValues.entries.map((e) => '${e.key}:${e.value}').join(';'), simulatePartially.value, isSimulating.value]); | ||||
| 
 | ||||
|     return Scaffold( | ||||
|       appBar: AppBar( | ||||
|         centerTitle: true, | ||||
|         title: Text('Design - ${component.componentName}'), | ||||
|         title: Text('${isSimulating.value ? 'Simulation' : 'Design'} - ${component.componentName}'), | ||||
|         actions: [ | ||||
|           IconButton( | ||||
|             icon: Icon(isSimulating.value ? Icons.stop : Icons.start), | ||||
|             tooltip: isSimulating.value ? 'Stop Simulation' : 'Start Simulation', | ||||
|             onPressed: () { | ||||
|               isSimulating.value = !isSimulating.value; | ||||
|             }, | ||||
|           ), | ||||
|         ], | ||||
|       ), | ||||
|       body: OrientationBuilder( | ||||
|         builder: (context, orientation) { | ||||
|           if (orientation == Orientation.portrait) { | ||||
|             return Column( | ||||
|               mainAxisSize: MainAxisSize.max, | ||||
|               children: [ | ||||
|                 Expanded( | ||||
|                   child: StackCanvas( | ||||
|                     canvasController: canvasController, | ||||
|                     backgroundColor: Theme.of(context).colorScheme.background, | ||||
|                   ), | ||||
|                 ), | ||||
|               ], | ||||
|             ); | ||||
|       body: GestureDetector( | ||||
|         onPanUpdate: (update) { | ||||
|           final hw = movingWidgetUpdater.value; | ||||
|           if (hw == null || isSimulating.value) { | ||||
|             canvasController.offset = canvasController.offset.translate(update.delta.dx, update.delta.dy); | ||||
|           } | ||||
|           else { | ||||
|             return Row( | ||||
|               mainAxisSize: MainAxisSize.max, | ||||
|               children: [ | ||||
|                 Expanded( | ||||
|                   child: StackCanvas( | ||||
|                     canvasController: canvasController, | ||||
|                   ), | ||||
|                 ), | ||||
|               ], | ||||
|             ); | ||||
|             hw(update.delta.dx, update.delta.dy); | ||||
|           } | ||||
|         } | ||||
|         }, | ||||
|         child: OrientationBuilder( | ||||
|           builder: (context, orientation) { | ||||
|             if (orientation == Orientation.portrait) { | ||||
|               return Column( | ||||
|                 mainAxisSize: MainAxisSize.max, | ||||
|                 children: [ | ||||
|                   Expanded( | ||||
|                     child: StackCanvas( | ||||
|                       key: canvasKey, | ||||
|                       canvasController: canvasController, | ||||
|                       animationDuration: const Duration(milliseconds: 50), | ||||
|                       // disposeController: false, | ||||
|                       backgroundColor: Theme.of(context).colorScheme.background, | ||||
|                     ), | ||||
|                   ), | ||||
|                 ], | ||||
|               ); | ||||
|             } | ||||
|             else { | ||||
|               return Row( | ||||
|                 mainAxisSize: MainAxisSize.max, | ||||
|                 children: [ | ||||
|                   Expanded( | ||||
|                     child: StackCanvas( | ||||
|                       key: canvasKey, | ||||
|                       canvasController: canvasController, | ||||
|                       animationDuration: const Duration(milliseconds: 50), | ||||
|                       // disposeController: false, | ||||
|                       backgroundColor: Theme.of(context).colorScheme.background, | ||||
|                     ), | ||||
|                   ), | ||||
|                 ], | ||||
|               ); | ||||
|             } | ||||
|           } | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| } | ||||
|  | @ -12,13 +12,41 @@ class ComponentState extends ChangeNotifier { | |||
|   ProjectEntry? _currentProject; | ||||
|   ComponentEntry? _currentComponent; | ||||
|   Wiring _wiring = const Wiring(instances: [], wires: []); | ||||
|   Wiring? _wiringDraft; | ||||
|   Design _design = const Design(components: [], wires: [], inputs: [], outputs: []); | ||||
|   Design? _designDraft; | ||||
|   SimulatedComponent? _simulatedComponent; | ||||
|   PartialVisualSimulation? _partialVisualSimulation; | ||||
| 
 | ||||
|   final Map<String, Tuple2<ProjectEntry, ComponentEntry>> _dependenciesMap = {}; | ||||
| 
 | ||||
|   ProjectEntry? get currentProject => _currentProject; | ||||
|   ComponentEntry? get currentComponent => _currentComponent; | ||||
|   Wiring get wiring => _wiring; | ||||
|   Wiring get wiringDraft => _wiringDraft ?? _wiring; | ||||
|   Design get design => _design; | ||||
|   Design get designDraft => _designDraft ?? _design; | ||||
|   PartialVisualSimulation? get partialVisualSimulation => _partialVisualSimulation; | ||||
| 
 | ||||
|   Future<SimulatedComponent> _onRequiredDependency(String depId) async { | ||||
|     final t = _dependenciesMap[depId]!; | ||||
|     final proj = t.item1; | ||||
|     final comp = t.item2; | ||||
|     final state = comp.visualDesigned ? ComponentState() : null; | ||||
|     if (state != null) { | ||||
|       await state.setCurrentComponent( | ||||
|         project: proj,  | ||||
|         component: comp,  | ||||
|         onDependencyNeeded: (projId, compId) async => _dependenciesMap['$projId/$compId'], | ||||
|       ); | ||||
|     } | ||||
|     return SimulatedComponent( | ||||
|       project: proj,  | ||||
|       component: comp,  | ||||
|       onRequiredDependency: _onRequiredDependency, | ||||
|       state: state, | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   Future<Directory> _getComponentDir() async { | ||||
|     if (_currentProject == null) { | ||||
|  | @ -40,6 +68,11 @@ class ComponentState extends ChangeNotifier { | |||
|     return result; | ||||
|   } | ||||
| 
 | ||||
|   Future<File> _getDesignFile() async { | ||||
|     final result = File(path.join((await _getComponentDir()).path, 'design.json')); | ||||
|     return result; | ||||
|   } | ||||
| 
 | ||||
|   Future<void> _loadComponentFiles() async { | ||||
|     final wiringFile = await _getWiringFile(); | ||||
|     if (!await wiringFile.exists()) { | ||||
|  | @ -49,6 +82,17 @@ class ComponentState extends ChangeNotifier { | |||
|     else { | ||||
|       _wiring = Wiring.fromJson(jsonDecode(await wiringFile.readAsString())); | ||||
|     } | ||||
|     _wiringDraft = null; | ||||
| 
 | ||||
|     final designFile = await _getDesignFile(); | ||||
|     if (!await designFile.exists()) { | ||||
|       _design = const Design(components: [], wires: [], inputs: [], outputs: []); | ||||
|       await designFile.writeAsString(jsonEncode(_design)); | ||||
|     } | ||||
|     else { | ||||
|       _design = Design.fromJson(jsonDecode(await designFile.readAsString())); | ||||
|     } | ||||
|     _designDraft = null; | ||||
|   } | ||||
| 
 | ||||
|   Future<void> setCurrentComponent({ | ||||
|  | @ -78,7 +122,18 @@ class ComponentState extends ChangeNotifier { | |||
|       throw DependenciesNotSatisfiedException(dependencies: unsatisfiedDependencies); | ||||
|     } | ||||
| 
 | ||||
|     return _loadComponentFiles().then((_) => notifyListeners()); | ||||
|     await _loadComponentFiles(); | ||||
| 
 | ||||
|     if (component.visualDesigned) { | ||||
|       _partialVisualSimulation = await PartialVisualSimulation.init( | ||||
|         project: project, | ||||
|         component: component, | ||||
|         state: this, | ||||
|         onRequiredDependency: _onRequiredDependency, | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     notifyListeners(); | ||||
|   } | ||||
| 
 | ||||
|   void noComponent() { | ||||
|  | @ -86,41 +141,63 @@ class ComponentState extends ChangeNotifier { | |||
|     _currentProject = null; | ||||
|     _currentComponent = null; | ||||
|     _wiring = const Wiring(instances: [], wires: []); | ||||
|     _design = const Design(components: [], wires: [], inputs: [], outputs: []); | ||||
|     _wiringDraft = _designDraft = null; | ||||
|     _simulatedComponent = null; | ||||
|     _partialVisualSimulation = null; | ||||
| 
 | ||||
|     notifyListeners(); | ||||
|   } | ||||
| 
 | ||||
|   Future<Map<String, bool>> simulate(Map<String, bool> inputs) async { | ||||
|     Future<SimulatedComponent> onRequiredDependency(String depId) async { | ||||
|       final t = _dependenciesMap[depId]!; | ||||
|       final proj = t.item1; | ||||
|       final comp = t.item2; | ||||
|       final state = comp.visualDesigned ? ComponentState() : null; | ||||
|       if (state != null) { | ||||
|         await state.setCurrentComponent( | ||||
|           project: proj,  | ||||
|           component: comp,  | ||||
|           onDependencyNeeded: (projId, compId) async => _dependenciesMap['$projId/$compId'], | ||||
|         ); | ||||
|   Tuple2<ProjectEntry, ComponentEntry> getMetaByInstance(String instanceId) { | ||||
|     for (final instance in wiring.instances) { | ||||
|       if (instance.instanceId == instanceId) { | ||||
|         return _dependenciesMap[instance.componentId]!; | ||||
|       } | ||||
|       return SimulatedComponent( | ||||
|         project: proj,  | ||||
|         component: comp,  | ||||
|         onRequiredDependency: onRequiredDependency, | ||||
|         state: state, | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     throw Exception('Instance $instanceId not found in the dependencies map'); | ||||
|   } | ||||
| 
 | ||||
|   Future<Map<String, bool>> simulate(Map<String, bool> inputs) async { | ||||
| 
 | ||||
|     _simulatedComponent ??= SimulatedComponent( | ||||
|       project: _currentProject!,  | ||||
|       component: _currentComponent!,  | ||||
|       onRequiredDependency: onRequiredDependency, | ||||
|       onRequiredDependency: _onRequiredDependency, | ||||
|       state: this, | ||||
|     ); | ||||
| 
 | ||||
|     return _simulatedComponent!.simulate(inputs); | ||||
|   } | ||||
| 
 | ||||
|   Future<Design> updateDesign(Design newDesign, {bool commit = true}) async { | ||||
|     if (commit) { | ||||
|       _design = newDesign; | ||||
|       _designDraft = null; | ||||
|       final designFile = await _getDesignFile(); | ||||
|       await designFile.writeAsString(jsonEncode(newDesign)); | ||||
|     } | ||||
|     else { | ||||
|       _designDraft = newDesign; | ||||
|     } | ||||
|     notifyListeners(); | ||||
|     return designDraft; | ||||
|   } | ||||
| 
 | ||||
|   Future<Wiring> updateWiring(Wiring newWiring, {bool commit = true}) async { | ||||
|     if (commit) { | ||||
|       _wiring = newWiring; | ||||
|       _wiringDraft = null; | ||||
|       final wiringFile = await _getWiringFile(); | ||||
|       await wiringFile.writeAsString(jsonEncode(newWiring)); | ||||
|     } | ||||
|     else { | ||||
|       _wiringDraft = newWiring; | ||||
|     } | ||||
|     notifyListeners(); | ||||
|     return wiringDraft; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| class DependenciesNotSatisfiedException with Exception { | ||||
|  |  | |||
							
								
								
									
										22
									
								
								lib/utils/future_call_debounce.dart
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								lib/utils/future_call_debounce.dart
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,22 @@ | |||
| class FutureCallDebounce<TParams extends Object> { | ||||
|   TParams? _params; | ||||
|   Future? _awaited; | ||||
|   final Future Function(TParams) futureCall; | ||||
|   final TParams Function(TParams oldParams, TParams newParams) combiner; | ||||
| 
 | ||||
|   static TParams _defaultCombiner<TParams>(TParams _, TParams newParams) => newParams; | ||||
| 
 | ||||
|   FutureCallDebounce({required this.futureCall, required this.combiner}); | ||||
|   FutureCallDebounce.replaceCombiner({required this.futureCall}) : combiner = _defaultCombiner; | ||||
| 
 | ||||
|   void call(TParams newParams) { | ||||
|     if (_params != null) { | ||||
|       _params = combiner(_params!, newParams); | ||||
|     } | ||||
|     else { | ||||
|       _params = newParams; | ||||
|     } | ||||
| 
 | ||||
|     _awaited ??= futureCall(_params!).then((value) => _awaited = null); | ||||
|   } | ||||
| } | ||||
|  | @ -1,3 +1,5 @@ | |||
| import 'package:collection/collection.dart'; | ||||
| import 'package:flutter/foundation.dart'; | ||||
| import 'package:logic_circuits_simulator/models.dart'; | ||||
| import 'package:logic_circuits_simulator/state/component.dart'; | ||||
| import 'package:logic_circuits_simulator/utils/iterable_extension.dart'; | ||||
|  | @ -10,19 +12,18 @@ class SimulatedComponent { | |||
|   final Future<SimulatedComponent> Function(String depId) onRequiredDependency; | ||||
|   final _instances = <String, SimulatedComponent>{}; | ||||
| 
 | ||||
|   SimulatedComponent({ | ||||
|     required this.project,  | ||||
|     required this.component,  | ||||
|     required this.onRequiredDependency, | ||||
|     this.state | ||||
|   }); | ||||
|   SimulatedComponent( | ||||
|       {required this.project, | ||||
|       required this.component, | ||||
|       required this.onRequiredDependency, | ||||
|       this.state}); | ||||
| 
 | ||||
|   Future<SimulatedComponent> _getInstance(String instanceId, String? depId) async { | ||||
|   Future<SimulatedComponent> _getInstance( | ||||
|       String instanceId, String? depId) async { | ||||
|     if (!_instances.containsKey(instanceId)) { | ||||
|       if (depId != null) { | ||||
|         _instances[instanceId] = await onRequiredDependency(depId); | ||||
|       } | ||||
|       else { | ||||
|       } else { | ||||
|         throw Exception('Attempted to get instance of unknown component'); | ||||
|       } | ||||
|     } | ||||
|  | @ -31,39 +32,32 @@ class SimulatedComponent { | |||
| 
 | ||||
|   Future<Map<String, bool>> simulate(Map<String, bool> inputs) async { | ||||
|     final input = int.parse( | ||||
|       component.inputs.map((input) => inputs[input]! ? '1' : '0').join(),  | ||||
|       component.inputs.map((input) => inputs[input]! ? '1' : '0').join(), | ||||
|       radix: 2, | ||||
|     ); | ||||
|     if (component.truthTable != null) { | ||||
|       final output = component.truthTable![input]; | ||||
|       return {  | ||||
|         for (final it in component.outputs.indexedMap( | ||||
|           (index, outName) => [outName, output[index]] | ||||
|         ))  | ||||
|         it[0] : it[1] == '1' | ||||
|       return { | ||||
|         for (final it in component.outputs | ||||
|             .indexedMap((index, outName) => [outName, output[index]])) | ||||
|           it[0]: it[1] == '1' | ||||
|       }; | ||||
|     } | ||||
|     else if (component.logicExpression != null) { | ||||
|     } else if (component.logicExpression != null) { | ||||
|       // Somehow? | ||||
|       // A truth table should be automatically generated for every logic expression component. | ||||
|       // Might as well handle cases where that isn't done anyway. | ||||
|       final results = component.outputs.zipWith( | ||||
|         [component.logicExpression!],  | ||||
|         [component.logicExpression!], | ||||
|         (zips) { | ||||
|           final output = zips[0]; | ||||
|           final le = LogicExpression.parse(zips[1]); | ||||
|           return [output, le.evaluate(inputs)]; | ||||
|         }, | ||||
|       ); | ||||
|       return { | ||||
|         for (final it in results) | ||||
|         it[0] as String : it[1] as bool | ||||
|       }; | ||||
|     } | ||||
|     else if (state == null) { | ||||
|       return {for (final it in results) it[0] as String: it[1] as bool}; | ||||
|     } else if (state == null) { | ||||
|       throw Exception('Cannot simulate designed component without its state'); | ||||
|     } | ||||
|     else { | ||||
|     } else { | ||||
|       // Create instances | ||||
|       final wiring = state!.wiring; | ||||
|       for (final instance in wiring.instances) { | ||||
|  | @ -75,8 +69,7 @@ class SimulatedComponent { | |||
|         ...component.outputs.map((output) => 'self/$output'), | ||||
|       ]; | ||||
|       final knownSources = { | ||||
|         for (final entry in inputs.entries) | ||||
|         'self/${entry.key}': entry.value | ||||
|         for (final entry in inputs.entries) 'self/${entry.key}': entry.value | ||||
|       }; | ||||
|       final knownSinks = <String, bool>{}; | ||||
| 
 | ||||
|  | @ -86,37 +79,40 @@ class SimulatedComponent { | |||
|         if (knownSinks.containsKey(sink)) { | ||||
|           // Requirement satisfied | ||||
|           continue; | ||||
|         } | ||||
|         else { | ||||
|         } else { | ||||
|           // Find wire that provides sink | ||||
|           final wire = wiring.wires.where((wire) => wire.input == sink).first; | ||||
|           if (knownSources.containsKey(wire.output)) { | ||||
|             // If we know the output provided through the wire,  | ||||
|             // If we know the output provided through the wire, | ||||
|             // we know the input provided to the sink | ||||
|             knownSinks[sink] = knownSources[wire.output]!; | ||||
|           } | ||||
|           else { | ||||
|           } else { | ||||
|             // The instance providing the source for the wire has not been simulated. | ||||
|             // See if all its sinks are known: | ||||
|             final instanceId = wire.output.split('/')[0]; | ||||
|             final instance = await _getInstance(instanceId, null); | ||||
|             final depSinks = instance.component.inputs.map((input) => '$instanceId/$input').toList(); | ||||
|             if (depSinks.map((depSink) => !knownSinks.containsKey(depSink)).where((cond) => cond).isEmpty) { | ||||
|             final depSinks = instance.component.inputs | ||||
|                 .map((input) => '$instanceId/$input') | ||||
|                 .toList(); | ||||
|             if (depSinks | ||||
|                 .map((depSink) => !knownSinks.containsKey(depSink)) | ||||
|                 .where((cond) => cond) | ||||
|                 .isEmpty) { | ||||
|               // If so, then simulate | ||||
|               final results = await instance.simulate({ | ||||
|                 for (final depSink in depSinks) | ||||
|                 depSink.split('/')[1] : knownSinks[depSink]! | ||||
|                   depSink.split('/')[1]: knownSinks[depSink]! | ||||
|               }); | ||||
|               knownSources.addAll({ | ||||
|                 for (final result in results.entries) | ||||
|                 '$instanceId/${result.key}' : result.value | ||||
|                   '$instanceId/${result.key}': result.value | ||||
|               }); | ||||
|               // And resolve needed sink | ||||
|               knownSinks[sink] = knownSources[wire.output]!; | ||||
|             } | ||||
|             else { | ||||
|             } else { | ||||
|               // Otherwise, require the sinks and reschedule the current one | ||||
|               requiredSinks.addAll(depSinks.where((depSink) => !knownSinks.containsKey(depSink))); | ||||
|               requiredSinks.addAll(depSinks | ||||
|                   .where((depSink) => !knownSinks.containsKey(depSink))); | ||||
|               requiredSinks.add(sink); | ||||
|             } | ||||
|           } | ||||
|  | @ -125,8 +121,162 @@ class SimulatedComponent { | |||
| 
 | ||||
|       return { | ||||
|         for (final output in component.outputs) | ||||
|         output : knownSinks['self/$output']! | ||||
|           output: knownSinks['self/$output']! | ||||
|       }; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| } | ||||
| 
 | ||||
| class PartialVisualSimulation with ChangeNotifier { | ||||
|   final Map<String, bool?> _outputsValues = {}; | ||||
|   final List<String> nextToSimulate = []; | ||||
|   final List<String> _alreadySimulated = []; | ||||
| 
 | ||||
|   UnmodifiableMapView<String, bool?> get outputsValues => UnmodifiableMapView(_outputsValues); | ||||
|   UnmodifiableMapView<String, bool?> get inputsValues => UnmodifiableMapView({ | ||||
|     for (final entry in outputsValues.entries) | ||||
|       if (entry.value != null) | ||||
|         for (final wire in state.wiringDraft.wires.where((w) => w.output == entry.key)) | ||||
|           wire.input: entry.value | ||||
|   }); | ||||
| 
 | ||||
|   final ProjectEntry project; | ||||
|   final ComponentEntry component; | ||||
|   final ComponentState state; | ||||
|   final Future<SimulatedComponent> Function(String depId) onRequiredDependency; | ||||
|   final _instances = <String, SimulatedComponent>{}; | ||||
| 
 | ||||
|   PartialVisualSimulation._( | ||||
|       {required this.project, | ||||
|       required this.component, | ||||
|       required this.state, | ||||
|       required this.onRequiredDependency}); | ||||
| 
 | ||||
|   Future<SimulatedComponent> _getInstance( | ||||
|       String instanceId, String? depId) async { | ||||
|     if (!_instances.containsKey(instanceId)) { | ||||
|       if (depId != null) { | ||||
|         _instances[instanceId] = await onRequiredDependency(depId); | ||||
|       } else { | ||||
|         throw Exception('Attempted to get instance of unknown component'); | ||||
|       } | ||||
|     } | ||||
|     return _instances[instanceId]!; | ||||
|   } | ||||
| 
 | ||||
|   static Future<PartialVisualSimulation> init({ | ||||
|     required ProjectEntry project, | ||||
|     required ComponentEntry component, | ||||
|     required ComponentState state, | ||||
|     required Future<SimulatedComponent> Function(String depId) onRequiredDependency, | ||||
|     Map<String, bool>? inputs, | ||||
|   }) async { | ||||
|     final sim = PartialVisualSimulation._(project: project, component: component, state: state, onRequiredDependency: onRequiredDependency); | ||||
| 
 | ||||
|     // Create instances | ||||
|     final wiring = state.wiring; | ||||
|     for (final instance in wiring.instances) { | ||||
|       await sim._getInstance(instance.instanceId, instance.componentId); | ||||
|     } | ||||
| 
 | ||||
|     // Populate inputs | ||||
|     inputs ??= {}; | ||||
|     for (final input in component.inputs) { | ||||
|       if (!inputs.containsKey(input)) { | ||||
|         inputs[input] = false; | ||||
|       } | ||||
|     } | ||||
|     await sim.provideInputs(inputs); | ||||
| 
 | ||||
|     return sim; | ||||
|   } | ||||
| 
 | ||||
|   Future<void> toggleInput(String inputName) { | ||||
|     final inputValue = _outputsValues['self/$inputName']!; | ||||
|     return modifyInput(inputName, !inputValue); | ||||
|   } | ||||
| 
 | ||||
|   Future<void> modifyInput(String inputName, bool newValue) { | ||||
|     _outputsValues['self/$inputName'] = newValue; | ||||
|     for (final key in _outputsValues.keys.toList()) { | ||||
|       if (!key.startsWith('self/')) { | ||||
|         _outputsValues.remove(key); | ||||
|       } | ||||
|     } | ||||
|     _alreadySimulated.clear(); | ||||
|     return reset(); | ||||
|   } | ||||
| 
 | ||||
|   Future<void> provideInputs(Map<String, bool> inputs) { | ||||
|     _alreadySimulated.clear(); | ||||
|     _outputsValues.clear(); | ||||
|     for (final entry in inputs.entries) { | ||||
|       _outputsValues['self/${entry.key}'] = entry.value; | ||||
|     } | ||||
|     return reset(); | ||||
|   } | ||||
| 
 | ||||
|   Future<void> reset() async { | ||||
|     nextToSimulate.clear(); | ||||
| 
 | ||||
|     final neededToBeNext = <String, List<String>>{}; | ||||
| 
 | ||||
|     for (final wire in state.wiringDraft.wires) { | ||||
|       if (_outputsValues.containsKey(wire.output)) { | ||||
|         final subcomponentId = wire.input.split('/')[0]; | ||||
| 
 | ||||
|         // Ignore component outputs, they require no computation | ||||
|         if (subcomponentId == 'self') { | ||||
|           continue; | ||||
|         } | ||||
| 
 | ||||
|         // Skip already simulated subcomponents | ||||
|         if (_alreadySimulated.contains(subcomponentId)) { | ||||
|           continue; | ||||
|         } | ||||
| 
 | ||||
|         if (neededToBeNext.containsKey(subcomponentId)) { | ||||
|           neededToBeNext[subcomponentId]!.remove(wire.input.split('/')[1]); | ||||
|           if (neededToBeNext[subcomponentId]!.isEmpty) { | ||||
|             nextToSimulate.add(subcomponentId); | ||||
|           } | ||||
|         } | ||||
|         else { | ||||
|           neededToBeNext[subcomponentId] =  | ||||
|             (await _getInstance(subcomponentId, null)) | ||||
|             .component | ||||
|             .inputs | ||||
|             .whereNot((e) => e == wire.input.split('/')[1]) | ||||
|             .toList(); | ||||
|           if (neededToBeNext[subcomponentId]!.isEmpty) { | ||||
|             nextToSimulate.add(subcomponentId); | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|      | ||||
|     notifyListeners(); | ||||
|   } | ||||
| 
 | ||||
|   Future<void> nextStep() async { | ||||
|     if (nextToSimulate.isEmpty) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     final currentlySimulating = nextToSimulate.toList(); | ||||
| 
 | ||||
|     for (final subcomponentId in currentlySimulating) { | ||||
|       final sim = await _getInstance(subcomponentId, null); | ||||
|       final outputs = await sim.simulate({ | ||||
|         for (final input in sim.component.inputs) | ||||
|           input: inputsValues['$subcomponentId/$input']! | ||||
|       }); | ||||
|       for (final entry in outputs.entries) { | ||||
|         _outputsValues['$subcomponentId/${entry.key}'] = entry.value; | ||||
|       } | ||||
|       _alreadySimulated.add(subcomponentId); | ||||
|     } | ||||
| 
 | ||||
|     return reset(); | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -633,9 +633,11 @@ packages: | |||
|   stack_canvas: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: stack_canvas | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|       path: "." | ||||
|       ref: HEAD | ||||
|       resolved-ref: "83e1032940e9424572c60ddad397f38320ee9cd4" | ||||
|       url: "https://github.com/dancojocaru2000/stack_canvas.git" | ||||
|     source: git | ||||
|     version: "0.2.0+2" | ||||
|   stack_trace: | ||||
|     dependency: transitive | ||||
|  |  | |||
|  | @ -38,7 +38,8 @@ dependencies: | |||
|   archive: ^3.3.0 | ||||
|   file_picker: ^4.6.1 | ||||
|   share_plus: ^4.0.8 | ||||
|   stack_canvas: ^0.2.0+2 | ||||
|   stack_canvas: | ||||
|     git: https://github.com/dancojocaru2000/stack_canvas.git | ||||
|   tuple: ^2.0.0 | ||||
| 
 | ||||
| dev_dependencies: | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue