178 lines
4.5 KiB
Dart
178 lines
4.5 KiB
Dart
|
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);
|
||
|
}
|
||
|
}
|
||
|
}
|