part of '../terminal.dart'; typedef Callback = Future 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 _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 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 _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 acts = []; Act? defaultAct; bool canShow = false; int displayX = 0; int displayY = 0; int displayWidth = 0; int displayHeight = 0; _Menu(List 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); } } }