-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
20 changed files
with
1,153 additions
and
318 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
import 'package:flutter/material.dart'; | ||
import 'package:visual_graphs/graph_editor/globals.dart'; | ||
import 'package:visual_graphs/graph_editor/models/graph.dart'; | ||
import 'package:visual_graphs/helpers/data_structures/pair.dart'; | ||
import 'package:visual_graphs/helpers/material_color_generator.dart'; | ||
import 'package:visual_graphs/helpers/notifiers/set_notifier.dart'; | ||
|
||
class Kruskal { | ||
int weight = 0; | ||
UnionFind<Vertex> unionFind = UnionFind(); | ||
final SetNotifier<Edge> includedEdges = SetNotifier<Edge>(); | ||
ValueNotifier<Edge?> currentEdge = ValueNotifier<Edge?>(null); | ||
|
||
void start() async { | ||
greyOut(); | ||
updateColors(); | ||
|
||
List<Edge> edges = Globals.game.graph.edges | ||
..sort((a, b) => a.weight.compareTo(b.weight)); | ||
|
||
for (var edge in edges) { | ||
await Future.delayed(const Duration(milliseconds: 500)); | ||
await checkEdge(currentEdge.value = edge); | ||
} | ||
currentEdge.value = null; | ||
} | ||
|
||
bool makesCycle(Edge edge) { | ||
return unionFind.find(edge.from).first == unionFind.find(edge.to).first; | ||
} | ||
|
||
Future checkEdge(Edge edge) async { | ||
edge.component | ||
..edgeWidth += 2 | ||
..setColors(Colors.white, Colors.white); | ||
|
||
edge.from.component | ||
..radius = Globals.defaultVertexRadius + 5 | ||
..drawBorder = true; | ||
|
||
edge.to.component | ||
..radius = Globals.defaultVertexRadius + 5 | ||
..drawBorder = true; | ||
|
||
await Future.delayed(const Duration(milliseconds: 500)); | ||
|
||
edge.component.edgeWidth = Globals.defaultEdgeWidth; | ||
edge.from.component | ||
..radius = Globals.defaultVertexRadius | ||
..drawBorder = false; | ||
edge.to.component | ||
..radius = Globals.defaultVertexRadius | ||
..drawBorder = false; | ||
|
||
if (!edge.isSelfEdge && !makesCycle(edge)) { | ||
includeEdge(edge); | ||
} else { | ||
edge.component.setColors(Colors.black, Colors.black); | ||
} | ||
updateColors(); | ||
} | ||
|
||
void includeEdge(Edge edge) { | ||
unionFind.union(edge.from, edge.to); | ||
includedEdges.add(edge); | ||
weight += edge.weight; | ||
} | ||
|
||
void updateColors() { | ||
for (var edge in includedEdges.set) { | ||
var color = unionFind.find(edge.from).second ?? Colors.white; | ||
edge.component.setColors(color, color); | ||
edge.from.component.setColors(color, color); | ||
edge.to.component.setColors(color, color); | ||
} | ||
} | ||
|
||
void greyOut() { | ||
for (var vertex in Globals.game.graph.vertices) { | ||
vertex.component.setColors(Colors.grey.shade800, Colors.grey.shade800); | ||
} | ||
for (var edge in Globals.game.graph.edges) { | ||
edge.component.setColors(Colors.grey.shade800, Colors.grey.shade800); | ||
} | ||
} | ||
|
||
void clear() { | ||
weight = 0; | ||
unionFind.clear(); | ||
includedEdges.clear(); | ||
} | ||
} | ||
|
||
class UnionFind<T> { | ||
final MaterialColorGenerator materialColorGenerator = | ||
MaterialColorGenerator(); | ||
|
||
Map<T, Pair<T, Color?>> parent = {}; | ||
|
||
union(T a, T b) { | ||
var aParent = find(a); | ||
var bParent = find(b); | ||
|
||
if (aParent.first == bParent.first) { | ||
return; | ||
} | ||
|
||
switch ((aParent.second != null, bParent.second != null)) { | ||
case (true, true): | ||
parent[aParent.first] = bParent; | ||
break; | ||
case (true, false): | ||
parent[bParent.first] = aParent; | ||
break; | ||
case (false, true): | ||
parent[aParent.first] = bParent; | ||
break; | ||
case (false, false): | ||
var color = materialColorGenerator.next(); | ||
parent[aParent.first] = Pair(bParent.first, color); | ||
parent[bParent.first] = Pair(bParent.first, color); | ||
break; | ||
} | ||
} | ||
|
||
Pair<T, Color?> find(T a) { | ||
if (parent[a] == null) { | ||
parent[a] = Pair(a, null); | ||
return parent[a]!; | ||
} else { | ||
if (parent[a]!.first == a) { | ||
return parent[a]!; | ||
} else { | ||
return parent[a] = find(parent[a]!.first); | ||
} | ||
} | ||
} | ||
|
||
void clear() { | ||
parent.clear(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,212 @@ | ||
import 'package:flutter/material.dart'; | ||
import 'package:visual_graphs/graph_editor/globals.dart'; | ||
import 'package:visual_graphs/graph_editor/widgets/game_info_box.dart'; | ||
|
||
class EditorControls extends StatefulWidget { | ||
const EditorControls({super.key}); | ||
|
||
@override | ||
State<EditorControls> createState() => _EditorControlsState(); | ||
} | ||
|
||
class _EditorControlsState extends State<EditorControls> { | ||
@override | ||
Widget build(BuildContext context) { | ||
bool smallScreen = MediaQuery.of(context).size.width < 600; | ||
|
||
if (Globals.game.gameMode == GameMode.pickVertex) { | ||
return Padding( | ||
padding: const EdgeInsets.all(20.0), | ||
child: Row( | ||
mainAxisSize: MainAxisSize.max, | ||
crossAxisAlignment: CrossAxisAlignment.center, | ||
mainAxisAlignment: MainAxisAlignment.start, | ||
children: [ | ||
const SizedBox(width: 40), | ||
const Spacer(), | ||
const Text( | ||
"Pick a vertex", | ||
style: TextStyle( | ||
color: Colors.white, | ||
fontSize: 20, | ||
), | ||
), | ||
const Spacer(), | ||
SizedBox( | ||
width: 40, | ||
child: IconButton.filledTonal( | ||
tooltip: "Cancel", | ||
onPressed: () => Globals.game.restorePreviousGameMode(), | ||
icon: const Icon(Icons.cancel), | ||
style: squareIconButtonStyle, | ||
), | ||
) | ||
], | ||
), | ||
); | ||
} | ||
|
||
return Padding( | ||
padding: const EdgeInsets.all(20.0), | ||
child: Column( | ||
mainAxisSize: MainAxisSize.max, | ||
mainAxisAlignment: MainAxisAlignment.start, | ||
crossAxisAlignment: CrossAxisAlignment.start, | ||
children: [ | ||
if (Globals.game.gameMode == GameMode.lockedMode) | ||
IconButton.filled( | ||
onPressed: () => Globals.game.gameMode = GameMode.defaultMode, | ||
icon: const Icon(Icons.edit), | ||
style: squareIconButtonStyle, | ||
), | ||
if (Globals.game.gameMode != GameMode.lockedMode) | ||
Column( | ||
mainAxisAlignment: MainAxisAlignment.start, | ||
mainAxisSize: MainAxisSize.min, | ||
crossAxisAlignment: CrossAxisAlignment.start, | ||
children: [ | ||
Row( | ||
mainAxisAlignment: MainAxisAlignment.start, | ||
mainAxisSize: MainAxisSize.max, | ||
crossAxisAlignment: CrossAxisAlignment.center, | ||
children: [ | ||
IconButton.filledTonal( | ||
onPressed: () => | ||
Globals.game.gameMode = GameMode.lockedMode, | ||
icon: const Icon(Icons.done), | ||
style: squareIconButtonStyle, | ||
), | ||
const Spacer(), | ||
ValueListenableBuilder( | ||
valueListenable: Globals.game.undoStack.stackSizeNotifier, | ||
builder: (context, size, child) { | ||
return IconButton.filled( | ||
icon: const Icon(Icons.undo), | ||
onPressed: size == 0 ? null : Globals.game.undo, | ||
tooltip: "Undo", | ||
disabledColor: Colors.grey, | ||
); | ||
}, | ||
), | ||
], | ||
), | ||
const SizedBox(height: 10), | ||
SegmentedButton<GameMode>( | ||
segments: [ | ||
ButtonSegment( | ||
value: GameMode.defaultMode, | ||
label: !smallScreen ? const Text("Default") : null, | ||
icon: const Icon(Icons.mouse), | ||
tooltip: smallScreen ? "Default Mode" : null, | ||
), | ||
ButtonSegment( | ||
value: GameMode.addVertex, | ||
label: !smallScreen ? const Text("Add Vertex") : null, | ||
icon: const Icon(Icons.add), | ||
tooltip: smallScreen ? "Add Vertex" : null, | ||
), | ||
ButtonSegment( | ||
value: GameMode.addEdge, | ||
label: !smallScreen ? const Text("Add Edge") : null, | ||
icon: const Icon(Icons.linear_scale), | ||
tooltip: smallScreen ? "Add Edge" : null, | ||
), | ||
ButtonSegment( | ||
value: GameMode.deleteComponent, | ||
label: !smallScreen ? const Text("Delete") : null, | ||
icon: const Icon(Icons.delete), | ||
tooltip: smallScreen ? "Delete" : null, | ||
), | ||
], | ||
style: SegmentedButton.styleFrom( | ||
backgroundColor: Theme.of(context).colorScheme.surface, | ||
foregroundColor: Theme.of(context).colorScheme.primary, | ||
selectedForegroundColor: | ||
Theme.of(context).colorScheme.onPrimary, | ||
selectedBackgroundColor: | ||
Theme.of(context).colorScheme.primary, | ||
), | ||
showSelectedIcon: false, | ||
selected: {Globals.game.gameMode}, | ||
onSelectionChanged: (p0) => Globals.game.gameMode = p0.first, | ||
), | ||
const SizedBox(height: 10), | ||
if (Globals.game.gameMode == GameMode.addEdge) | ||
Card( | ||
margin: EdgeInsets.zero, | ||
child: Padding( | ||
padding: const EdgeInsets.symmetric( | ||
horizontal: 8, | ||
vertical: 0, | ||
), | ||
child: Row( | ||
mainAxisSize: MainAxisSize.min, | ||
mainAxisAlignment: MainAxisAlignment.start, | ||
children: [ | ||
Checkbox.adaptive( | ||
visualDensity: VisualDensity.compact, | ||
value: Globals.game.addEdgeModeIsDirected, | ||
onChanged: (value) => setState(() => | ||
Globals.game.addEdgeModeIsDirected = value!), | ||
), | ||
const Text("Directed"), | ||
const SizedBox(width: 20), | ||
Checkbox.adaptive( | ||
value: Globals.game.addEdgeModeIsWeighted, | ||
visualDensity: VisualDensity.compact, | ||
onChanged: (value) => setState(() => | ||
Globals.game.addEdgeModeIsWeighted = value!), | ||
), | ||
const Text("Weighted"), | ||
], | ||
), | ||
), | ||
), | ||
if (Globals.game.gameMode == GameMode.addEdge) | ||
const SizedBox(height: 10), | ||
ElevatedButton.icon( | ||
onPressed: () => Globals.game.clearGraph(), | ||
label: const Text("Clear Graph"), | ||
icon: const Icon(Icons.clear), | ||
), | ||
], | ||
), | ||
const Spacer(), | ||
Row( | ||
mainAxisSize: MainAxisSize.max, | ||
mainAxisAlignment: MainAxisAlignment.start, | ||
children: [ | ||
IconButton( | ||
onPressed: () => Globals.game.centerCamera(), | ||
icon: const Icon( | ||
Icons.center_focus_strong, | ||
color: Colors.white, | ||
), | ||
tooltip: "Center Camera", | ||
), | ||
const Spacer(), | ||
FloatingActionButton.small( | ||
tooltip: "Help", | ||
onPressed: () { | ||
showDialog( | ||
context: context, | ||
builder: (context) => const GameInfoBox(), | ||
); | ||
}, | ||
child: const Icon(Icons.help), | ||
), | ||
], | ||
), | ||
], | ||
), | ||
); | ||
} | ||
} | ||
|
||
final squareIconButtonStyle = ButtonStyle( | ||
shape: WidgetStateProperty.all<RoundedRectangleBorder>( | ||
RoundedRectangleBorder( | ||
borderRadius: BorderRadius.circular(10), | ||
), | ||
), | ||
); |
Oops, something went wrong.