Initial commit
This commit is contained in:
33
lib/assets.dart
Normal file
33
lib/assets.dart
Normal file
@ -0,0 +1,33 @@
|
||||
import 'dart:developer';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class Assets {
|
||||
final Map<String, ui.Image> _loaded = {};
|
||||
final Set<String> _waiting = {};
|
||||
|
||||
Future<ui.Image> getImageAsync(String name) async {
|
||||
final assetImageByteData = await rootBundle.load(name);
|
||||
final codec =
|
||||
await ui.instantiateImageCodec(assetImageByteData.buffer.asUint8List());
|
||||
final image = (await codec.getNextFrame()).image;
|
||||
|
||||
_loaded[name] = image;
|
||||
return image;
|
||||
}
|
||||
|
||||
ui.Image? getImageIfAvailable(String name) {
|
||||
if (!_waiting.contains(name)) {
|
||||
_waiting.add(name);
|
||||
getImageAsync(name);
|
||||
}
|
||||
return _loaded[name];
|
||||
}
|
||||
}
|
||||
|
||||
final assets = Assets();
|
||||
|
||||
ui.Image? getImageIfAvailable(String name) {
|
||||
return assets.getImageIfAvailable(name);
|
||||
}
|
307
lib/cp437.dart
Normal file
307
lib/cp437.dart
Normal file
@ -0,0 +1,307 @@
|
||||
typedef Cp437 = int;
|
||||
|
||||
bool initialized = false;
|
||||
final List<String> _fromCp437 = [
|
||||
"\u0000",
|
||||
"\u0001",
|
||||
"\u0002",
|
||||
"\u0003",
|
||||
"\u0004",
|
||||
"\u0005",
|
||||
"\u0006",
|
||||
"\u0007",
|
||||
"\b",
|
||||
"\t",
|
||||
"\n",
|
||||
"\u000b",
|
||||
"\f",
|
||||
"\r",
|
||||
"\u000e",
|
||||
"\u000f",
|
||||
"\u0010",
|
||||
"\u0011",
|
||||
"\u0012",
|
||||
"\u0013",
|
||||
"\u0014",
|
||||
"\u0015",
|
||||
"\u0016",
|
||||
"\u0017",
|
||||
"\u0018",
|
||||
"\u0019",
|
||||
"\u001a",
|
||||
"\u001b",
|
||||
"\u001c",
|
||||
"\u001d",
|
||||
"\u001e",
|
||||
"\u001f",
|
||||
" ",
|
||||
"!",
|
||||
"\"",
|
||||
"#",
|
||||
"\$",
|
||||
"%",
|
||||
"&",
|
||||
"'",
|
||||
"(",
|
||||
")",
|
||||
"*",
|
||||
"+",
|
||||
",",
|
||||
"-",
|
||||
".",
|
||||
"/",
|
||||
"0",
|
||||
"1",
|
||||
"2",
|
||||
"3",
|
||||
"4",
|
||||
"5",
|
||||
"6",
|
||||
"7",
|
||||
"8",
|
||||
"9",
|
||||
":",
|
||||
";",
|
||||
"<",
|
||||
"=",
|
||||
">",
|
||||
"?",
|
||||
"@",
|
||||
"A",
|
||||
"B",
|
||||
"C",
|
||||
"D",
|
||||
"E",
|
||||
"F",
|
||||
"G",
|
||||
"H",
|
||||
"I",
|
||||
"J",
|
||||
"K",
|
||||
"L",
|
||||
"M",
|
||||
"N",
|
||||
"O",
|
||||
"P",
|
||||
"Q",
|
||||
"R",
|
||||
"S",
|
||||
"T",
|
||||
"U",
|
||||
"V",
|
||||
"W",
|
||||
"X",
|
||||
"Y",
|
||||
"Z",
|
||||
"[",
|
||||
"\\",
|
||||
"]",
|
||||
"^",
|
||||
"_",
|
||||
"`",
|
||||
"a",
|
||||
"b",
|
||||
"c",
|
||||
"d",
|
||||
"e",
|
||||
"f",
|
||||
"g",
|
||||
"h",
|
||||
"i",
|
||||
"j",
|
||||
"k",
|
||||
"l",
|
||||
"m",
|
||||
"n",
|
||||
"o",
|
||||
"p",
|
||||
"q",
|
||||
"r",
|
||||
"s",
|
||||
"t",
|
||||
"u",
|
||||
"v",
|
||||
"w",
|
||||
"x",
|
||||
"y",
|
||||
"z",
|
||||
"{",
|
||||
"|",
|
||||
"}",
|
||||
"~",
|
||||
"\u007f",
|
||||
"\u00c7",
|
||||
"\u00fc",
|
||||
"\u00e9",
|
||||
"\u00e2",
|
||||
"\u00e4",
|
||||
"\u00e0",
|
||||
"\u00e5",
|
||||
"\u00e7",
|
||||
"\u00ea",
|
||||
"\u00eb",
|
||||
"\u00e8",
|
||||
"\u00ef",
|
||||
"\u00ee",
|
||||
"\u00ec",
|
||||
"\u00c4",
|
||||
"\u00c5",
|
||||
"\u00c9",
|
||||
"\u00e6",
|
||||
"\u00c6",
|
||||
"\u00f4",
|
||||
"\u00f6",
|
||||
"\u00f2",
|
||||
"\u00fb",
|
||||
"\u00f9",
|
||||
"\u00ff",
|
||||
"\u00d6",
|
||||
"\u00dc",
|
||||
"\u00a2",
|
||||
"\u00a3",
|
||||
"\u00a5",
|
||||
"\u20a7",
|
||||
"\u0192",
|
||||
"\u00e1",
|
||||
"\u00ed",
|
||||
"\u00f3",
|
||||
"\u00fa",
|
||||
"\u00f1",
|
||||
"\u00d1",
|
||||
"\u00aa",
|
||||
"\u00ba",
|
||||
"\u00bf",
|
||||
"\u2310",
|
||||
"\u00ac",
|
||||
"\u00bd",
|
||||
"\u00bc",
|
||||
"\u00a1",
|
||||
"\u00ab",
|
||||
"\u00bb",
|
||||
"\u2591",
|
||||
"\u2592",
|
||||
"\u2593",
|
||||
"\u2502",
|
||||
"\u2524",
|
||||
"\u2561",
|
||||
"\u2562",
|
||||
"\u2556",
|
||||
"\u2555",
|
||||
"\u2563",
|
||||
"\u2551",
|
||||
"\u2557",
|
||||
"\u255d",
|
||||
"\u255c",
|
||||
"\u255b",
|
||||
"\u2510",
|
||||
"\u2514",
|
||||
"\u2534",
|
||||
"\u252c",
|
||||
"\u251c",
|
||||
"\u2500",
|
||||
"\u253c",
|
||||
"\u255e",
|
||||
"\u255f",
|
||||
"\u255a",
|
||||
"\u2554",
|
||||
"\u2569",
|
||||
"\u2566",
|
||||
"\u2560",
|
||||
"\u2550",
|
||||
"\u256c",
|
||||
"\u2567",
|
||||
"\u2568",
|
||||
"\u2564",
|
||||
"\u2565",
|
||||
"\u2559",
|
||||
"\u2558",
|
||||
"\u2552",
|
||||
"\u2553",
|
||||
"\u256b",
|
||||
"\u256a",
|
||||
"\u2518",
|
||||
"\u250c",
|
||||
"\u2588",
|
||||
"\u2584",
|
||||
"\u258c",
|
||||
"\u2590",
|
||||
"\u2580",
|
||||
"\u03b1",
|
||||
"\u00df",
|
||||
"\u0393",
|
||||
"\u03c0",
|
||||
"\u03a3",
|
||||
"\u03c3",
|
||||
"\u00b5",
|
||||
"\u03c4",
|
||||
"\u03a6",
|
||||
"\u0398",
|
||||
"\u03a9",
|
||||
"\u03b4",
|
||||
"\u221e",
|
||||
"\u03c6",
|
||||
"\u03b5",
|
||||
"\u2229",
|
||||
"\u2261",
|
||||
"\u00b1",
|
||||
"\u2265",
|
||||
"\u2264",
|
||||
"\u2320",
|
||||
"\u2321",
|
||||
"\u00f7",
|
||||
"\u2248",
|
||||
"\u00b0",
|
||||
"\u2219",
|
||||
"\u00b7",
|
||||
"\u221a",
|
||||
"\u207f",
|
||||
"\u00b2",
|
||||
"\u25a0",
|
||||
"\u00a0"
|
||||
];
|
||||
final Map<String, Cp437> _toCp437 = {};
|
||||
|
||||
void _init() {
|
||||
if (initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (final (i, c) in _fromCp437.indexed) {
|
||||
_toCp437[c] = i;
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
String fromCp437Char(Cp437 c) {
|
||||
_init();
|
||||
if (c < 0 || c > _fromCp437.length) {
|
||||
return "?";
|
||||
}
|
||||
return _fromCp437[c];
|
||||
}
|
||||
|
||||
Cp437 toCp437Char(String c) {
|
||||
_init();
|
||||
var cp = _toCp437[c];
|
||||
if (cp == null) {
|
||||
return toCp437Char("?");
|
||||
}
|
||||
return cp;
|
||||
}
|
||||
|
||||
String fromCp437String(List<Cp437> s) {
|
||||
var out = "";
|
||||
for (final c in s) {
|
||||
out += fromCp437Char(c);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
List<Cp437> toCp437String(String s) {
|
||||
List<Cp437> out = [];
|
||||
for (final c in s.runes) {
|
||||
out.add(c);
|
||||
}
|
||||
return out;
|
||||
}
|
13
lib/fonts.dart
Normal file
13
lib/fonts.dart
Normal file
@ -0,0 +1,13 @@
|
||||
class Font {
|
||||
final String imageName;
|
||||
final int cellWidth, cellHeight;
|
||||
final int nCellsW, nCellsH;
|
||||
|
||||
const Font(this.imageName, this.cellWidth, this.cellHeight, this.nCellsW,
|
||||
this.nCellsH);
|
||||
|
||||
static const Font bg = Font("assets/images/fonts/font_bg.png", 1, 1, 1, 1);
|
||||
static const Font small =
|
||||
Font("assets/images/fonts/font_small.png", 1, 1, 16, 16);
|
||||
static const Font normal = Font("assets/images/fonts/font.png", 1, 2, 32, 8);
|
||||
}
|
79
lib/main.dart
Normal file
79
lib/main.dart
Normal file
@ -0,0 +1,79 @@
|
||||
import 'package:dartterm/terminal.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const MyApp());
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'DARTTERM',
|
||||
theme: ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.red),
|
||||
useMaterial3: true,
|
||||
),
|
||||
builder: (BuildContext context, Widget? child) {
|
||||
return MediaQuery(
|
||||
data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0),
|
||||
child: child!);
|
||||
},
|
||||
home: const MyHomePage(title: 'DARTTERM'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MyHomePage extends StatefulWidget {
|
||||
const MyHomePage({super.key, required this.title});
|
||||
|
||||
final String title;
|
||||
|
||||
@override
|
||||
State<MyHomePage> createState() => _MyHomePageState();
|
||||
}
|
||||
|
||||
class _MyHomePageState extends State<MyHomePage> {
|
||||
Ticker? ticker;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
ticker = Ticker(tick);
|
||||
ticker!.start();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
child:
|
||||
AspectRatio(aspectRatio: 16 / 9.0, child: terminal.toWidget(context)),
|
||||
);
|
||||
}
|
||||
|
||||
void tick(Duration elapsed) {
|
||||
setState(() {/* state changed, force a redraw */});
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
class TerminalCustomPainter extends CustomPainter {
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final paint = Paint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 4.0
|
||||
..color = Colors.indigo;
|
||||
|
||||
canvas.drawRect(
|
||||
Rect.fromLTWH(-20 + Random().nextDouble() * 20 - 10, -20, 40, 40),
|
||||
paint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(TerminalCustomPainter oldDelegate) => true;
|
||||
}
|
||||
*/
|
126
lib/main.dart.old
Normal file
126
lib/main.dart.old
Normal file
@ -0,0 +1,126 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const MyApp());
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({super.key});
|
||||
|
||||
// This widget is the root of your application.
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'Flutter Demo',
|
||||
theme: ThemeData(
|
||||
// This is the theme of your application.
|
||||
//
|
||||
// TRY THIS: Try running your application with "flutter run". You'll see
|
||||
// the application has a blue toolbar. Then, without quitting the app,
|
||||
// try changing the seedColor in the colorScheme below to Colors.green
|
||||
// and then invoke "hot reload" (save your changes or press the "hot
|
||||
// reload" button in a Flutter-supported IDE, or press "r" if you used
|
||||
// the command line to start the app).
|
||||
//
|
||||
// Notice that the counter didn't reset back to zero; the application
|
||||
// state is not lost during the reload. To reset the state, use hot
|
||||
// restart instead.
|
||||
//
|
||||
// This works for code too, not just values: Most code changes can be
|
||||
// tested with just a hot reload.
|
||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
|
||||
useMaterial3: true,
|
||||
),
|
||||
home: const MyHomePage(title: 'Flutter Demo Home Page'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MyHomePage extends StatefulWidget {
|
||||
const MyHomePage({super.key, required this.title});
|
||||
|
||||
// This widget is the home page of your application. It is stateful, meaning
|
||||
// that it has a State object (defined below) that contains fields that affect
|
||||
// how it looks.
|
||||
|
||||
// This class is the configuration for the state. It holds the values (in this
|
||||
// case the title) provided by the parent (in this case the App widget) and
|
||||
// used by the build method of the State. Fields in a Widget subclass are
|
||||
// always marked "final".
|
||||
|
||||
final String title;
|
||||
|
||||
@override
|
||||
State<MyHomePage> createState() => _MyHomePageState();
|
||||
}
|
||||
|
||||
class _MyHomePageState extends State<MyHomePage> {
|
||||
int _counter = 0;
|
||||
|
||||
void _incrementCounter() {
|
||||
setState(() {
|
||||
// This call to setState tells the Flutter framework that something has
|
||||
// changed in this State, which causes it to rerun the build method below
|
||||
// so that the display can reflect the updated values. If we changed
|
||||
// _counter without calling setState(), then the build method would not be
|
||||
// called again, and so nothing would appear to happen.
|
||||
_counter++;
|
||||
});
|
||||
}
|
||||
|
||||
@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 Scaffold(
|
||||
appBar: AppBar(
|
||||
// TRY THIS: Try changing the color here to a specific color (to
|
||||
// Colors.amber, perhaps?) and trigger a hot reload to see the AppBar
|
||||
// change color while the other colors stay the same.
|
||||
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
|
||||
// backgroundColor: Colors.amber,
|
||||
// Here we take the value from the MyHomePage object that was created by
|
||||
// the App.build method, and use it to set our appbar title.
|
||||
title: Text(widget.title),
|
||||
),
|
||||
body: Center(
|
||||
// Center is a layout widget. It takes a single child and positions it
|
||||
// in the middle of the parent.
|
||||
child: Column(
|
||||
// Column is also a layout widget. It takes a list of children and
|
||||
// arranges them vertically. By default, it sizes itself to fit its
|
||||
// children horizontally, and tries to be as tall as its parent.
|
||||
//
|
||||
// Column has various properties to control how it sizes itself and
|
||||
// how it positions its children. Here we use mainAxisAlignment to
|
||||
// center the children vertically; the main axis here is the vertical
|
||||
// axis because Columns are vertical (the cross axis would be
|
||||
// horizontal).
|
||||
//
|
||||
// TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint"
|
||||
// action in the IDE, or press "p" in the console), to see the
|
||||
// wireframe for each widget.
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
const Text(
|
||||
'You have pushed the button this many times:',
|
||||
),
|
||||
Text(
|
||||
'$_counter',
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: _incrementCounter,
|
||||
tooltip: 'Increment',
|
||||
child: const Icon(Icons.add),
|
||||
), // This trailing comma makes auto-formatting nicer for build methods.
|
||||
);
|
||||
}
|
||||
}
|
87
lib/terminal.dart
Normal file
87
lib/terminal.dart
Normal file
@ -0,0 +1,87 @@
|
||||
import 'package:dartterm/cp437.dart';
|
||||
import 'package:dartterm/fonts.dart';
|
||||
import 'package:dartterm/terminal_painter.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class Terminal {
|
||||
static const int width = 96;
|
||||
static const int height = 54;
|
||||
static const int cellW = 64;
|
||||
static const int cellH = 64;
|
||||
static const int nTiles = width * height;
|
||||
|
||||
late List<Tile> tiles;
|
||||
|
||||
Terminal() {
|
||||
tiles = [for (var i = 0; i < nTiles; i += 1) Tile.empty()];
|
||||
for (final (x, c) in toCp437String("Hello, world!").indexed) {
|
||||
tiles[x].content = Content(Font.normal.imageName, c % 32, 2 * (c ~/ 32));
|
||||
tiles[x + Terminal.width].content =
|
||||
Content(Font.normal.imageName, c % 32, 2 * (c ~/ 32) + 1);
|
||||
}
|
||||
|
||||
for (final (x, c) in toCp437String("BEWARE OF THE BAT!").indexed) {
|
||||
tiles[x + Terminal.width * 2].content =
|
||||
Content(Font.small.imageName, c % 16, c ~/ 16);
|
||||
}
|
||||
|
||||
for (final (x, c) in toCp437String("BEWARE OF THE BAT!").indexed) {
|
||||
tiles[Terminal.width * (x + 2)].content =
|
||||
Content(Font.small.imageName, c % 16, c ~/ 16);
|
||||
}
|
||||
}
|
||||
|
||||
CustomPaint toWidget(BuildContext context) {
|
||||
var scalingFactor = MediaQuery.devicePixelRatioOf(context);
|
||||
return CustomPaint(painter: TerminalCustomPainter(this, scalingFactor));
|
||||
}
|
||||
|
||||
(int, int)? toXY(int i) {
|
||||
if (i < 0 || i > nTiles) {
|
||||
return null;
|
||||
}
|
||||
return (i % width, i ~/ width);
|
||||
}
|
||||
|
||||
int? fromXY(int x, int y) {
|
||||
if (x < 0 || x >= width) {
|
||||
return null;
|
||||
}
|
||||
if (y < 0 || y >= height) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return x + y * width;
|
||||
}
|
||||
}
|
||||
|
||||
class Tile {
|
||||
Content? content;
|
||||
|
||||
Color bg;
|
||||
Color fg;
|
||||
|
||||
Tile(this.content, this.bg, this.fg);
|
||||
|
||||
static Tile empty() => Tile(null, Colors.black, Colors.white);
|
||||
}
|
||||
|
||||
class Content {
|
||||
final String sourceImage;
|
||||
final int sourceCx;
|
||||
final int sourceCy;
|
||||
|
||||
const Content(this.sourceImage, this.sourceCx, this.sourceCy);
|
||||
}
|
||||
|
||||
// reexports
|
||||
Terminal terminal = Terminal();
|
||||
const int width = Terminal.width;
|
||||
const int height = Terminal.height;
|
||||
const int cellW = Terminal.cellW;
|
||||
const int cellH = Terminal.cellH;
|
||||
const int nTiles = Terminal.nTiles;
|
||||
|
||||
Widget toWidget(BuildContext context) {
|
||||
return terminal.toWidget(context);
|
||||
}
|
154
lib/terminal_painter.dart
Normal file
154
lib/terminal_painter.dart
Normal file
@ -0,0 +1,154 @@
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:dartterm/assets.dart';
|
||||
import 'package:dartterm/fonts.dart';
|
||||
import 'package:dartterm/terminal.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class TerminalCustomPainter extends CustomPainter {
|
||||
Terminal t;
|
||||
double scalingFactor;
|
||||
|
||||
TerminalCustomPainter(this.t, this.scalingFactor);
|
||||
|
||||
@override
|
||||
bool shouldRepaint(TerminalCustomPainter oldDelegate) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final paint = Paint()..filterQuality = FilterQuality.none;
|
||||
|
||||
// == Fill backdrop ==
|
||||
final paint2 = Paint()
|
||||
..color = Colors.black
|
||||
..style = PaintingStyle.fill;
|
||||
canvas.drawRect(const Offset(0, 0) & size, paint2);
|
||||
|
||||
// == Estimate dimensions for scaling ==
|
||||
var sx = size.width / Terminal.width;
|
||||
var sy = size.height / Terminal.height;
|
||||
var dpx = 0.0;
|
||||
var dpy = 0.0;
|
||||
var invScalingFactor = 1 / scalingFactor;
|
||||
|
||||
for (double preferredSx in [64, 56, 48, 40, 32, 24, 16, 8]) {
|
||||
preferredSx *= invScalingFactor;
|
||||
var preferredSy = preferredSx;
|
||||
if (preferredSx < sx && preferredSy < sy) {
|
||||
sx = preferredSx.toDouble();
|
||||
sy = preferredSy.toDouble();
|
||||
dpx = ((size.width - (sx * Terminal.width)) / 2);
|
||||
dpy = ((size.height - (sy * Terminal.height)) / 2);
|
||||
|
||||
dpx /= invScalingFactor;
|
||||
dpx = dpx.floorToDouble();
|
||||
dpx *= invScalingFactor;
|
||||
|
||||
dpy /= invScalingFactor;
|
||||
dpy = dpy.floorToDouble();
|
||||
dpy *= invScalingFactor;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < nTiles; i++) {
|
||||
final tile = t.tiles[i];
|
||||
|
||||
final (pcxDst, pcyDst) = terminal.toXY(i) ?? (0, 0);
|
||||
final (pxDst, pyDst) = (dpx + pcxDst * sx, dpy + pcyDst * sy);
|
||||
final rectDst = Rect.fromLTWH(pxDst, pyDst, sx, sy);
|
||||
|
||||
var content = tile.content;
|
||||
if (content != null) {
|
||||
var source = content.sourceImage;
|
||||
var image = assets.getImageIfAvailable(source);
|
||||
if (image != null) {
|
||||
var fgRect = Rect.fromLTWH(
|
||||
content.sourceCx.toDouble() * cellW,
|
||||
content.sourceCy.toDouble() * cellH,
|
||||
cellW.toDouble(),
|
||||
cellH.toDouble());
|
||||
canvas.drawImageRect(
|
||||
image,
|
||||
fgRect,
|
||||
rectDst,
|
||||
paint,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var todos = Todos();
|
||||
|
||||
// == Draw the background and foreground of every tile ==
|
||||
for (var i = 0; i < nTiles; i++) {
|
||||
final (pcxDst, pcyDst) = terminal.toXY(i) ?? (0, 0);
|
||||
final (pxDst, pyDst) = (dpx + pcxDst * sx, dpy + pcyDst * sy);
|
||||
final rectDst = Rect.fromLTWH(pxDst, pyDst, sx, sy);
|
||||
|
||||
final tile = t.tiles[i];
|
||||
|
||||
var bgRect = Rect.fromLTWH(0, 0, cellW.toDouble(), cellH.toDouble());
|
||||
todos.add(Font.bg.imageName, bgRect, rectDst, tile.bg);
|
||||
|
||||
var c = tile.content;
|
||||
if (c != null) {
|
||||
var fgRect = Rect.fromLTWH(c.sourceCx.toDouble() * cellW,
|
||||
c.sourceCy.toDouble() * cellH, cellW.toDouble(), cellH.toDouble());
|
||||
todos.add(c.sourceImage, fgRect, rectDst, tile.fg);
|
||||
}
|
||||
}
|
||||
|
||||
for (var t in todos.imageTodos) {
|
||||
final atlas = t.atlas;
|
||||
if (atlas != null) {
|
||||
canvas.drawAtlas(atlas, t.transforms, t.rects, t.colors,
|
||||
BlendMode.modulate, null, paint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Todos {
|
||||
List<ImageTodos> imageTodos = [];
|
||||
Map<String, int> imageTodoIx = {};
|
||||
|
||||
void add(String source, Rect src, Rect dst, Color color) {
|
||||
var ix = imageTodoIx[source];
|
||||
if (ix == null) {
|
||||
ix = imageTodos.length;
|
||||
var image = assets.getImageIfAvailable(source);
|
||||
imageTodos.add(ImageTodos(image));
|
||||
imageTodoIx[source] = ix;
|
||||
}
|
||||
imageTodos[ix].add(src, dst, color);
|
||||
}
|
||||
}
|
||||
|
||||
class ImageTodos {
|
||||
ui.Image? atlas;
|
||||
List<RSTransform> transforms = [];
|
||||
List<Rect> rects = [];
|
||||
List<Color> colors = [];
|
||||
|
||||
ImageTodos(this.atlas);
|
||||
|
||||
void add(Rect src, Rect dst, Color color) {
|
||||
// NOTE: Because main.dart uses aspectRatio, scaleX and scaleY should be close to the same
|
||||
var scaleX = dst.width / src.width;
|
||||
|
||||
transforms.add(RSTransform.fromComponents(
|
||||
rotation: 0.0,
|
||||
scale: scaleX,
|
||||
anchorX: 0.0,
|
||||
anchorY: 0.0,
|
||||
translateX: dst.left,
|
||||
translateY: dst.top,
|
||||
));
|
||||
rects.add(src);
|
||||
colors.add(color);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user