Compare commits

..

No commits in common. "0ab607ae3ddfdc1c65f2fe3bf2255957ebf531e1" and "2a19505ed36c50c89597c9811ef9983b3c15c80f" have entirely different histories.

4 changed files with 48 additions and 241 deletions

View file

@ -27,16 +27,6 @@ class NewProjectDialog extends HookWidget {
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Align(
alignment: Alignment.centerRight,
child: IconButton(
icon: const Icon(Icons.close),
tooltip: 'Close',
onPressed: () {
Navigator.of(context).pop();
},
),
),
Center( Center(
child: Padding( child: Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),

View file

@ -1,4 +1,3 @@
import 'package:collection/collection.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:logic_circuits_simulator/models/project.dart'; import 'package:logic_circuits_simulator/models/project.dart';
@ -19,50 +18,18 @@ class EditComponentPage extends HookWidget {
final anySave = useState(false); final anySave = useState(false);
final projectState = useProvider<ProjectState>(); final projectState = useProvider<ProjectState>();
final ce = projectState.index.components.where((c) => c.componentId == component.componentId).first; final ce = projectState.index.components.where((c) => c.componentId == component.componentId).first;
final truthTable = useState(ce.truthTable?.toList());
final inputs = useState(ce.inputs.toList());
final outputs = useState(ce.outputs.toList());
final componentNameEditingController = useTextEditingController(text: ce.componentName); final componentNameEditingController = useTextEditingController(text: ce.componentName);
useValueListenable(componentNameEditingController); useValueListenable(componentNameEditingController);
final dirty = useMemoized( final dirty = useMemoized(() {
() { if (componentNameEditingController.text.isEmpty) {
if (componentNameEditingController.text.isEmpty) { // Don't allow saving empty name
// Don't allow saving empty name
return false;
}
if (inputs.value.isEmpty) {
// Don't allow saving empty inputs
return false;
}
if (outputs.value.isEmpty) {
// Don't allow saving empty outputs
return false;
}
if (componentNameEditingController.text != ce.componentName) {
return true;
}
if (!const ListEquality().equals(inputs.value, ce.inputs)) {
return true;
}
if (!const ListEquality().equals(outputs.value, ce.outputs)) {
return true;
}
if (!const ListEquality().equals(truthTable.value, ce.truthTable)) {
return true;
}
return false; return false;
}, }
[ if (componentNameEditingController.text != ce.componentName) {
componentNameEditingController.text, return true;
ce.componentName, }
inputs.value, return false;
ce.inputs, }, [componentNameEditingController.text, ce.componentName]);
outputs.value,
ce.outputs,
truthTable.value,
ce.truthTable,
],
);
return WillPopScope( return WillPopScope(
onWillPop: () async { onWillPop: () async {
@ -124,154 +91,38 @@ class EditComponentPage extends HookWidget {
SliverToBoxAdapter( SliverToBoxAdapter(
child: Padding( child: Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Row( child: Text(
crossAxisAlignment: CrossAxisAlignment.center, 'Inputs',
children: [ style: Theme.of(context).textTheme.headline5,
Expanded(
child: Text(
'Inputs',
style: Theme.of(context).textTheme.headline5,
),
),
IconButton(
icon: const Icon(Icons.add),
tooltip: 'Add new input',
onPressed: () {
},
),
],
), ),
), ),
), ),
SliverList( SliverList(
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, idx) => ListTile( (context, i) => ListTile(
leading: inputs.value.length > 1 ? IconButton( title: Text(ce.inputs[i]),
icon: const Icon(Icons.remove_circle),
color: Colors.red,
tooltip: 'Remove input ${inputs.value[idx]}',
onPressed: () async {
final shouldRemove = await showDialog<bool>(
context: context,
builder: (context) {
return AlertDialog(
title: Text('Remove Input ${inputs.value[idx]}?'),
content: const Text('Are you sure you want to remove the input?'),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text('Cancel'),
),
ElevatedButton(
onPressed: () {
Navigator.of(context).pop(true);
},
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.red),
),
child: const Text('Remove'),
),
],
);
},
);
if (shouldRemove == true) {
if (truthTable.value != null) {
final tt = truthTable.value!.toList();
final shiftIndex = inputs.value.length - 1 - idx;
final shifted = 1 << shiftIndex;
final indecesToRemove = [];
for (var i = tt.length - 1; i >= 0; i--) {
if (i & shifted == 0) {
indecesToRemove.add(i);
}
}
for (final i in indecesToRemove) {
tt.removeAt(i);
}
truthTable.value = tt;
}
inputs.value = inputs.value.toList()..removeRange(idx, idx+1);
}
},
) : null,
title: Text(inputs.value[idx]),
), ),
childCount: inputs.value.length, childCount: ce.inputs.length,
), ),
), ),
SliverToBoxAdapter( SliverToBoxAdapter(
child: Padding( child: Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Row( child: Text(
crossAxisAlignment: CrossAxisAlignment.center, 'Outputs',
children: [ style: Theme.of(context).textTheme.headline5,
Expanded(
child: Text(
'Outputs',
style: Theme.of(context).textTheme.headline5,
),
),
IconButton(
icon: const Icon(Icons.add),
tooltip: 'Add new output',
onPressed: () {
},
),
],
), ),
), ),
), ),
SliverList( SliverList(
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, idx) => ListTile( (context, i) => ListTile(
leading: outputs.value.length > 1 ? IconButton( title: Text(ce.outputs[i]),
icon: const Icon(Icons.remove_circle),
color: Colors.red,
tooltip: 'Remove output ${outputs.value[idx]}',
onPressed: () async {
final shouldRemove = await showDialog<bool>(
context: context,
builder: (context) {
return AlertDialog(
title: Text('Remove Output ${outputs.value[idx]}?'),
content: const Text('Are you sure you want to remove the output?'),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text('Cancel'),
),
ElevatedButton(
onPressed: () {
Navigator.of(context).pop(true);
},
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.red),
),
child: const Text('Remove'),
),
],
);
},
);
if (shouldRemove == true) {
if (truthTable.value != null) {
for (var i = 0; i < truthTable.value!.length; i++) {
truthTable.value!.replaceRange(i, i+1, [truthTable.value![i].replaceRange(idx, idx+1, "")]);
}
}
outputs.value = outputs.value.toList()..removeRange(idx, idx+1);
}
},
) : null,
title: Text(outputs.value[idx]),
), ),
childCount: outputs.value.length, childCount: ce.outputs.length,
), ),
), ),
if (truthTable.value != null) ...[ if (ce.truthTable != null) ...[
SliverToBoxAdapter( SliverToBoxAdapter(
child: Padding( child: Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
@ -285,12 +136,9 @@ class EditComponentPage extends HookWidget {
child: Padding( child: Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: TruthTableEditor( child: TruthTableEditor(
truthTable: truthTable.value!, truthTable: ce.truthTable!,
inputs: inputs.value, inputs: ce.inputs,
outputs: outputs.value, outputs: ce.outputs,
onUpdateTable: (idx, newValue) {
truthTable.value = truthTable.value?.toList()?..replaceRange(idx, idx+1, [newValue]);
},
), ),
), ),
) )
@ -302,11 +150,6 @@ class EditComponentPage extends HookWidget {
if (componentNameEditingController.text.isNotEmpty) { if (componentNameEditingController.text.isNotEmpty) {
await projectState.editComponent(component.copyWith(componentName: componentNameEditingController.text)); await projectState.editComponent(component.copyWith(componentName: componentNameEditingController.text));
} }
await projectState.editComponent(ce.copyWith(
inputs: inputs.value,
outputs: outputs.value,
truthTable: truthTable.value,
));
anySave.value = true; anySave.value = true;
// TODO: Implement saving // TODO: Implement saving
}, },
@ -344,9 +187,8 @@ class TruthTableHeaderText extends StatelessWidget {
class TruthTableTrue extends StatelessWidget { class TruthTableTrue extends StatelessWidget {
final BoxBorder? border; final BoxBorder? border;
final void Function()? onTap;
const TruthTableTrue({super.key, this.border, this.onTap}); const TruthTableTrue({super.key, this.border});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -354,15 +196,12 @@ class TruthTableTrue extends StatelessWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
border: border, border: border,
), ),
child: InkWell( child: Text(
onTap: onTap, 'T',
child: Text( textAlign: TextAlign.center,
'T', style: Theme.of(context).textTheme.bodyLarge!.copyWith(
textAlign: TextAlign.center, color: Colors.green,
style: Theme.of(context).textTheme.bodyLarge!.copyWith( fontSize: 20,
color: Colors.green,
fontSize: 20,
),
), ),
), ),
); );
@ -371,9 +210,8 @@ class TruthTableTrue extends StatelessWidget {
class TruthTableFalse extends StatelessWidget { class TruthTableFalse extends StatelessWidget {
final BoxBorder? border; final BoxBorder? border;
final void Function()? onTap;
const TruthTableFalse({super.key, this.border, this.onTap}); const TruthTableFalse({super.key, this.border});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -381,15 +219,12 @@ class TruthTableFalse extends StatelessWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
border: border, border: border,
), ),
child: InkWell( child: Text(
onTap: onTap, 'F',
child: Text( textAlign: TextAlign.center,
'F', style: Theme.of(context).textTheme.bodyLarge!.copyWith(
textAlign: TextAlign.center, color: Colors.red,
style: Theme.of(context).textTheme.bodyLarge!.copyWith( fontSize: 20,
color: Colors.red,
fontSize: 20,
),
), ),
), ),
); );
@ -401,9 +236,7 @@ class TruthTableEditor extends StatelessWidget {
final List<String> outputs; final List<String> outputs;
final List<String> truthTable; final List<String> truthTable;
final void Function(int, String) onUpdateTable; const TruthTableEditor({Key? key, required this.inputs, required this.outputs, required this.truthTable}) : super(key: key);
const TruthTableEditor({Key? key, required this.inputs, required this.outputs, required this.truthTable, required this.onUpdateTable}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -435,33 +268,18 @@ class TruthTableEditor extends StatelessWidget {
final inputBinary = (index - 1).toRadixString(2).padLeft(inputs.length, '0'); final inputBinary = (index - 1).toRadixString(2).padLeft(inputs.length, '0');
final outputBinary = truthTable[index - 1]; final outputBinary = truthTable[index - 1];
Widget runeToWidget({required int rune, void Function()? onTap, BoxBorder? border}) { Widget runeToWidget(int rune, {BoxBorder? border}) {
return int.parse(String.fromCharCode(rune)) != 0 return int.parse(String.fromCharCode(rune)) != 0 ? TruthTableTrue(border: border) : TruthTableFalse(border: border);
? TruthTableTrue(
border: border,
onTap: onTap,
)
: TruthTableFalse(
border: border,
onTap: onTap,
);
} }
return TableRow( return TableRow(
children: inputBinary.runes.indexedMap( children: inputBinary.runes.indexedMap(
(i, r) => runeToWidget( (index, r) => runeToWidget(
rune: r, r,
border: i == inputBinary.runes.length - 1 ? const Border(right: BorderSide(width: 2)) : null, border: index == inputBinary.runes.length - 1 ? const Border(right: BorderSide(width: 2)) : null,
) )
) )
.followedBy(outputBinary.runes.indexedMap( .followedBy(outputBinary.runes.map((r) => runeToWidget(r)))
(i, r) => runeToWidget(
rune: r,
onTap: () {
onUpdateTable(index - 1, outputBinary.replaceRange(i, i+1, (outputBinary[i] == "1") ? "0" : "1"));
},
),
))
.toList(), .toList(),
); );
}, },

View file

@ -28,7 +28,7 @@ packages:
name: async name: async
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.8.2" version: "2.9.0"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@ -128,7 +128,7 @@ packages:
source: hosted source: hosted
version: "4.1.0" version: "4.1.0"
collection: collection:
dependency: "direct main" dependency: transitive
description: description:
name: collection name: collection
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"

View file

@ -34,7 +34,6 @@ dependencies:
intl: ^0.17.0 intl: ^0.17.0
flutter_hooks: ^0.18.3 flutter_hooks: ^0.18.3
uuid: ^3.0.6 uuid: ^3.0.6
collection: ^1.16.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: