Basic menuing
This commit is contained in:
parent
969089eee5
commit
8b8006d4cb
@ -1,20 +1,37 @@
|
|||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
import 'package:dartterm/colors.dart';
|
import 'package:dartterm/colors.dart';
|
||||||
import 'package:dartterm/terminal.dart';
|
import 'package:dartterm/terminal.dart';
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
at(0, 0).puts("Hello, bats!");
|
var descriptor = "generic";
|
||||||
at(0, 2).fg(Palette.subtitle).small().puts("Beware of the bat!");
|
while (true) {
|
||||||
at(4, 4)
|
at(0, 0).clear();
|
||||||
.bg(Palette.subtitle)
|
at(0, 0).puts("Hello, bats!");
|
||||||
.fg(Palette.defaultBg)
|
at(0, 2).fg(Palette.subtitle).small().puts("Beware of the bat!");
|
||||||
.big()
|
at(4, 4)
|
||||||
.puts("BEWARE OF THE BAT!\nA cool bat!");
|
.bg(Palette.subtitle)
|
||||||
|
.fg(Palette.defaultBg)
|
||||||
|
.big()
|
||||||
|
.highlight()
|
||||||
|
.act(Act(
|
||||||
|
isDefault: true,
|
||||||
|
label: "Strong!",
|
||||||
|
callback: () async {
|
||||||
|
log("strong!");
|
||||||
|
descriptor = "strong";
|
||||||
|
}))
|
||||||
|
.act(Act(
|
||||||
|
label: "Nocturnal!",
|
||||||
|
callback: () async {
|
||||||
|
log("nocturnal!");
|
||||||
|
descriptor = "nocturnal";
|
||||||
|
}))
|
||||||
|
.puts("ALTER BAT");
|
||||||
|
|
||||||
await zzz(1.0);
|
at(4, 8).normal().puts("A $descriptor bat!");
|
||||||
|
|
||||||
at(4, 8)
|
// await zzz(1.0);
|
||||||
.bg(Palette.subtitle)
|
await waitMenu();
|
||||||
.fg(Palette.defaultBg)
|
}
|
||||||
.big()
|
|
||||||
.puts("A strong bat!");
|
|
||||||
}
|
}
|
||||||
|
36
lib/inp.dart
Normal file
36
lib/inp.dart
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
import 'package:dartterm/terminal.dart' as terminal;
|
||||||
|
import 'package:dartterm/terminal.dart';
|
||||||
|
|
||||||
|
typedef Callback = Future<void> Function();
|
||||||
|
|
||||||
|
class Inp {
|
||||||
|
final Map<int, List<Act>> _regions = {};
|
||||||
|
|
||||||
|
Inp();
|
||||||
|
}
|
||||||
|
|
||||||
|
class Act {
|
||||||
|
Inp owner;
|
||||||
|
String label;
|
||||||
|
Callback callback;
|
||||||
|
bool isDefault;
|
||||||
|
|
||||||
|
Act(
|
||||||
|
{required this.owner,
|
||||||
|
required this.label,
|
||||||
|
required this.callback,
|
||||||
|
this.isDefault = false});
|
||||||
|
|
||||||
|
Cursor at(int x, int y) {
|
||||||
|
return terminal.at(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void putAt(int i) {
|
||||||
|
if (owner._regions[i] == null) {
|
||||||
|
owner._regions[i] = [];
|
||||||
|
}
|
||||||
|
(owner._regions[i]!).add(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
@ -1,25 +1,44 @@
|
|||||||
|
import 'dart:async';
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
|
import 'dart:math' as math;
|
||||||
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
|
import 'package:dartterm/assets.dart';
|
||||||
import 'package:dartterm/colors.dart';
|
import 'package:dartterm/colors.dart';
|
||||||
import 'package:dartterm/cp437.dart';
|
import 'package:dartterm/cp437.dart';
|
||||||
import 'package:dartterm/fonts.dart';
|
import 'package:dartterm/fonts.dart';
|
||||||
import 'package:dartterm/input.dart';
|
import 'package:dartterm/input.dart';
|
||||||
import 'package:dartterm/terminal_painter.dart';
|
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
class Terminal {
|
class Terminal {
|
||||||
static const int width = 88;
|
static const int width = 88;
|
||||||
static const int height = 50;
|
static const int height = 50;
|
||||||
static const int nTiles = width * height;
|
static const int nTiles = width * height;
|
||||||
|
|
||||||
late List<Tile> tiles;
|
late List<Tile> _tiles;
|
||||||
(int, int)? lastSeenMouse;
|
(int, int)? _lastSeenMouse;
|
||||||
ScreenDimensions? screenDimensions;
|
ScreenDimensions? _screenDimensions;
|
||||||
List<Input> inputEvents = [];
|
|
||||||
|
final StreamController<Input> _inputSink =
|
||||||
|
StreamController<Input>.broadcast();
|
||||||
|
late final Stream<Input> _inputSource = _inputSink.stream.asBroadcastStream();
|
||||||
|
// ignore: prefer_final_fields
|
||||||
|
_State _state = _State.baseState;
|
||||||
|
|
||||||
Terminal() {
|
Terminal() {
|
||||||
tiles = [for (var i = 0; i < nTiles; i += 1) Tile.empty()];
|
_tiles = [for (var i = 0; i < nTiles; i += 1) Tile()];
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget toWidget(BuildContext context) {
|
Widget toWidget(BuildContext context) {
|
||||||
@ -33,14 +52,14 @@ class Terminal {
|
|||||||
painter: TerminalCustomPainter(this, scalingFactor))));
|
painter: TerminalCustomPainter(this, scalingFactor))));
|
||||||
}
|
}
|
||||||
|
|
||||||
(int, int)? toXY(int i) {
|
(int, int)? _toXY(int i) {
|
||||||
if (i < 0 || i > nTiles) {
|
if (i < 0 || i > nTiles) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (i % width, i ~/ width);
|
return (i % width, i ~/ width);
|
||||||
}
|
}
|
||||||
|
|
||||||
int? fromXY(int x, int y) {
|
int? _fromXY(int x, int y) {
|
||||||
if (x < 0 || x >= width) {
|
if (x < 0 || x >= width) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -51,18 +70,21 @@ class Terminal {
|
|||||||
return x + y * width;
|
return x + y * width;
|
||||||
}
|
}
|
||||||
|
|
||||||
void notifyInput(Input i) {
|
void _notifyInput(Input i) {
|
||||||
// TODO: Handle it
|
log("Input: $i $_lastSeenMouse");
|
||||||
log("Input: $i $lastSeenMouse");
|
_inputSink.add(i);
|
||||||
inputEvents.add(i);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void notifyScreenDimensions(ScreenDimensions sd) {
|
void _notifyScreenDimensions(ScreenDimensions sd) {
|
||||||
screenDimensions = sd;
|
_screenDimensions = sd;
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<Input> rawInput() {
|
||||||
|
return _inputSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _onPointerDown(PointerDownEvent me) {
|
bool _onPointerDown(PointerDownEvent me) {
|
||||||
final button;
|
final Button button;
|
||||||
if (me.buttons & kPrimaryMouseButton != 0) {
|
if (me.buttons & kPrimaryMouseButton != 0) {
|
||||||
button = Button.left;
|
button = Button.left;
|
||||||
} else if (me.buttons & kSecondaryMouseButton != 0) {
|
} else if (me.buttons & kSecondaryMouseButton != 0) {
|
||||||
@ -73,27 +95,27 @@ class Terminal {
|
|||||||
|
|
||||||
var localPosition = me.localPosition;
|
var localPosition = me.localPosition;
|
||||||
final xy = _relativizeMouse(localPosition.dx, localPosition.dy);
|
final xy = _relativizeMouse(localPosition.dx, localPosition.dy);
|
||||||
lastSeenMouse = xy;
|
_lastSeenMouse = xy;
|
||||||
if (xy != null) {
|
if (xy != null) {
|
||||||
final (x, y) = xy;
|
final (x, y) = xy;
|
||||||
notifyInput(Click(button, x, y));
|
_notifyInput(Click(button, x, y));
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _onPointerExit(PointerExitEvent me) {
|
bool _onPointerExit(PointerExitEvent me) {
|
||||||
lastSeenMouse = null;
|
_lastSeenMouse = null;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _onPointerHover(PointerHoverEvent me) {
|
bool _onPointerHover(PointerHoverEvent me) {
|
||||||
var localPosition = me.localPosition;
|
var localPosition = me.localPosition;
|
||||||
lastSeenMouse = _relativizeMouse(localPosition.dx, localPosition.dy);
|
_lastSeenMouse = _relativizeMouse(localPosition.dx, localPosition.dy);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
(int, int)? _relativizeMouse(double localX, double localY) {
|
(int, int)? _relativizeMouse(double localX, double localY) {
|
||||||
final sd = screenDimensions;
|
final sd = _screenDimensions;
|
||||||
if (sd == null) return null;
|
if (sd == null) return null;
|
||||||
if (sd.xSz == 0 || sd.ySz == 0) return null;
|
if (sd.xSz == 0 || sd.ySz == 0) return null;
|
||||||
|
|
||||||
@ -116,138 +138,3 @@ class ScreenDimensions {
|
|||||||
required this.xSz,
|
required this.xSz,
|
||||||
required this.ySz});
|
required this.ySz});
|
||||||
}
|
}
|
||||||
|
|
||||||
class Cursor {
|
|
||||||
final Terminal t;
|
|
||||||
int x, y;
|
|
||||||
Color? _bg, _fg;
|
|
||||||
Font font = Font.normal;
|
|
||||||
|
|
||||||
// TODO: Clip
|
|
||||||
|
|
||||||
Cursor({required this.t, required this.x, required this.y});
|
|
||||||
|
|
||||||
void clear() {
|
|
||||||
t.tiles = [for (var i = 0; i < nTiles; i += 1) Tile.empty()];
|
|
||||||
}
|
|
||||||
|
|
||||||
void putc(int c) {
|
|
||||||
for (var dx = 0; dx < font.cellWidth; dx += 1) {
|
|
||||||
for (var dy = 0; dy < font.cellHeight; dy += 1) {
|
|
||||||
var i = t.fromXY(x + dx, y + dy);
|
|
||||||
|
|
||||||
if (i != null) {
|
|
||||||
final (sourceCx, sourceCy) = (
|
|
||||||
(c % font.nCellsW) * font.cellWidth + dx,
|
|
||||||
(c ~/ font.nCellsW) * font.cellHeight + dy
|
|
||||||
);
|
|
||||||
|
|
||||||
t.tiles[i].content = Content(font.imageName, sourceCx, sourceCy);
|
|
||||||
|
|
||||||
final (bg, fg) = (_bg, _fg);
|
|
||||||
if (bg != null) {
|
|
||||||
t.tiles[i].bg = bg;
|
|
||||||
}
|
|
||||||
if (fg != null) {
|
|
||||||
t.tiles[i].fg = fg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void puts(String s) {
|
|
||||||
var startX = x;
|
|
||||||
for (final c in toCp437String(s)) {
|
|
||||||
if (c == 10) {
|
|
||||||
// newline
|
|
||||||
x = startX;
|
|
||||||
y += font.cellHeight;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (c == 13) {
|
|
||||||
// carriage return
|
|
||||||
x = startX;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
putc(c);
|
|
||||||
x += font.cellWidth;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Cursor fg(Color c) {
|
|
||||||
_fg = c;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
Cursor bg(Color c) {
|
|
||||||
_bg = c;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
Cursor small() {
|
|
||||||
font = Font.small;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
Cursor normal() {
|
|
||||||
font = Font.normal;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
Cursor big() {
|
|
||||||
font = Font.big;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Clip {}
|
|
||||||
|
|
||||||
class Tile {
|
|
||||||
Content? content;
|
|
||||||
|
|
||||||
Color bg;
|
|
||||||
Color fg;
|
|
||||||
|
|
||||||
Tile(this.content, this.bg, this.fg);
|
|
||||||
|
|
||||||
static Tile empty() => Tile(null, Palette.defaultBg, Palette.defaultFg);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 nTiles = Terminal.nTiles;
|
|
||||||
|
|
||||||
Widget toWidget(BuildContext context) {
|
|
||||||
return _terminal.toWidget(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
void notifyInput(Input i) {
|
|
||||||
_terminal.notifyInput(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
void notifyScreenDimensions(ScreenDimensions sd) {
|
|
||||||
_terminal.notifyScreenDimensions(sd);
|
|
||||||
}
|
|
||||||
|
|
||||||
void clear() {
|
|
||||||
at(0, 0).clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
Cursor at(int x, int y) {
|
|
||||||
return Cursor(t: _terminal, x: x, y: y);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> zzz(double t) async {
|
|
||||||
await Future.delayed(Duration(milliseconds: (t * 1000).toInt()));
|
|
||||||
}
|
|
||||||
|
177
lib/terminal/action_oriented.dart
Normal file
177
lib/terminal/action_oriented.dart
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
part of '../terminal.dart';
|
||||||
|
|
||||||
|
typedef Callback = Future<void> Function();
|
||||||
|
|
||||||
|
class Act {
|
||||||
|
String label;
|
||||||
|
Callback callback;
|
||||||
|
|
||||||
|
// If true, then push this to the top of the menu.
|
||||||
|
// Note that if multiple actions are isDefault, then they just get a section of their own
|
||||||
|
bool isDefault;
|
||||||
|
|
||||||
|
// If true, then left clicking can trigger this.
|
||||||
|
// If false, then the user must _always_ right click.
|
||||||
|
bool canBeDefaultedTo;
|
||||||
|
|
||||||
|
// If true, then right clicking can open a menu where this is the only item.
|
||||||
|
// (Ordinary command buttons will never be interesting.)
|
||||||
|
// Note that if canBeDefaultedTo is false and yet this is the only item,
|
||||||
|
//then isInteresting will be ignored.
|
||||||
|
bool isInteresting;
|
||||||
|
|
||||||
|
Act(
|
||||||
|
{required this.label,
|
||||||
|
required this.callback,
|
||||||
|
this.isDefault = false,
|
||||||
|
this.canBeDefaultedTo = true,
|
||||||
|
this.isInteresting = true});
|
||||||
|
}
|
||||||
|
|
||||||
|
int _compareAct(Act a1, Act a2) {
|
||||||
|
// compare on defaultness
|
||||||
|
final dl1 = a1.isDefault ? 1 : 0;
|
||||||
|
final dl2 = a2.isDefault ? 1 : 0;
|
||||||
|
final compare0 = dl1.compareTo(dl2);
|
||||||
|
if (compare0 != 0) {
|
||||||
|
return -compare0; // higher defaulting levels go towards the top
|
||||||
|
}
|
||||||
|
|
||||||
|
// compare on label
|
||||||
|
return a1.label.compareTo(a2.label);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _waitMenu(Terminal t, bool modal) async {
|
||||||
|
var oldState = t._state;
|
||||||
|
t._state = _State.waitMenu;
|
||||||
|
loop:
|
||||||
|
await for (var inp in t.rawInput()) {
|
||||||
|
switch (inp) {
|
||||||
|
case Click(button: var btn, x: var x, y: var y):
|
||||||
|
final i = t._fromXY(x, y);
|
||||||
|
List<Act> acts = [];
|
||||||
|
if (i != null) {
|
||||||
|
final tile = t._tiles[i];
|
||||||
|
acts = tile.actions;
|
||||||
|
|
||||||
|
if (!modal && acts.isEmpty) {
|
||||||
|
break loop;
|
||||||
|
} // if not modal, then skip out on clicking an empty tile
|
||||||
|
}
|
||||||
|
|
||||||
|
var menu = _Menu(acts, x, y);
|
||||||
|
|
||||||
|
switch (btn) {
|
||||||
|
case Button.left:
|
||||||
|
var act = menu.defaultAct;
|
||||||
|
if (act != null) {
|
||||||
|
await act.callback();
|
||||||
|
break loop;
|
||||||
|
}
|
||||||
|
case Button.right:
|
||||||
|
if (menu.canShow) {
|
||||||
|
await _showMenu(t, menu);
|
||||||
|
break loop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t._state = oldState;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _showMenu(Terminal t, _Menu menu) async {
|
||||||
|
// First of all, save the old state of the view
|
||||||
|
var oldTiles = t._tiles;
|
||||||
|
t._tiles = [for (Tile tile in oldTiles) tile.fadeDisable()];
|
||||||
|
|
||||||
|
menu.draw();
|
||||||
|
await _waitMenu(t, false);
|
||||||
|
t._tiles = oldTiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _Menu {
|
||||||
|
List<Act> acts = [];
|
||||||
|
Act? defaultAct;
|
||||||
|
bool canShow = false;
|
||||||
|
|
||||||
|
int displayX = 0;
|
||||||
|
int displayY = 0;
|
||||||
|
int displayWidth = 0;
|
||||||
|
int displayHeight = 0;
|
||||||
|
|
||||||
|
_Menu(List<Act> a, int mouseX, int mouseY) {
|
||||||
|
// == compute act list ==
|
||||||
|
acts.addAll(a);
|
||||||
|
acts.sort(_compareAct);
|
||||||
|
|
||||||
|
// == compute defaulting status ==
|
||||||
|
var hasDefaulting = false;
|
||||||
|
|
||||||
|
if (acts.isEmpty) {
|
||||||
|
hasDefaulting = false;
|
||||||
|
canShow = false;
|
||||||
|
} else if (acts.length == 1) {
|
||||||
|
hasDefaulting = acts[0].canBeDefaultedTo;
|
||||||
|
if (acts[0].isInteresting || !hasDefaulting) {
|
||||||
|
canShow = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var def1 = acts[0].isDefault;
|
||||||
|
hasDefaulting = acts[0].canBeDefaultedTo;
|
||||||
|
canShow = true;
|
||||||
|
for (var i = 1; hasDefaulting && i < acts.length; i++) {
|
||||||
|
if (acts[i].isDefault == def1) {
|
||||||
|
hasDefaulting = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasDefaulting) {
|
||||||
|
defaultAct = acts[0];
|
||||||
|
}
|
||||||
|
// == compute dimensions ==
|
||||||
|
final (int h, int w) = (Font.normal.cellHeight, Font.normal.cellWidth);
|
||||||
|
displayHeight = h * 2;
|
||||||
|
for (var a in acts) {
|
||||||
|
displayWidth = math.max(displayWidth, (a.label.length + 4) * w);
|
||||||
|
displayHeight += h;
|
||||||
|
}
|
||||||
|
|
||||||
|
displayX = mouseX - w * 2;
|
||||||
|
displayY = mouseY - h;
|
||||||
|
// TODO: Do this with math
|
||||||
|
while (displayX + displayWidth > Terminal.width) {
|
||||||
|
displayX -= 1;
|
||||||
|
}
|
||||||
|
while (displayY + displayHeight > Terminal.height) {
|
||||||
|
displayY -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void draw() {
|
||||||
|
final (int h, int w) = (Font.normal.cellHeight, Font.normal.cellWidth);
|
||||||
|
for (var y = 0; y < displayHeight; y += h) {
|
||||||
|
for (var x = 0; x < displayWidth; x += w) {
|
||||||
|
at(displayX + x, displayY + y)
|
||||||
|
.bg(Palette.defaultBg)
|
||||||
|
.fg(Palette.defaultFg)
|
||||||
|
.putc(0xb0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < acts.length; i++) {
|
||||||
|
at(displayX + w * 2, displayY + (i + 1) * h)
|
||||||
|
.bg(Palette.defaultBg)
|
||||||
|
.fg(Palette.defaultFg)
|
||||||
|
.highlight()
|
||||||
|
.act(Act(
|
||||||
|
label: acts[i].label,
|
||||||
|
callback: acts[i].callback,
|
||||||
|
isInteresting: false))
|
||||||
|
.puts(acts[i].label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
105
lib/terminal/cursor.dart
Normal file
105
lib/terminal/cursor.dart
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
part of '../terminal.dart';
|
||||||
|
|
||||||
|
var _nextHighlightGroup = 0;
|
||||||
|
|
||||||
|
class Cursor {
|
||||||
|
final Terminal t;
|
||||||
|
int x, y;
|
||||||
|
Color? _bg, _fg;
|
||||||
|
Font font = Font.normal;
|
||||||
|
int? highlightGroup;
|
||||||
|
final List<Act> _acts = [];
|
||||||
|
|
||||||
|
// TODO: Clip
|
||||||
|
|
||||||
|
Cursor({required this.t, required this.x, required this.y});
|
||||||
|
|
||||||
|
void clear() {
|
||||||
|
t._tiles = [for (var i = 0; i < nTiles; i += 1) Tile()];
|
||||||
|
}
|
||||||
|
|
||||||
|
void putc(int c) {
|
||||||
|
for (var dx = 0; dx < font.cellWidth; dx += 1) {
|
||||||
|
for (var dy = 0; dy < font.cellHeight; dy += 1) {
|
||||||
|
var i = t._fromXY(x + dx, y + dy);
|
||||||
|
|
||||||
|
if (i != null) {
|
||||||
|
final (sourceCx, sourceCy) = (
|
||||||
|
(c % font.nCellsW) * font.cellWidth + dx,
|
||||||
|
(c ~/ font.nCellsW) * font.cellHeight + dy
|
||||||
|
);
|
||||||
|
|
||||||
|
t._tiles[i].content = Content(font.imageName, sourceCx, sourceCy);
|
||||||
|
|
||||||
|
final (bg, fg) = (_bg, _fg);
|
||||||
|
if (bg != null) {
|
||||||
|
t._tiles[i].bg = bg;
|
||||||
|
}
|
||||||
|
if (fg != null) {
|
||||||
|
t._tiles[i].fg = fg;
|
||||||
|
}
|
||||||
|
if (highlightGroup != null) {
|
||||||
|
t._tiles[i].highlightGroup = highlightGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
t._tiles[i].actions.addAll(_acts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void puts(String s) {
|
||||||
|
var startX = x;
|
||||||
|
for (final c in toCp437String(s)) {
|
||||||
|
if (c == 10) {
|
||||||
|
// newline
|
||||||
|
x = startX;
|
||||||
|
y += font.cellHeight;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (c == 13) {
|
||||||
|
// carriage return
|
||||||
|
x = startX;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
putc(c);
|
||||||
|
x += font.cellWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Cursor fg(Color c) {
|
||||||
|
_fg = c;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Cursor bg(Color c) {
|
||||||
|
_bg = c;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Cursor small() {
|
||||||
|
font = Font.small;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Cursor normal() {
|
||||||
|
font = Font.normal;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Cursor big() {
|
||||||
|
font = Font.big;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Cursor highlight() {
|
||||||
|
highlightGroup = _nextHighlightGroup;
|
||||||
|
_nextHighlightGroup += 1;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Cursor act(Act a) {
|
||||||
|
_acts.add(a);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,4 @@
|
|||||||
import 'dart:developer';
|
part of '../terminal.dart';
|
||||||
import 'dart:ui' as ui;
|
|
||||||
import 'dart: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 {
|
class TerminalCustomPainter extends CustomPainter {
|
||||||
Terminal t;
|
Terminal t;
|
||||||
@ -23,7 +16,7 @@ class TerminalCustomPainter extends CustomPainter {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void paint(Canvas canvas, Size size) {
|
void paint(Canvas canvas, Size size) {
|
||||||
var pr = PictureRecorder();
|
var pr = ui.PictureRecorder();
|
||||||
|
|
||||||
var smallSize = Size((cellW * Terminal.width).toDouble(),
|
var smallSize = Size((cellW * Terminal.width).toDouble(),
|
||||||
(cellH * Terminal.height).toDouble());
|
(cellH * Terminal.height).toDouble());
|
||||||
@ -65,27 +58,44 @@ class TerminalCustomPainter extends CustomPainter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void virtualPaint(Canvas canvas, Size size) {
|
void virtualPaint(Canvas canvas, Size size) {
|
||||||
|
// == Figure out where the mouse is ==
|
||||||
|
var lsm = t._lastSeenMouse;
|
||||||
|
int? activeHighlightGroup;
|
||||||
|
if (t._state == _State.waitMenu && lsm != null) {
|
||||||
|
final (x, y) = lsm;
|
||||||
|
final ti = t._fromXY(x, y);
|
||||||
|
if (ti != null) {
|
||||||
|
activeHighlightGroup = t._tiles[ti].highlightGroup;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final paint = Paint();
|
final paint = Paint();
|
||||||
|
|
||||||
var todos = Todos();
|
var todos = Todos();
|
||||||
|
|
||||||
// == Draw the background and foreground of every tile ==
|
// == Draw the background and foreground of every tile ==
|
||||||
for (var i = 0; i < nTiles; i++) {
|
for (var i = 0; i < nTiles; i++) {
|
||||||
final (pcxDst, pcyDst) = t.toXY(i) ?? (0, 0);
|
final (pcxDst, pcyDst) = t._toXY(i) ?? (0, 0);
|
||||||
final (pxDst, pyDst) = (pcxDst * cellW, pcyDst * cellH);
|
final (pxDst, pyDst) = (pcxDst * cellW, pcyDst * cellH);
|
||||||
final rectDst = Rect.fromLTWH(pxDst.toDouble(), pyDst.toDouble(),
|
final rectDst = Rect.fromLTWH(pxDst.toDouble(), pyDst.toDouble(),
|
||||||
cellW.toDouble(), cellH.toDouble());
|
cellW.toDouble(), cellH.toDouble());
|
||||||
|
|
||||||
final tile = t.tiles[i];
|
final tile = t._tiles[i];
|
||||||
|
|
||||||
|
var (bg, fg) = (tile.bg, tile.fg);
|
||||||
|
if (tile.highlightGroup != null &&
|
||||||
|
tile.highlightGroup == activeHighlightGroup) {
|
||||||
|
(fg, bg) = (bg, fg);
|
||||||
|
}
|
||||||
|
|
||||||
var bgRect = Rect.fromLTWH(0, 0, cellW.toDouble(), cellH.toDouble());
|
var bgRect = Rect.fromLTWH(0, 0, cellW.toDouble(), cellH.toDouble());
|
||||||
todos.add(Font.bg.imageName, bgRect, rectDst, tile.bg);
|
todos.add(Font.bg.imageName, bgRect, rectDst, bg);
|
||||||
|
|
||||||
var c = tile.content;
|
var c = tile.content;
|
||||||
if (c != null) {
|
if (c != null) {
|
||||||
var fgRect = Rect.fromLTWH(c.sourceCx.toDouble() * cellW,
|
var fgRect = Rect.fromLTWH(c.sourceCx.toDouble() * cellW,
|
||||||
c.sourceCy.toDouble() * cellH, cellW.toDouble(), cellH.toDouble());
|
c.sourceCy.toDouble() * cellH, cellW.toDouble(), cellH.toDouble());
|
||||||
todos.add(c.sourceImage, fgRect, rectDst, tile.fg);
|
todos.add(c.sourceImage, fgRect, rectDst, fg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
34
lib/terminal/reexports.dart
Normal file
34
lib/terminal/reexports.dart
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
part of '../terminal.dart';
|
||||||
|
|
||||||
|
Terminal _terminal = Terminal();
|
||||||
|
const int width = Terminal.width;
|
||||||
|
const int height = Terminal.height;
|
||||||
|
const int nTiles = Terminal.nTiles;
|
||||||
|
|
||||||
|
Widget toWidget(BuildContext context) {
|
||||||
|
return _terminal.toWidget(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
void notifyInput(Input i) {
|
||||||
|
_terminal._notifyInput(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
void notifyScreenDimensions(ScreenDimensions sd) {
|
||||||
|
_terminal._notifyScreenDimensions(sd);
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear() {
|
||||||
|
at(0, 0).clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
Cursor at(int x, int y) {
|
||||||
|
return Cursor(t: _terminal, x: x, y: y);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> zzz(double t) async {
|
||||||
|
await Future.delayed(Duration(milliseconds: (t * 1000).toInt()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> waitMenu({bool modal = false}) async {
|
||||||
|
await _waitMenu(_terminal, modal);
|
||||||
|
}
|
31
lib/terminal/tile.dart
Normal file
31
lib/terminal/tile.dart
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
part of '../terminal.dart';
|
||||||
|
|
||||||
|
class Tile {
|
||||||
|
Content? content;
|
||||||
|
|
||||||
|
Color bg = Palette.defaultBg;
|
||||||
|
Color fg = Palette.defaultFg;
|
||||||
|
|
||||||
|
int? highlightGroup;
|
||||||
|
|
||||||
|
List<Act> actions = [];
|
||||||
|
|
||||||
|
Tile();
|
||||||
|
|
||||||
|
Tile fadeDisable() {
|
||||||
|
var t2 = Tile();
|
||||||
|
t2.content = content;
|
||||||
|
t2.bg = bg;
|
||||||
|
t2.fg = fg;
|
||||||
|
// no highlight group, no actions
|
||||||
|
return t2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Content {
|
||||||
|
final String sourceImage;
|
||||||
|
final int sourceCx;
|
||||||
|
final int sourceCy;
|
||||||
|
|
||||||
|
const Content(this.sourceImage, this.sourceCx, this.sourceCy);
|
||||||
|
}
|
@ -41,6 +41,11 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<script>
|
<script>
|
||||||
|
// disable right clicks
|
||||||
|
document.body.addEventListener('contextmenu', (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
window.addEventListener('load', function(ev) {
|
window.addEventListener('load', function(ev) {
|
||||||
// Download main.dart.js
|
// Download main.dart.js
|
||||||
_flutter.loader.loadEntrypoint({
|
_flutter.loader.loadEntrypoint({
|
||||||
|
Loading…
Reference in New Issue
Block a user