mirror of
https://github.com/dancojocaru2000/logic-circuits-simulator.git
synced 2025-06-19 10:32:28 +03:00
Compare commits
No commits in common. "a86a5e6aec20a14d888c8d08fc1265d3eac2f640" and "8abd6b3ca8d620267384fd419a4e1aff9b287c93" have entirely different histories.
a86a5e6aec
...
8abd6b3ca8
4 changed files with 116 additions and 569 deletions
|
@ -11,28 +11,10 @@ class VisualComponent extends HookWidget {
|
||||||
final Map<String, Color?> inputColors;
|
final Map<String, Color?> inputColors;
|
||||||
final Map<String, Color?> outputColors;
|
final Map<String, Color?> outputColors;
|
||||||
final bool isNextToSimulate;
|
final bool isNextToSimulate;
|
||||||
final void Function(String)? onInputHovered;
|
|
||||||
final void Function(String)? onInputUnhovered;
|
|
||||||
final void Function(String)? onOutputHovered;
|
|
||||||
final void Function(String)? onOutputUnhovered;
|
|
||||||
|
|
||||||
VisualComponent({
|
VisualComponent({super.key, required this.name, required this.inputs, required this.outputs, Map<String, Color?>? inputColors, Map<String, Color?>? outputColors, this.isNextToSimulate = false})
|
||||||
super.key,
|
|
||||||
required this.name,
|
|
||||||
required this.inputs,
|
|
||||||
required this.outputs,
|
|
||||||
Map<String, Color?>? inputColors,
|
|
||||||
Map<String, Color?>? outputColors,
|
|
||||||
this.isNextToSimulate = false,
|
|
||||||
this.onInputHovered,
|
|
||||||
this.onInputUnhovered,
|
|
||||||
this.onOutputHovered,
|
|
||||||
this.onOutputUnhovered,
|
|
||||||
})
|
|
||||||
: inputColors = inputColors ?? {}
|
: inputColors = inputColors ?? {}
|
||||||
, outputColors = outputColors ?? {}
|
, outputColors = outputColors ?? {};
|
||||||
, assert((onInputHovered == null) == (onInputUnhovered == null))
|
|
||||||
, assert((onOutputHovered == null) == (onOutputUnhovered == null));
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -58,20 +40,12 @@ class VisualComponent extends HookWidget {
|
||||||
|
|
||||||
final hovered = useState(false);
|
final hovered = useState(false);
|
||||||
|
|
||||||
useEffect(() {
|
|
||||||
if (onInputHovered != null || onOutputHovered != null) {
|
|
||||||
hovered.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}, [onInputHovered, onOutputHovered]);
|
|
||||||
|
|
||||||
final inputsWidth = inputs.map((input) => IOLabel.getNeededWidth(context, input)).fold<double>(0, (previousValue, element) => max(previousValue, element));
|
final inputsWidth = inputs.map((input) => IOLabel.getNeededWidth(context, input)).fold<double>(0, (previousValue, element) => max(previousValue, element));
|
||||||
final outputsWidth = outputs.map((output) => IOLabel.getNeededWidth(context, output)).fold<double>(0, (previousValue, element) => max(previousValue, element));
|
final outputsWidth = outputs.map((output) => IOLabel.getNeededWidth(context, output)).fold<double>(0, (previousValue, element) => max(previousValue, element));
|
||||||
|
|
||||||
return MouseRegion(
|
return MouseRegion(
|
||||||
onEnter: onInputHovered == null && onOutputHovered == null ? (event) => hovered.value = true : null,
|
onEnter: (event) => hovered.value = true,
|
||||||
onExit: onInputUnhovered == null && onOutputUnhovered== null ? (event) => hovered.value = false : null,
|
onExit: (event) => hovered.value = false,
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Column(
|
Column(
|
||||||
|
@ -85,9 +59,6 @@ class VisualComponent extends HookWidget {
|
||||||
? Theme.of(context).colorScheme.primary
|
? Theme.of(context).colorScheme.primary
|
||||||
: inputColors[input] ?? Colors.black,
|
: inputColors[input] ?? Colors.black,
|
||||||
width: inputsWidth,
|
width: inputsWidth,
|
||||||
onHovered: onInputHovered == null ? null : () => onInputHovered!(input),
|
|
||||||
onUnhovered: onInputUnhovered == null ? null : () => onInputUnhovered!(input),
|
|
||||||
flashing: onInputHovered != null,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -121,9 +92,6 @@ class VisualComponent extends HookWidget {
|
||||||
? Theme.of(context).colorScheme.primary
|
? Theme.of(context).colorScheme.primary
|
||||||
: outputColors[output] ?? Colors.black,
|
: outputColors[output] ?? Colors.black,
|
||||||
width: outputsWidth,
|
width: outputsWidth,
|
||||||
onHovered: onOutputHovered == null ? null : () => onOutputHovered!(output),
|
|
||||||
onUnhovered: onOutputUnhovered == null ? null : () => onOutputUnhovered!(output),
|
|
||||||
flashing: onOutputHovered != null,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -175,71 +143,22 @@ class IOComponent extends HookWidget {
|
||||||
final double circleDiameter;
|
final double circleDiameter;
|
||||||
final Color? color;
|
final Color? color;
|
||||||
final void Function()? onTap;
|
final void Function()? onTap;
|
||||||
final void Function()? onHovered;
|
|
||||||
final void Function()? onUnhovered;
|
|
||||||
final bool flashing;
|
|
||||||
|
|
||||||
const IOComponent({
|
const IOComponent({super.key, required this.name, required this.input, this.width = 100, this.circleDiameter = 20, this.color, this.onTap});
|
||||||
super.key,
|
|
||||||
required this.name,
|
|
||||||
required this.input,
|
|
||||||
this.width = 100,
|
|
||||||
this.circleDiameter = 20,
|
|
||||||
this.color,
|
|
||||||
this.onTap,
|
|
||||||
this.onHovered,
|
|
||||||
this.onUnhovered,
|
|
||||||
this.flashing = false,
|
|
||||||
}) : assert((onHovered == null) == (onUnhovered == null));
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final flashingAnimation = useAnimationController(
|
|
||||||
duration: const Duration(milliseconds: 500),
|
|
||||||
initialValue: 0.0,
|
|
||||||
lowerBound: 0.0,
|
|
||||||
upperBound: 1.0,
|
|
||||||
);
|
|
||||||
useEffect(() {
|
|
||||||
if (flashing) {
|
|
||||||
flashingAnimation.repeat(
|
|
||||||
reverse: true,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
flashingAnimation.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}, [flashing]);
|
|
||||||
final flashingAnimProgress = useAnimation(flashingAnimation);
|
|
||||||
|
|
||||||
final hovered = useState(false);
|
final hovered = useState(false);
|
||||||
|
|
||||||
useEffect(() {
|
|
||||||
if (onHovered != null) {
|
|
||||||
hovered.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}, [onHovered]);
|
|
||||||
|
|
||||||
return MouseRegion(
|
return MouseRegion(
|
||||||
onEnter: (event) => onHovered != null ? onHovered!() : hovered.value = true,
|
onEnter: (event) => hovered.value = true,
|
||||||
onExit: (event) => onUnhovered != null ? onUnhovered!() : hovered.value = false,
|
onExit: (event) => hovered.value = false,
|
||||||
hitTestBehavior: HitTestBehavior.translucent,
|
hitTestBehavior: HitTestBehavior.translucent,
|
||||||
opaque: false,
|
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
behavior: HitTestBehavior.translucent,
|
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
child: Builder(
|
child: Builder(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
final lineColor = hovered.value ? Theme.of(context).colorScheme.primary : color ?? Colors.black;
|
final lineColor = hovered.value ? Theme.of(context).colorScheme.primary : color ?? Colors.black;
|
||||||
final animLineColor = Color.lerp(
|
|
||||||
lineColor,
|
|
||||||
Colors.blue,
|
|
||||||
flashingAnimProgress,
|
|
||||||
)!;
|
|
||||||
|
|
||||||
return Row(
|
return Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
@ -248,7 +167,7 @@ class IOComponent extends HookWidget {
|
||||||
width: circleDiameter,
|
width: circleDiameter,
|
||||||
height: circleDiameter,
|
height: circleDiameter,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border.all(color: animLineColor),
|
border: Border.all(color: lineColor),
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
color: color,
|
color: color,
|
||||||
),
|
),
|
||||||
|
@ -258,7 +177,7 @@ class IOComponent extends HookWidget {
|
||||||
child: IOLabel(
|
child: IOLabel(
|
||||||
label: name,
|
label: name,
|
||||||
input: !input,
|
input: !input,
|
||||||
lineColor: animLineColor,
|
lineColor: lineColor,
|
||||||
width: width - circleDiameter,
|
width: width - circleDiameter,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -266,7 +185,7 @@ class IOComponent extends HookWidget {
|
||||||
width: circleDiameter,
|
width: circleDiameter,
|
||||||
height: circleDiameter,
|
height: circleDiameter,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border.all(color: animLineColor),
|
border: Border.all(color: lineColor),
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
color: color,
|
color: color,
|
||||||
),
|
),
|
||||||
|
@ -284,78 +203,35 @@ class IOComponent extends HookWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class IOLabel extends HookWidget {
|
class IOLabel extends StatelessWidget {
|
||||||
final bool input;
|
final bool input;
|
||||||
final String label;
|
final String label;
|
||||||
final Color lineColor;
|
final Color lineColor;
|
||||||
final double width;
|
final double width;
|
||||||
final void Function()? onHovered;
|
|
||||||
final void Function()? onUnhovered;
|
|
||||||
final bool flashing;
|
|
||||||
|
|
||||||
const IOLabel({super.key, required this.lineColor, required this.label, required this.input, this.width = 80, this.onHovered, this.onUnhovered, this.flashing = false,})
|
const IOLabel({super.key, required this.lineColor, required this.label, required this.input, this.width = 80});
|
||||||
: assert((onHovered == null) == (onUnhovered == null));
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final flashingAnimation = useAnimationController(
|
return Container(
|
||||||
duration: const Duration(milliseconds: 500),
|
width: width,
|
||||||
initialValue: 0.0,
|
height: 20,
|
||||||
lowerBound: 0.0,
|
decoration: BoxDecoration(
|
||||||
upperBound: 1.0,
|
border: Border(
|
||||||
);
|
bottom: BorderSide(color: lineColor),
|
||||||
useEffect(() {
|
|
||||||
if (flashing) {
|
|
||||||
flashingAnimation.repeat(
|
|
||||||
reverse: true,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
flashingAnimation.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}, [flashing]);
|
|
||||||
final flashingAnimProgress = useAnimation(flashingAnimation);
|
|
||||||
|
|
||||||
final hovered = useState(false);
|
|
||||||
|
|
||||||
return MouseRegion(
|
|
||||||
hitTestBehavior: onHovered != null ? HitTestBehavior.translucent : HitTestBehavior.deferToChild,
|
|
||||||
onEnter: onHovered == null ? null : (_) {
|
|
||||||
hovered.value = true;
|
|
||||||
onHovered!();
|
|
||||||
},
|
|
||||||
onExit: onUnhovered == null ? null : (_) {
|
|
||||||
hovered.value = false;
|
|
||||||
onUnhovered!();
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
width: width,
|
|
||||||
height: 20,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
border: Border(
|
|
||||||
bottom: BorderSide(
|
|
||||||
color: Color.lerp(
|
|
||||||
lineColor,
|
|
||||||
Colors.blue,
|
|
||||||
flashingAnimProgress,
|
|
||||||
)!,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
child: Align(
|
),
|
||||||
alignment: input ? Alignment.bottomRight : Alignment.bottomLeft,
|
child: Align(
|
||||||
child: Padding(
|
alignment: input ? Alignment.bottomRight : Alignment.bottomLeft,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
child: Padding(
|
||||||
child: Text(
|
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||||
label,
|
child: Text(
|
||||||
style: const TextStyle(
|
label,
|
||||||
inherit: true,
|
style: const TextStyle(
|
||||||
fontFeatures: [
|
inherit: true,
|
||||||
FontFeature.tabularFigures(),
|
fontFeatures: [
|
||||||
],
|
FontFeature.tabularFigures(),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -6,17 +6,13 @@ import 'package:logic_circuits_simulator/components/visual_component.dart';
|
||||||
import 'package:logic_circuits_simulator/models.dart';
|
import 'package:logic_circuits_simulator/models.dart';
|
||||||
import 'package:logic_circuits_simulator/pages_arguments/design_component.dart';
|
import 'package:logic_circuits_simulator/pages_arguments/design_component.dart';
|
||||||
import 'package:logic_circuits_simulator/state/component.dart';
|
import 'package:logic_circuits_simulator/state/component.dart';
|
||||||
import 'package:logic_circuits_simulator/state/project.dart';
|
|
||||||
import 'package:logic_circuits_simulator/state/projects.dart';
|
|
||||||
import 'package:logic_circuits_simulator/utils/future_call_debounce.dart';
|
import 'package:logic_circuits_simulator/utils/future_call_debounce.dart';
|
||||||
import 'package:logic_circuits_simulator/utils/iterable_extension.dart';
|
import 'package:logic_circuits_simulator/utils/iterable_extension.dart';
|
||||||
import 'package:logic_circuits_simulator/utils/provider_hook.dart';
|
import 'package:logic_circuits_simulator/utils/provider_hook.dart';
|
||||||
import 'package:logic_circuits_simulator/utils/stack_canvas_controller_hook.dart';
|
import 'package:logic_circuits_simulator/utils/stack_canvas_controller_hook.dart';
|
||||||
import 'package:stack_canvas/stack_canvas.dart';
|
import 'package:stack_canvas/stack_canvas.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
|
||||||
|
|
||||||
Key canvasKey = GlobalKey();
|
Key canvasKey = GlobalKey();
|
||||||
Key pickerKey = GlobalKey();
|
|
||||||
|
|
||||||
class DesignComponentPage extends HookWidget {
|
class DesignComponentPage extends HookWidget {
|
||||||
final ComponentEntry component;
|
final ComponentEntry component;
|
||||||
|
@ -42,12 +38,6 @@ class DesignComponentPage extends HookWidget {
|
||||||
|
|
||||||
final movingWidgetUpdater = useState<void Function(double dx, double dy)?>(null);
|
final movingWidgetUpdater = useState<void Function(double dx, double dy)?>(null);
|
||||||
final movingWidget = useState<dynamic>(null);
|
final movingWidget = useState<dynamic>(null);
|
||||||
final deleteOnDrop = useState<bool>(false);
|
|
||||||
final designSelection = useState<String?>(null);
|
|
||||||
final wireToDelete = useState<String?>(null);
|
|
||||||
final sourceToConnect = useState<String?>(null);
|
|
||||||
final hoveredIO = useState<String?>(null);
|
|
||||||
|
|
||||||
final widgets = useMemoized(() => [
|
final widgets = useMemoized(() => [
|
||||||
for (final subcomponent in componentState.designDraft.components)
|
for (final subcomponent in componentState.designDraft.components)
|
||||||
CanvasObject(
|
CanvasObject(
|
||||||
|
@ -100,8 +90,6 @@ class DesignComponentPage extends HookWidget {
|
||||||
movingWidget.value = null;
|
movingWidget.value = null;
|
||||||
},
|
},
|
||||||
child: MouseRegion(
|
child: MouseRegion(
|
||||||
opaque: false,
|
|
||||||
hitTestBehavior: HitTestBehavior.translucent,
|
|
||||||
cursor: movingWidget.value == subcomponent ? SystemMouseCursors.move : MouseCursor.defer,
|
cursor: movingWidget.value == subcomponent ? SystemMouseCursors.move : MouseCursor.defer,
|
||||||
child: VisualComponent(
|
child: VisualComponent(
|
||||||
name: componentState.getMetaByInstance(subcomponent.instanceId).item2.componentName,
|
name: componentState.getMetaByInstance(subcomponent.instanceId).item2.componentName,
|
||||||
|
@ -120,18 +108,6 @@ class DesignComponentPage extends HookWidget {
|
||||||
: Colors.black,
|
: Colors.black,
|
||||||
} : null,
|
} : null,
|
||||||
isNextToSimulate: isSimulating.value && componentState.partialVisualSimulation!.nextToSimulate.contains(subcomponent.instanceId),
|
isNextToSimulate: isSimulating.value && componentState.partialVisualSimulation!.nextToSimulate.contains(subcomponent.instanceId),
|
||||||
onInputHovered: designSelection.value == 'wiring' && sourceToConnect.value != null ? (input) {
|
|
||||||
hoveredIO.value = '${subcomponent.instanceId}/$input';
|
|
||||||
} : null,
|
|
||||||
onInputUnhovered: designSelection.value == 'wiring' && sourceToConnect.value != null ? (input) {
|
|
||||||
hoveredIO.value = null;
|
|
||||||
} : null,
|
|
||||||
onOutputHovered: designSelection.value == 'wiring' && sourceToConnect.value == null ? (output) {
|
|
||||||
hoveredIO.value = '${subcomponent.instanceId}/$output';
|
|
||||||
} : null,
|
|
||||||
onOutputUnhovered: designSelection.value == 'wiring' && sourceToConnect.value == null ? (output) {
|
|
||||||
hoveredIO.value = null;
|
|
||||||
} : null,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -165,12 +141,10 @@ class DesignComponentPage extends HookWidget {
|
||||||
movingWidgetUpdater.value = (dx, dy) {
|
movingWidgetUpdater.value = (dx, dy) {
|
||||||
debouncer.call([dx, dy]);
|
debouncer.call([dx, dy]);
|
||||||
};
|
};
|
||||||
movingWidget.value = input;
|
|
||||||
},
|
},
|
||||||
onPointerUp: (event) {
|
onPointerUp: (event) {
|
||||||
componentState.updateDesign(componentState.designDraft);
|
componentState.updateDesign(componentState.designDraft);
|
||||||
movingWidgetUpdater.value = null;
|
movingWidgetUpdater.value = null;
|
||||||
movingWidget.value = null;
|
|
||||||
},
|
},
|
||||||
child: IOComponent(
|
child: IOComponent(
|
||||||
input: true,
|
input: true,
|
||||||
|
@ -184,13 +158,6 @@ class DesignComponentPage extends HookWidget {
|
||||||
onTap: isSimulating.value ? () {
|
onTap: isSimulating.value ? () {
|
||||||
componentState.partialVisualSimulation!.toggleInput(input.name);
|
componentState.partialVisualSimulation!.toggleInput(input.name);
|
||||||
} : null,
|
} : null,
|
||||||
onHovered: designSelection.value == 'wiring' && sourceToConnect.value == null ? () {
|
|
||||||
hoveredIO.value = 'self/${input.name}';
|
|
||||||
} : null,
|
|
||||||
onUnhovered: designSelection.value == 'wiring' && sourceToConnect.value == null ? () {
|
|
||||||
hoveredIO.value = null;
|
|
||||||
} : null,
|
|
||||||
flashing: designSelection.value == 'wiring' && sourceToConnect.value == null,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -223,12 +190,10 @@ class DesignComponentPage extends HookWidget {
|
||||||
movingWidgetUpdater.value = (dx, dy) {
|
movingWidgetUpdater.value = (dx, dy) {
|
||||||
debouncer.call([dx, dy]);
|
debouncer.call([dx, dy]);
|
||||||
};
|
};
|
||||||
movingWidget.value = output;
|
|
||||||
},
|
},
|
||||||
onPointerUp: (event) {
|
onPointerUp: (event) {
|
||||||
componentState.updateDesign(componentState.designDraft);
|
componentState.updateDesign(componentState.designDraft);
|
||||||
movingWidgetUpdater.value = null;
|
movingWidgetUpdater.value = null;
|
||||||
movingWidget.value = null;
|
|
||||||
},
|
},
|
||||||
child: IOComponent(
|
child: IOComponent(
|
||||||
input: false,
|
input: false,
|
||||||
|
@ -239,13 +204,6 @@ class DesignComponentPage extends HookWidget {
|
||||||
: componentState.partialVisualSimulation!.inputsValues['self/${output.name}'] == false ? Colors.red
|
: componentState.partialVisualSimulation!.inputsValues['self/${output.name}'] == false ? Colors.red
|
||||||
: null)
|
: null)
|
||||||
: null,
|
: null,
|
||||||
onHovered: designSelection.value == 'wiring' && sourceToConnect.value != null ? () {
|
|
||||||
hoveredIO.value = 'self/${output.name}';
|
|
||||||
} : null,
|
|
||||||
onUnhovered: designSelection.value == 'wiring' && sourceToConnect.value != null ? () {
|
|
||||||
hoveredIO.value = null;
|
|
||||||
} : null,
|
|
||||||
flashing: designSelection.value == 'wiring' && sourceToConnect.value != null,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -352,46 +310,16 @@ class DesignComponentPage extends HookWidget {
|
||||||
dy: min(from.dy, to.dy),
|
dy: min(from.dy, to.dy),
|
||||||
width: (to - from).dx.abs(),
|
width: (to - from).dx.abs(),
|
||||||
height: (to - from).dy.abs(),
|
height: (to - from).dy.abs(),
|
||||||
child: MouseRegion(
|
child: IgnorePointer(
|
||||||
hitTestBehavior: HitTestBehavior.translucent,
|
child: WireWidget(
|
||||||
opaque: false,
|
from: from,
|
||||||
onEnter: (_) {
|
to: to,
|
||||||
if (designSelection.value == 'wiring') {
|
color: wireColor,
|
||||||
wireToDelete.value = wire.wireId;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onExit: (_) {
|
|
||||||
wireToDelete.value = null;
|
|
||||||
},
|
|
||||||
child: GestureDetector(
|
|
||||||
behavior: HitTestBehavior.translucent,
|
|
||||||
onTap: () async {
|
|
||||||
if (designSelection.value == 'wiring') {
|
|
||||||
if (wireToDelete.value != wire.wireId) {
|
|
||||||
wireToDelete.value = wire.wireId;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Delete the wire
|
|
||||||
await componentState.updateDesign(componentState.designDraft.copyWith(
|
|
||||||
wires: componentState.designDraft.wires.where((w) => w.wireId != wireToDelete.value).toList(),
|
|
||||||
));
|
|
||||||
await componentState.updateWiring(componentState.wiringDraft.copyWith(
|
|
||||||
wires: componentState.wiringDraft.wires.where((w) => w.wireId != wireToDelete.value).toList(),
|
|
||||||
));
|
|
||||||
wireToDelete.value = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: WireWidget(
|
|
||||||
from: from,
|
|
||||||
to: to,
|
|
||||||
color: wireToDelete.value == wire.wireId ? Colors.red : wireColor,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
})(),
|
})(),
|
||||||
], [componentState.designDraft, isSimulating.value, componentState.partialVisualSimulation!.outputsValues, designSelection.value, sourceToConnect.value]);
|
], [componentState.designDraft, isSimulating.value, componentState.partialVisualSimulation!.outputsValues]);
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
final wList = widgets;
|
final wList = widgets;
|
||||||
canvasController.addCanvasObjects(wList);
|
canvasController.addCanvasObjects(wList);
|
||||||
|
@ -416,173 +344,95 @@ class DesignComponentPage extends HookWidget {
|
||||||
title: Text('${isSimulating.value ? 'Simulation' : 'Design'} - ${component.componentName}'),
|
title: Text('${isSimulating.value ? 'Simulation' : 'Design'} - ${component.componentName}'),
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(isSimulating.value ? Icons.stop : Icons.play_arrow),
|
icon: Icon(isSimulating.value ? Icons.stop : Icons.start),
|
||||||
tooltip: isSimulating.value ? 'Stop Simulation' : 'Start Simulation',
|
tooltip: isSimulating.value ? 'Stop Simulation' : 'Start Simulation',
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
isSimulating.value = !isSimulating.value;
|
isSimulating.value = !isSimulating.value;
|
||||||
designSelection.value = null;
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: OrientationBuilder(
|
body: GestureDetector(
|
||||||
builder: (context, orientation) {
|
onPanUpdate: (update) {
|
||||||
final stackCanvas = GestureDetector(
|
final hw = movingWidgetUpdater.value;
|
||||||
behavior: HitTestBehavior.translucent,
|
if (hw == null || isSimulating.value) {
|
||||||
onPanUpdate: (update) {
|
canvasController.offset = canvasController.offset.translate(update.delta.dx, update.delta.dy);
|
||||||
final hw = movingWidgetUpdater.value;
|
|
||||||
if (hw == null || isSimulating.value) {
|
|
||||||
canvasController.offset = canvasController.offset.translate(update.delta.dx, update.delta.dy);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
hw(update.delta.dx, update.delta.dy);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onTap: () {
|
|
||||||
if (designSelection.value == 'wiring') {
|
|
||||||
// Handle wire creation
|
|
||||||
if (hoveredIO.value == null) {
|
|
||||||
// If clicking on something not hovered, ignore
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else if (sourceToConnect.value == null) {
|
|
||||||
sourceToConnect.value = hoveredIO.value;
|
|
||||||
hoveredIO.value = null;
|
|
||||||
} else {
|
|
||||||
// Create wire only if sink is not already connected
|
|
||||||
if (componentState.wiringDraft.wires.where((w) => w.input == hoveredIO.value).isNotEmpty) {
|
|
||||||
// Sink already connected
|
|
||||||
final sinkType = hoveredIO.value!.startsWith('self/') ? 'component output' : 'subcomponent input';
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
|
||||||
content: Text('Wire already connected to that $sinkType.'),
|
|
||||||
));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
componentState.updateWiring(componentState.wiringDraft.copyWith(
|
|
||||||
wires: componentState.wiringDraft.wires + [
|
|
||||||
WiringWire(
|
|
||||||
wireId: const Uuid().v4(),
|
|
||||||
output: sourceToConnect.value!,
|
|
||||||
input: hoveredIO.value!,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
));
|
|
||||||
sourceToConnect.value = null;
|
|
||||||
hoveredIO.value = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Stack(
|
|
||||||
children: [
|
|
||||||
StackCanvas(
|
|
||||||
key: canvasKey,
|
|
||||||
canvasController: canvasController,
|
|
||||||
animationDuration: const Duration(milliseconds: 50),
|
|
||||||
// disposeController: false,
|
|
||||||
backgroundColor: Theme.of(context).colorScheme.background,
|
|
||||||
),
|
|
||||||
if (!isSimulating.value)
|
|
||||||
Positioned(
|
|
||||||
bottom: 0,
|
|
||||||
right: 0,
|
|
||||||
child: MouseRegion(
|
|
||||||
hitTestBehavior: HitTestBehavior.translucent,
|
|
||||||
opaque: false,
|
|
||||||
onEnter: (_) {
|
|
||||||
deleteOnDrop.value = true;
|
|
||||||
},
|
|
||||||
onExit: (_) {
|
|
||||||
deleteOnDrop.value = false;
|
|
||||||
},
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16.0),
|
|
||||||
child: Icon(
|
|
||||||
Icons.delete,
|
|
||||||
color: movingWidget.value != null && deleteOnDrop.value ? Colors.red : null,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
final debuggingButtons = DebuggingButtons(
|
|
||||||
partialSimulation: simulatePartially.value,
|
|
||||||
onPartialSimulationToggle: () {
|
|
||||||
simulatePartially.value = !simulatePartially.value;
|
|
||||||
},
|
|
||||||
onReset: simulatePartially.value ? () {
|
|
||||||
componentState.partialVisualSimulation!.restart();
|
|
||||||
} : null,
|
|
||||||
onNextStep: simulatePartially.value && componentState.partialVisualSimulation!.nextToSimulate.isNotEmpty ? () {
|
|
||||||
componentState.partialVisualSimulation!.nextStep();
|
|
||||||
} : null,
|
|
||||||
);
|
|
||||||
|
|
||||||
final componentPicker = ComponentPicker(
|
|
||||||
key: pickerKey,
|
|
||||||
onSeletionUpdate: (selection) {
|
|
||||||
designSelection.value = selection;
|
|
||||||
if (selection != 'wiring') {
|
|
||||||
wireToDelete.value = null;
|
|
||||||
sourceToConnect.value = null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (orientation == Orientation.portrait) {
|
|
||||||
return Column(
|
|
||||||
mainAxisSize: MainAxisSize.max,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Stack(
|
|
||||||
children: [
|
|
||||||
stackCanvas,
|
|
||||||
if (isSimulating.value)
|
|
||||||
Positioned(
|
|
||||||
top: 8,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
child: Center(
|
|
||||||
child: debuggingButtons,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (!isSimulating.value)
|
|
||||||
componentPicker,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return Row(
|
hw(update.delta.dx, update.delta.dy);
|
||||||
mainAxisSize: MainAxisSize.max,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Stack(
|
|
||||||
children: [
|
|
||||||
stackCanvas,
|
|
||||||
if (isSimulating.value)
|
|
||||||
Positioned(
|
|
||||||
top: 8,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
child: Center(
|
|
||||||
child: debuggingButtons,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (!isSimulating.value)
|
|
||||||
componentPicker,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
child: OrientationBuilder(
|
||||||
|
builder: (context, orientation) {
|
||||||
|
final stackCanvas = StackCanvas(
|
||||||
|
key: canvasKey,
|
||||||
|
canvasController: canvasController,
|
||||||
|
animationDuration: const Duration(milliseconds: 50),
|
||||||
|
// disposeController: false,
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.background,
|
||||||
|
);
|
||||||
|
|
||||||
|
final debuggingButtons = DebuggingButtons(
|
||||||
|
partialSimulation: simulatePartially.value,
|
||||||
|
onPartialSimulationToggle: () {
|
||||||
|
simulatePartially.value = !simulatePartially.value;
|
||||||
|
},
|
||||||
|
onReset: simulatePartially.value ? () {
|
||||||
|
componentState.partialVisualSimulation!.restart();
|
||||||
|
} : null,
|
||||||
|
onNextStep: simulatePartially.value && componentState.partialVisualSimulation!.nextToSimulate.isNotEmpty ? () {
|
||||||
|
componentState.partialVisualSimulation!.nextStep();
|
||||||
|
} : null,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (orientation == Orientation.portrait) {
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
stackCanvas,
|
||||||
|
if (isSimulating.value)
|
||||||
|
Positioned(
|
||||||
|
top: 8,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
child: Center(
|
||||||
|
child: debuggingButtons,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return Row(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
stackCanvas,
|
||||||
|
if (isSimulating.value)
|
||||||
|
Positioned(
|
||||||
|
top: 8,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
child: Center(
|
||||||
|
child: debuggingButtons,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -627,168 +477,4 @@ class DebuggingButtons extends StatelessWidget {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ComponentPicker extends HookWidget {
|
|
||||||
const ComponentPicker({required this.onSeletionUpdate, super.key});
|
|
||||||
|
|
||||||
final void Function(String? selection) onSeletionUpdate;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final projectsState = useProvider<ProjectsState>();
|
|
||||||
final tickerProvider = useSingleTickerProvider();
|
|
||||||
final selection = useState<String?>(null);
|
|
||||||
final tabBarControllerState = useState<TabController?>(null );
|
|
||||||
useEffect(() {
|
|
||||||
selection.addListener(() {
|
|
||||||
onSeletionUpdate(selection.value);
|
|
||||||
});
|
|
||||||
|
|
||||||
tabBarControllerState.value = TabController(
|
|
||||||
length: 1 + projectsState.projects.length,
|
|
||||||
vsync: tickerProvider,
|
|
||||||
initialIndex: 1,
|
|
||||||
);
|
|
||||||
|
|
||||||
tabBarControllerState.value!.addListener(() {
|
|
||||||
if (tabBarControllerState.value!.index == 0) {
|
|
||||||
selection.value = 'wiring';
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
selection.value = null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return () {
|
|
||||||
tabBarControllerState.value?.dispose();
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
final tabBarController = tabBarControllerState.value!;
|
|
||||||
|
|
||||||
return OrientationBuilder(
|
|
||||||
builder: (context, orientation) {
|
|
||||||
return SizedBox(
|
|
||||||
height: orientation == Orientation.portrait ? 200 : null,
|
|
||||||
width: orientation == Orientation.landscape ? 300 : null,
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
TabBar(
|
|
||||||
controller: tabBarController,
|
|
||||||
tabs: [
|
|
||||||
const Tab(
|
|
||||||
text: 'Wiring',
|
|
||||||
),
|
|
||||||
for (final project in projectsState.projects)
|
|
||||||
Tab(
|
|
||||||
text: project.projectName,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
isScrollable: true,
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: TabBarView(
|
|
||||||
controller: tabBarController,
|
|
||||||
children: [
|
|
||||||
Center(
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: const [
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.all(8.0),
|
|
||||||
child: Text(
|
|
||||||
'To create wires, click a source and then click a sink to link them.',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.all(8.0),
|
|
||||||
child: Text(
|
|
||||||
'To remove wires, click them or tap them twice.',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
for (final project in projectsState.projects)
|
|
||||||
HookBuilder(
|
|
||||||
builder: (context) {
|
|
||||||
final scrollController = useScrollController();
|
|
||||||
|
|
||||||
final projectState = useFuture(() async {
|
|
||||||
final projectState = ProjectState();
|
|
||||||
await projectState.setCurrentProject(project);
|
|
||||||
return projectState;
|
|
||||||
}());
|
|
||||||
|
|
||||||
if (projectState.data == null) {
|
|
||||||
return Container();
|
|
||||||
}
|
|
||||||
final components = projectState.data!.index.components;
|
|
||||||
|
|
||||||
return Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: [
|
|
||||||
const Padding(
|
|
||||||
padding: EdgeInsets.all(8.0),
|
|
||||||
child: Text('To add a component, select it below and then click on the canvas to place it.'),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Scrollbar(
|
|
||||||
controller: scrollController,
|
|
||||||
scrollbarOrientation: orientation == Orientation.portrait ? ScrollbarOrientation.bottom : ScrollbarOrientation.right,
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
controller: scrollController,
|
|
||||||
scrollDirection: orientation == Orientation.portrait ? Axis.horizontal : Axis.vertical,
|
|
||||||
child: Wrap(
|
|
||||||
direction: orientation == Orientation.portrait ? Axis.vertical : Axis.horizontal,
|
|
||||||
crossAxisAlignment: WrapCrossAlignment.center,
|
|
||||||
children: [
|
|
||||||
for (final component in components)
|
|
||||||
IntrinsicWidth(
|
|
||||||
child: Card(
|
|
||||||
color: selection.value == '${project.projectId}/${component.componentId}' ? Theme.of(context).colorScheme.primaryContainer : null,
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () {
|
|
||||||
if (selection.value != '${project.projectId}/${component.componentId}') {
|
|
||||||
selection.value = '${project.projectId}/${component.componentId}';
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
selection.value = null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: Text(
|
|
||||||
component.componentName,
|
|
||||||
style: selection.value == '${project.projectId}/${component.componentId}'
|
|
||||||
? TextStyle(
|
|
||||||
inherit: true,
|
|
||||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
14
pubspec.lock
14
pubspec.lock
|
@ -275,13 +275,6 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.0"
|
version: "2.1.0"
|
||||||
hetu_script:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: hetu_script
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "0.3.12"
|
|
||||||
http_multi_server:
|
http_multi_server:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -499,13 +492,6 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.0"
|
version: "3.1.0"
|
||||||
recase:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: recase
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "4.0.0"
|
|
||||||
share_plus:
|
share_plus:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -41,7 +41,6 @@ dependencies:
|
||||||
stack_canvas:
|
stack_canvas:
|
||||||
git: https://github.com/dancojocaru2000/stack_canvas.git
|
git: https://github.com/dancojocaru2000/stack_canvas.git
|
||||||
tuple: ^2.0.0
|
tuple: ^2.0.0
|
||||||
hetu_script: ^0.3.12
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
Loading…
Add table
Reference in a new issue