diff options
| author | Mica White <botahamec@outlook.com> | 2025-12-08 19:54:36 -0500 |
|---|---|---|
| committer | Mica White <botahamec@outlook.com> | 2025-12-08 19:54:36 -0500 |
| commit | 3fad3812e117c6bc16b5007076803f498538e4c4 (patch) | |
| tree | 197b7b27c3f1c1d2b8396e4c4150b37b4d3127e5 /lib/main.dart | |
Diffstat (limited to 'lib/main.dart')
| -rwxr-xr-x | lib/main.dart | 254 |
1 files changed, 254 insertions, 0 deletions
diff --git a/lib/main.dart b/lib/main.dart new file mode 100755 index 0000000..270eb34 --- /dev/null +++ b/lib/main.dart @@ -0,0 +1,254 @@ +import 'dart:io';
+
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+
+import 'console.dart';
+import 'logs.dart';
+import 'project.dart';
+import 'settings.dart';
+import 'profiler.dart';
+import 'serializer.dart';
+
+const maxConsoleEntries = 15000;
+const maxProfileFrames = 15000;
+
+void main() {
+ runApp(MultiProvider(
+ providers: [
+ ChangeNotifierProvider(create: (context) => ProjectConfig()),
+ ],
+ child: const AlligatorApp(),
+ ));
+}
+
+class AlligatorApp extends StatelessWidget {
+ const AlligatorApp({super.key});
+
+ // This widget is the root of your application.
+ @override
+ Widget build(BuildContext context) {
+ return MaterialApp(
+ title: 'Alligator Editor',
+ locale: const Locale('en', 'US'),
+ theme: ThemeData(
+ colorScheme: ColorScheme.fromSeed(
+ brightness: Brightness.dark,
+ seedColor: Colors.green,
+ ),
+ useMaterial3: true,
+ ),
+ home: const MyHomePage(),
+ );
+ }
+}
+
+class RunButtons extends StatelessWidget {
+ final bool isRunning;
+ final Function(BuildContext) onStart;
+ final Function(BuildContext) onStop;
+
+ const RunButtons(this.isRunning, {required this.onStart, required this.onStop, super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ if (!this.isRunning) {
+ return TextButton.icon(
+ onPressed: () => this.onStart(context),
+ icon: const Icon(Icons.play_arrow),
+ label: const Text('Run'),
+ style: TextButton.styleFrom(
+ shape: RoundedRectangleBorder(
+ borderRadius: const BorderRadius.all(Radius.circular(8)),
+ side: BorderSide(
+ color: Theme.of(context).colorScheme.primary,
+ width: 3,
+ ),
+ ),
+ ),
+ );
+ } else {
+ return TextButton.icon(
+ onPressed: () => this.onStop(context),
+ icon: const Icon(Icons.stop),
+ label: const Text('Stop'),
+ style: TextButton.styleFrom(
+ foregroundColor: Theme.of(context).colorScheme.error,
+ shape: RoundedRectangleBorder(
+ borderRadius: const BorderRadius.all(Radius.circular(8)),
+ side: BorderSide(
+ color: Theme.of(context).colorScheme.error,
+ width: 3,
+ ),
+ ),
+ ),
+ );
+ }
+ }
+}
+
+class MyHomePage extends StatefulWidget {
+ const MyHomePage({super.key});
+
+ @override
+ State<MyHomePage> createState() => _MyHomePageState();
+}
+
+class _MyHomePageState extends State<MyHomePage> {
+ final List<ConsoleEntry> _consoleEntries = [];
+
+ static const List<Tab> tabs = <Tab>[
+ Tab(text: 'Project'),
+ Tab(text: 'Log'),
+ Tab(text: 'Profiler'),
+ Tab(text: 'Console'),
+ Tab(text: 'Settings'),
+ ];
+
+ Process? _runningGame;
+ final StringBuffer _buffer = StringBuffer();
+
+ final List<LogEntry> _logEntries = [];
+
+ DateTime? _previousFrameTime;
+ int _nextFrameId = 0;
+ final List<ProfileFrame> _frames = [];
+
+ void _sendMessage(String msg) {
+ this._runningGame?.stdin.writeln(msg);
+ setState(() {
+ _consoleEntries.add(ConsoleEntry(
+ text: msg,
+ generatedByRuntime: false,
+ timeGenerated: DateTime.now(),
+ ));
+ });
+ }
+
+ void _parseMessage(String message) {
+ final args = message.split(' ');
+ if (args[0] == 'runtimelog') {
+ final logType = LogTypeExtension.parse(args[1]);
+ final [fileName, lineNumberS] = args[2].split(':');
+ final lineNumber = int.parse(lineNumberS);
+ final logMsg = args.sublist(3).join(' ');
+ setState(() => this._logEntries.add(LogEntry(false, logType, fileName, lineNumber, logMsg)));
+ } else if (args[0] == 'scriptlog') {
+ final logType = LogTypeExtension.parse(args[1]);
+ final [fileName, lineNumberS] = args[2].split(':');
+ final lineNumber = int.parse(lineNumberS);
+ final logMsg = args.sublist(3).join(' ');
+ setState(() => this._logEntries.add(LogEntry(true, logType, fileName, lineNumber, logMsg)));
+ } else if (args[0] == 'frametime') {
+ if (_previousFrameTime == null) {
+ _previousFrameTime = DateTime.fromMicrosecondsSinceEpoch(int.parse(args[1]));
+ return;
+ }
+
+ final id = _nextFrameId++;
+ final start = _previousFrameTime!;
+ final end = DateTime.fromMicrosecondsSinceEpoch(int.parse(args[1]));
+ _previousFrameTime = end;
+
+ setState(() {
+ _frames.add(ProfileFrame(id, start, end, []));
+ if (_frames.length >= maxProfileFrames) {
+ _frames.removeRange(0, _frames.length - maxProfileFrames);
+ }
+ });
+ }
+ }
+
+ void _startGame(BuildContext cx) async {
+ ProjectConfig projectConfig = Provider.of(cx, listen: false);
+ final gameConfig = AlligatorGame.fromConfig(projectConfig: projectConfig).toJson();
+
+ this._runningGame?.kill();
+
+ this._buffer.clear();
+ _consoleEntries.clear();
+ this._logEntries.clear();
+ this._previousFrameTime = null;
+ this._nextFrameId = 0;
+ this._frames.clear();
+
+ this._runningGame = await Process.start("alligator", ["--config", gameConfig, "--debug"]);
+ this._runningGame!.exitCode.then((value) => this._runningGame = null);
+ this._runningGame!.stdout.listen(
+ (event) async {
+ await Future.delayed(const Duration(milliseconds: 16));
+ for (final code in event) {
+ final char = String.fromCharCode(code);
+
+ if (char == "\n") {
+ final message = this._buffer.toString();
+ setState(
+ () => _consoleEntries.add(ConsoleEntry(
+ text: message,
+ generatedByRuntime: true,
+ timeGenerated: DateTime.now(),
+ )),
+ );
+
+ this._buffer.clear();
+ _parseMessage(message);
+ } else {
+ this._buffer.write(char);
+ }
+
+ setState(() {
+ if (_consoleEntries.length > maxConsoleEntries) {
+ _consoleEntries.removeRange(0, _consoleEntries.length - maxConsoleEntries);
+ }
+ });
+ }
+ },
+ onDone: () {
+ this.setState(() => this._runningGame = null);
+ },
+ );
+ }
+
+ @override
+ void initState() {
+ super.initState();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ // This method is rerun every time setState is called, for instance as done
+ // by the _incrementCounter method above.
+ //
+ // The Flutter framework has been optimized to make rerunning build methods
+ // fast, so that you can just rebuild anything that needs updating rather
+ // than having to individually change instances of widgets.
+ return DefaultTabController(
+ length: tabs.length,
+ child: Scaffold(
+ appBar: AppBar(
+ title: const Text("Alligator Editor"),
+ bottom: const TabBar(
+ tabs: tabs,
+ ),
+ actions: [
+ RunButtons(
+ this._runningGame != null,
+ onStart: this._startGame,
+ onStop: (_) => this._runningGame!.kill(),
+ ),
+ const SizedBox(width: 20),
+ ],
+ ),
+ body: TabBarView(
+ children: [
+ const ProjectPage(),
+ LogPage(this._logEntries),
+ ProfilerPage(_frames),
+ ConsolePage(_consoleEntries, this._sendMessage),
+ const SettingsPage(),
+ ],
+ ),
+ ),
+ );
+ }
+}
|
