2023-09-11 02:00:06 +00:00
|
|
|
import 'dart:async';
|
2023-09-10 03:26:30 +00:00
|
|
|
import 'dart:developer';
|
2023-09-11 02:00:06 +00:00
|
|
|
import 'dart:math' as math;
|
|
|
|
import 'dart:ui' as ui;
|
2023-09-10 03:26:30 +00:00
|
|
|
|
2023-09-11 02:00:06 +00:00
|
|
|
import 'package:dartterm/assets.dart';
|
2023-09-06 23:38:53 +00:00
|
|
|
import 'package:dartterm/colors.dart';
|
2023-09-06 03:11:15 +00:00
|
|
|
import 'package:dartterm/cp437.dart';
|
|
|
|
import 'package:dartterm/fonts.dart';
|
2023-09-10 03:26:30 +00:00
|
|
|
import 'package:dartterm/input.dart';
|
|
|
|
import 'package:flutter/gestures.dart';
|
2023-09-06 03:11:15 +00:00
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
|
2023-09-11 02:00:06 +00:00
|
|
|
part 'terminal/action_oriented.dart';
|
|
|
|
part 'terminal/cursor.dart';
|
|
|
|
part 'terminal/painter.dart';
|
|
|
|
part 'terminal/reexports.dart';
|
|
|
|
part 'terminal/tile.dart';
|
|
|
|
|
|
|
|
enum _State {
|
|
|
|
baseState,
|
|
|
|
waitMenu,
|
|
|
|
}
|
|
|
|
|
2023-09-06 03:11:15 +00:00
|
|
|
class Terminal {
|
2023-09-10 00:36:37 +00:00
|
|
|
static const int width = 88;
|
2023-09-06 23:45:34 +00:00
|
|
|
static const int height = 50;
|
2023-09-06 03:11:15 +00:00
|
|
|
static const int nTiles = width * height;
|
|
|
|
|
2023-09-11 02:00:06 +00:00
|
|
|
late List<Tile> _tiles;
|
|
|
|
(int, int)? _lastSeenMouse;
|
|
|
|
ScreenDimensions? _screenDimensions;
|
|
|
|
|
|
|
|
final StreamController<Input> _inputSink =
|
|
|
|
StreamController<Input>.broadcast();
|
|
|
|
late final Stream<Input> _inputSource = _inputSink.stream.asBroadcastStream();
|
|
|
|
// ignore: prefer_final_fields
|
|
|
|
_State _state = _State.baseState;
|
2023-09-06 03:11:15 +00:00
|
|
|
|
|
|
|
Terminal() {
|
2023-09-11 02:00:06 +00:00
|
|
|
_tiles = [for (var i = 0; i < nTiles; i += 1) Tile()];
|
2023-09-06 03:11:15 +00:00
|
|
|
}
|
|
|
|
|
2023-09-10 03:26:30 +00:00
|
|
|
Widget toWidget(BuildContext context) {
|
2023-09-06 03:11:15 +00:00
|
|
|
var scalingFactor = MediaQuery.devicePixelRatioOf(context);
|
2023-09-10 03:26:30 +00:00
|
|
|
return MouseRegion(
|
|
|
|
onExit: _onPointerExit,
|
|
|
|
child: Listener(
|
|
|
|
onPointerDown: _onPointerDown,
|
|
|
|
onPointerHover: _onPointerHover,
|
|
|
|
child: CustomPaint(
|
|
|
|
painter: TerminalCustomPainter(this, scalingFactor))));
|
2023-09-06 03:11:15 +00:00
|
|
|
}
|
|
|
|
|
2023-09-11 02:00:06 +00:00
|
|
|
(int, int)? _toXY(int i) {
|
2023-09-06 03:11:15 +00:00
|
|
|
if (i < 0 || i > nTiles) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
return (i % width, i ~/ width);
|
|
|
|
}
|
|
|
|
|
2023-09-11 02:00:06 +00:00
|
|
|
int? _fromXY(int x, int y) {
|
2023-09-06 03:11:15 +00:00
|
|
|
if (x < 0 || x >= width) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
if (y < 0 || y >= height) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return x + y * width;
|
|
|
|
}
|
2023-09-10 03:26:30 +00:00
|
|
|
|
2023-09-11 02:00:06 +00:00
|
|
|
void _notifyInput(Input i) {
|
|
|
|
log("Input: $i $_lastSeenMouse");
|
|
|
|
_inputSink.add(i);
|
2023-09-10 03:26:30 +00:00
|
|
|
}
|
|
|
|
|
2023-09-11 02:00:06 +00:00
|
|
|
void _notifyScreenDimensions(ScreenDimensions sd) {
|
|
|
|
_screenDimensions = sd;
|
|
|
|
}
|
|
|
|
|
|
|
|
Stream<Input> rawInput() {
|
|
|
|
return _inputSource;
|
2023-09-10 03:26:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool _onPointerDown(PointerDownEvent me) {
|
2023-09-11 02:00:06 +00:00
|
|
|
final Button button;
|
2023-09-10 03:26:30 +00:00
|
|
|
if (me.buttons & kPrimaryMouseButton != 0) {
|
|
|
|
button = Button.left;
|
|
|
|
} else if (me.buttons & kSecondaryMouseButton != 0) {
|
|
|
|
button = Button.right;
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
var localPosition = me.localPosition;
|
|
|
|
final xy = _relativizeMouse(localPosition.dx, localPosition.dy);
|
2023-09-11 02:00:06 +00:00
|
|
|
_lastSeenMouse = xy;
|
2023-09-10 03:26:30 +00:00
|
|
|
if (xy != null) {
|
|
|
|
final (x, y) = xy;
|
2023-09-11 02:00:06 +00:00
|
|
|
_notifyInput(Click(button, x, y));
|
2023-09-10 03:26:30 +00:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool _onPointerExit(PointerExitEvent me) {
|
2023-09-11 02:00:06 +00:00
|
|
|
_lastSeenMouse = null;
|
2023-09-10 03:26:30 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool _onPointerHover(PointerHoverEvent me) {
|
|
|
|
var localPosition = me.localPosition;
|
2023-09-11 02:00:06 +00:00
|
|
|
_lastSeenMouse = _relativizeMouse(localPosition.dx, localPosition.dy);
|
2023-09-10 03:26:30 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
(int, int)? _relativizeMouse(double localX, double localY) {
|
2023-09-11 02:00:06 +00:00
|
|
|
final sd = _screenDimensions;
|
2023-09-10 03:26:30 +00:00
|
|
|
if (sd == null) return null;
|
|
|
|
if (sd.xSz == 0 || sd.ySz == 0) return null;
|
|
|
|
|
|
|
|
final tx = ((localX - sd.x0) * width) ~/ sd.xSz;
|
|
|
|
final ty = ((localY - sd.y0) * height) ~/ sd.ySz;
|
|
|
|
|
|
|
|
if (tx < 0 || tx >= width) return null;
|
|
|
|
if (ty < 0 || ty >= height) return null;
|
|
|
|
|
|
|
|
return (tx, ty);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class ScreenDimensions {
|
|
|
|
final double x0, y0, xSz, ySz;
|
|
|
|
|
|
|
|
const ScreenDimensions(
|
|
|
|
{required this.x0,
|
|
|
|
required this.y0,
|
|
|
|
required this.xSz,
|
|
|
|
required this.ySz});
|
2023-09-06 03:11:15 +00:00
|
|
|
}
|