From 3fad3812e117c6bc16b5007076803f498538e4c4 Mon Sep 17 00:00:00 2001 From: Mica White Date: Mon, 8 Dec 2025 19:54:36 -0500 Subject: First commit --- lib/main.dart | 254 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 254 insertions(+) create mode 100755 lib/main.dart (limited to 'lib/main.dart') 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 createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + final List _consoleEntries = []; + + static const List tabs = [ + Tab(text: 'Project'), + Tab(text: 'Log'), + Tab(text: 'Profiler'), + Tab(text: 'Console'), + Tab(text: 'Settings'), + ]; + + Process? _runningGame; + final StringBuffer _buffer = StringBuffer(); + + final List _logEntries = []; + + DateTime? _previousFrameTime; + int _nextFrameId = 0; + final List _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(), + ], + ), + ), + ); + } +} -- cgit v1.2.3