Merge pull request #2371 from myk002/myk_overlay_hotkeys

[hotkeys] implement hotspot menu widget
develop
Myk 2022-11-14 16:50:30 -08:00 committed by GitHub
commit 35cea1b5e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 386 additions and 307 deletions

@ -12,9 +12,8 @@
keybinding add ` gui/launcher keybinding add ` gui/launcher
keybinding add Ctrl-Shift-D gui/launcher keybinding add Ctrl-Shift-D gui/launcher
# show all current key bindings # show hotkey popup menu
keybinding add Ctrl-F1 hotkeys keybinding add Ctrl-Shift-C hotkeys
keybinding add Alt-F1 hotkeys
# on-screen keyboard # on-screen keyboard
keybinding add Ctrl-Shift-K gui/cp437-table keybinding add Ctrl-Shift-K gui/cp437-table

@ -56,6 +56,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
- `dwarfmonitor`: widgets have been ported to the overlay framework and can be enabled and configured via the overlay command - `dwarfmonitor`: widgets have been ported to the overlay framework and can be enabled and configured via the overlay command
- `ls`: indent tag listings and wrap them in the right column for better readability - `ls`: indent tag listings and wrap them in the right column for better readability
- `ls`: new ``--exclude`` option for hiding matched scripts from the output. this can be especially useful for modders who don't want their mod scripts to be included in ``ls`` output. - `ls`: new ``--exclude`` option for hiding matched scripts from the output. this can be especially useful for modders who don't want their mod scripts to be included in ``ls`` output.
- `hotkeys`: hotkey screen has been transformed into an interactive `overlay` widget that you can bring up by moving the mouse cursor over the hotspot (in the upper left corner of the screen by default)
- `digtype`: new ``-z`` option for digtype to restrict designations to the current z-level and down - `digtype`: new ``-z`` option for digtype to restrict designations to the current z-level and down
- UX: List widgets now have mouse-interactive scrollbars - UX: List widgets now have mouse-interactive scrollbars
- UX: You can now hold down the mouse button on a scrollbar to make it scroll multiple times. - UX: You can now hold down the mouse button on a scrollbar to make it scroll multiple times.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

@ -2,7 +2,7 @@ hotkeys
======= =======
.. dfhack-tool:: .. dfhack-tool::
:summary: Show all dfhack keybindings for the current context. :summary: Show all DFHack keybindings for the current context.
:tags: dfhack :tags: dfhack
The command opens an in-game screen showing which DFHack keybindings are active The command opens an in-game screen showing which DFHack keybindings are active
@ -11,8 +11,22 @@ in the current context. See also `hotkey-notes`.
Usage Usage
----- -----
:: ``hotkeys``
Show the list of keybindings for the current context in an in-game menu.
``hotkeys list``
List the keybindings to the console.
hotkeys Menu overlay widget
-------------------
.. image:: ../images/hotkeys.png The in-game hotkeys menu is registered with the `overlay` framework and can be
enabled as a hotspot in the upper-left corner of the screen. You can bring up
the menu by hovering the mouse cursor over the hotspot and can select a command
to run from the list by clicking on it with the mouse or by using the keyboard
to select a command with the arrow keys and hitting :kbd:`Enter`.
A short description of the command will appear in a nearby textbox. If you'd
like to see the full help text for the command or edit the command before
running, you can open it for editing in `gui/launcher` by right clicking on the
command, left clicking on the arrow to the left of the command, or by pressing
the right arrow key while the command is selected.

@ -1,94 +1,116 @@
#include "uicommon.h" #include <map>
#include "listcolumn.h" #include <string>
#include <vector>
#include "df/viewscreen_dwarfmodest.h"
#include "df/ui.h"
#include "modules/Maps.h"
#include "modules/World.h"
#include "modules/Gui.h" #include "modules/Gui.h"
#include "modules/Screen.h"
#include "Debug.h"
#include "LuaTools.h" #include "LuaTools.h"
#include "PluginManager.h" #include "PluginManager.h"
DFHACK_PLUGIN("hotkeys"); DFHACK_PLUGIN("hotkeys");
#define PLUGIN_VERSION 0.1
namespace DFHack {
DBG_DECLARE(hotkeys, log, DebugCategory::LINFO);
}
using std::map;
using std::string;
using std::vector;
using namespace DFHack;
static const string INVOKE_MENU_COMMAND = "overlay trigger hotkeys.menu";
static const string INVOKE_HOTKEYS_COMMAND = "hotkeys";
static const std::string MENU_SCREEN_FOCUS_STRING = "dfhack/lua/hotkeys/menu";
static bool valid = false; // whether the following two vars contain valid data
static string current_focus;
static map<string, string> current_bindings; static map<string, string> current_bindings;
static vector<string> sorted_keys; static vector<string> sorted_keys;
static bool show_usage = false;
static bool can_invoke(string cmdline, df::viewscreen *screen) static bool can_invoke(const string &cmdline, df::viewscreen *screen) {
{
vector<string> cmd_parts; vector<string> cmd_parts;
split_string(&cmd_parts, cmdline, " "); split_string(&cmd_parts, cmdline, " ");
if (toLower(cmd_parts[0]) == "hotkeys")
return false;
return Core::getInstance().getPluginManager()->CanInvokeHotkey(cmd_parts[0], screen); return Core::getInstance().getPluginManager()->CanInvokeHotkey(cmd_parts[0], screen);
} }
static void add_binding_if_valid(string sym, string cmdline, df::viewscreen *screen) static int cleanupHotkeys(lua_State *) {
{ DEBUG(log).print("cleaning up old stub keybindings for %s\n", current_focus.c_str());
std::for_each(sorted_keys.begin(), sorted_keys.end(), [](const string &sym) {
string keyspec = sym + "@" + MENU_SCREEN_FOCUS_STRING;
DEBUG(log).print("clearing keybinding: %s\n", keyspec.c_str());
Core::getInstance().ClearKeyBindings(keyspec);
});
valid = false;
current_focus = "";
sorted_keys.clear();
current_bindings.clear();
return 0;
}
static void add_binding_if_valid(const string &sym, const string &cmdline, df::viewscreen *screen, bool filtermenu) {
if (!can_invoke(cmdline, screen)) if (!can_invoke(cmdline, screen))
return; return;
if (filtermenu && (cmdline == INVOKE_MENU_COMMAND ||
cmdline == INVOKE_HOTKEYS_COMMAND)) {
DEBUG(log).print("filtering out hotkey menu keybinding\n");
return;
}
current_bindings[sym] = cmdline; current_bindings[sym] = cmdline;
sorted_keys.push_back(sym); sorted_keys.push_back(sym);
string keyspec = sym + "@dfhack/viewscreen_hotkeys"; string keyspec = sym + "@" + MENU_SCREEN_FOCUS_STRING;
Core::getInstance().AddKeyBinding(keyspec, "hotkeys invoke " + int_to_string(sorted_keys.size() - 1)); string binding = "hotkeys invoke " + int_to_string(sorted_keys.size() - 1);
DEBUG(log).print("adding keybinding: %s -> %s\n", keyspec.c_str(), binding.c_str());
Core::getInstance().AddKeyBinding(keyspec, binding);
} }
static void find_active_keybindings(df::viewscreen *screen) static void find_active_keybindings(df::viewscreen *screen, bool filtermenu) {
{ DEBUG(log).print("scanning for active keybindings\n");
current_bindings.clear(); if (valid)
sorted_keys.clear(); cleanupHotkeys(NULL);
vector<string> valid_keys; vector<string> valid_keys;
for (char c = 'A'; c <= 'Z'; c++) for (char c = 'A'; c <= 'Z'; c++) {
{
valid_keys.push_back(string(&c, 1)); valid_keys.push_back(string(&c, 1));
} }
for (int i = 1; i < 10; i++) for (int i = 1; i < 10; i++) {
{
valid_keys.push_back("F" + int_to_string(i)); valid_keys.push_back("F" + int_to_string(i));
} }
valid_keys.push_back("`"); valid_keys.push_back("`");
auto current_focus = Gui::getFocusString(screen); current_focus = Gui::getFocusString(screen);
for (int shifted = 0; shifted < 2; shifted++) for (int shifted = 0; shifted < 2; shifted++) {
{ for (int alt = 0; alt < 2; alt++) {
for (int ctrl = 0; ctrl < 2; ctrl++) for (int ctrl = 0; ctrl < 2; ctrl++) {
{ for (auto it = valid_keys.begin(); it != valid_keys.end(); it++) {
for (int alt = 0; alt < 2; alt++)
{
for (auto it = valid_keys.begin(); it != valid_keys.end(); it++)
{
string sym; string sym;
if (shifted) sym += "Shift-";
if (ctrl) sym += "Ctrl-"; if (ctrl) sym += "Ctrl-";
if (alt) sym += "Alt-"; if (alt) sym += "Alt-";
if (shifted) sym += "Shift-";
sym += *it; sym += *it;
auto list = Core::getInstance().ListKeyBindings(sym); auto list = Core::getInstance().ListKeyBindings(sym);
for (auto invoke_cmd = list.begin(); invoke_cmd != list.end(); invoke_cmd++) for (auto invoke_cmd = list.begin(); invoke_cmd != list.end(); invoke_cmd++) {
{ string::size_type colon_pos = invoke_cmd->find(":");
if (invoke_cmd->find(":") == string::npos) // colons at location 0 are for commands like ":lua"
{ if (colon_pos == string::npos || colon_pos == 0) {
add_binding_if_valid(sym, *invoke_cmd, screen); add_binding_if_valid(sym, *invoke_cmd, screen, filtermenu);
} }
else else {
{
vector<string> tokens; vector<string> tokens;
split_string(&tokens, *invoke_cmd, ":"); split_string(&tokens, *invoke_cmd, ":");
string focus = tokens[0].substr(1); string focus = tokens[0].substr(1);
if (prefix_matches(focus, current_focus)) if (prefix_matches(focus, current_focus)) {
{
auto cmdline = trim(tokens[1]); auto cmdline = trim(tokens[1]);
add_binding_if_valid(sym, cmdline, screen); add_binding_if_valid(sym, cmdline, screen, filtermenu);
} }
} }
} }
@ -96,288 +118,87 @@ static void find_active_keybindings(df::viewscreen *screen)
} }
} }
} }
}
static bool close_hotkeys_screen()
{
auto screen = Core::getTopViewscreen();
if (Gui::getFocusString(screen) != "dfhack/viewscreen_hotkeys")
return false;
Screen::dismiss(Core::getTopViewscreen()); valid = true;
for_each_(sorted_keys, [] (const string &sym)
{ Core::getInstance().ClearKeyBindings(sym + "@dfhack/viewscreen_hotkeys"); });
sorted_keys.clear();
return true;
} }
static int getHotkeys(lua_State *L) {
static void invoke_command(const size_t index) find_active_keybindings(Gui::getCurViewscreen(true), true);
{ Lua::PushVector(L, sorted_keys);
if (sorted_keys.size() <= index) Lua::Push(L, current_bindings);
return; return 2;
auto cmd = current_bindings[sorted_keys[index]];
if (close_hotkeys_screen())
{
Core::getInstance().setHotkeyCmd(cmd);
}
} }
static std::string get_help(const std::string &command, bool full_help) DFHACK_PLUGIN_LUA_COMMANDS {
{ DFHACK_LUA_COMMAND(getHotkeys),
auto L = Lua::Core::State; DFHACK_LUA_COMMAND(cleanupHotkeys),
color_ostream_proxy out(Core::getInstance().getConsole()); DFHACK_LUA_END
Lua::StackUnwinder top(L); };
if (!lua_checkstack(L, 2) ||
!Lua::PushModulePublic(out, L, "helpdb",
full_help ? "get_entry_long_help" : "get_entry_short_help"))
return "Help text unavailable.";
Lua::Push(L, command);
if (!Lua::SafeCall(out, L, 1, 1)) static void list(color_ostream &out) {
return "Help text unavailable."; DEBUG(log).print("listing active hotkeys\n");
bool was_valid = valid;
if (!valid)
find_active_keybindings(Gui::getCurViewscreen(true), false);
const char *s = lua_tostring(L, -1); out.print("Valid keybindings for the current screen (%s)\n",
if (!s) current_focus.c_str());
return "Help text unavailable."; std::for_each(sorted_keys.begin(), sorted_keys.end(), [&](const string &sym) {
out.print("%s: %s\n", sym.c_str(), current_bindings[sym].c_str());
});
return s; if (!was_valid)
cleanupHotkeys(NULL);
} }
class ViewscreenHotkeys : public dfhack_viewscreen static bool invoke_command(color_ostream &out, const size_t index) {
{ auto screen = Core::getTopViewscreen();
public: if (sorted_keys.size() <= index ||
ViewscreenHotkeys(df::viewscreen *top_screen) : top_screen(top_screen) Gui::getFocusString(screen) != MENU_SCREEN_FOCUS_STRING)
{ return false;
hotkeys_column.multiselect = false;
hotkeys_column.auto_select = true;
hotkeys_column.setTitle("Key Binding");
hotkeys_column.bottom_margin = 4;
hotkeys_column.allow_search = false;
focus = Gui::getFocusString(top_screen);
populateColumns();
}
void populateColumns()
{
hotkeys_column.clear();
size_t max_key_length = 0;
for_each_(sorted_keys, [&] (const string &sym)
{ if (sym.length() > max_key_length) { max_key_length = sym.length(); } });
int padding = max_key_length + 2;
for (size_t i = 0; i < sorted_keys.size(); i++)
{
string text = pad_string(sorted_keys[i], padding, false);
text += current_bindings[sorted_keys[i]];
hotkeys_column.add(text, i+1);
}
help_start = hotkeys_column.fixWidth() + 2;
hotkeys_column.filterDisplay();
}
void feed(set<df::interface_key> *input)
{
if (hotkeys_column.feed(input))
return;
if (input->count(interface_key::LEAVESCREEN))
{
close_hotkeys_screen();
}
else if (input->count(interface_key::SELECT))
{
invoke_command(hotkeys_column.highlighted_index);
}
else if (input->count(interface_key::CUSTOM_U))
{
show_usage = !show_usage;
}
}
void render()
{
if (Screen::isDismissed(this))
return;
dfhack_viewscreen::render();
Screen::clear();
Screen::drawBorder(" Hotkeys ");
hotkeys_column.display(true);
int32_t y = gps->dimy - 3;
int32_t x = 2;
OutputHotkeyString(x, y, "Leave", "Esc");
x += 3;
OutputHotkeyString(x, y, "Invoke", "Enter or Hotkey");
x += 3;
OutputToggleString(x, y, "Show Usage", "u", show_usage);
y = gps->dimy - 4;
x = 2;
OutputHotkeyString(x, y, focus.c_str(), "Context", false, help_start, COLOR_WHITE, COLOR_BROWN);
if (sorted_keys.size() == 0)
return;
y = 2;
x = help_start;
auto width = gps->dimx - help_start - 2;
vector <string> parts;
Core::cheap_tokenise(current_bindings[sorted_keys[hotkeys_column.highlighted_index]], parts);
if(parts.size() == 0)
return;
string first = parts[0];
parts.erase(parts.begin());
if (first[0] == '#')
return;
OutputString(COLOR_BROWN, x, y, "Help", true, help_start); auto cmd = current_bindings[sorted_keys[index]];
string help_text = get_help(first, show_usage); DEBUG(log).print("invoking command: '%s'\n", cmd.c_str());
vector <string> lines;
split_string(&lines, help_text, "\n");
for (auto it = lines.begin(); it != lines.end() && y < gps->dimy - 4; it++)
{
auto wrapped_lines = wrapString(*it, width);
for (auto wit = wrapped_lines.begin(); wit != wrapped_lines.end() && y < gps->dimy - 4; wit++)
{
OutputString(COLOR_WHITE, x, y, *wit, true, help_start);
}
}
}
virtual std::string getFocusString()
{ {
return "viewscreen_hotkeys"; Screen::Hide hideGuard(screen, Screen::Hide::RESTORE_AT_TOP);
Core::getInstance().runCommand(out, cmd);
} }
private: Screen::dismiss(screen);
ListColumn<int> hotkeys_column; return true;
df::viewscreen *top_screen; }
string focus;
int32_t help_start;
void resize(int32_t x, int32_t y)
{
dfhack_viewscreen::resize(x, y);
hotkeys_column.resize();
}
static vector<string> wrapString(string str, int width) static command_result hotkeys_cmd(color_ostream &out, vector <string> & parameters) {
{ if (!parameters.size()) {
vector<string> result; DEBUG(log).print("invoking command: '%s'\n", INVOKE_MENU_COMMAND.c_str());
string excess; return Core::getInstance().runCommand(out, INVOKE_MENU_COMMAND );
if (int(str.length()) > width)
{
auto cut_space = str.rfind(' ', width-1);
int excess_start;
if (cut_space == string::npos)
{
cut_space = width-1;
excess_start = cut_space;
}
else
{
excess_start = cut_space + 1;
} }
string line = str.substr(0, cut_space); if (parameters[0] == "list") {
excess = str.substr(excess_start); list(out);
result.push_back(line); return CR_OK;
auto excess_lines = wrapString(excess, width);
result.insert(result.end(), excess_lines.begin(), excess_lines.end());
}
else
{
result.push_back(str);
} }
return result; // internal command -- intentionally undocumented
} if (parameters.size() != 2 || parameters[0] != "invoke")
}; return CR_WRONG_USAGE;
CoreSuspender guard;
static command_result hotkeys_cmd(color_ostream &out, vector <string> & parameters) int index = string_to_int(parameters[1], -1);
{ if (index < 0)
if (parameters.empty())
{
if (Maps::IsValid())
{
auto top_screen = Core::getTopViewscreen();
if (Gui::getFocusString(top_screen) != "dfhack/viewscreen_hotkeys")
{
find_active_keybindings(top_screen);
Screen::show(dts::make_unique<ViewscreenHotkeys>(top_screen), plugin_self);
}
}
}
else
{
auto cmd = parameters[0][0];
if (cmd == 'v')
{
out << "Hotkeys" << endl << "Version: " << PLUGIN_VERSION << endl;
}
else if (cmd == 'i')
{
int index;
stringstream index_raw(parameters[1]);
index_raw >> index;
invoke_command(index);
}
else
{
return CR_WRONG_USAGE; return CR_WRONG_USAGE;
} return invoke_command(out, index) ? CR_OK : CR_WRONG_USAGE;
}
return CR_OK;
} }
DFhackCExport command_result plugin_init (color_ostream &out, std::vector <PluginCommand> &commands) {
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
if (!gps)
out.printerr("Could not insert hotkeys hooks!\n");
commands.push_back( commands.push_back(
PluginCommand( PluginCommand(
"hotkeys", "hotkeys",
"Show all dfhack keybindings in current context.", "Invoke hotkeys from the interactive menu.",
hotkeys_cmd)); hotkeys_cmd,
Gui::anywhere_hotkey));
return CR_OK;
}
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
return CR_OK;
}
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
{
switch (event) {
case SC_MAP_LOADED:
sorted_keys.clear();
break;
default:
break;
}
return CR_OK; return CR_OK;
} }

@ -0,0 +1,244 @@
local _ENV = mkmodule('plugins.hotkeys')
local gui = require('gui')
local helpdb = require('helpdb')
local overlay = require('plugins.overlay')
local widgets = require('gui.widgets')
-- ----------------- --
-- HotspotMenuWidget --
-- ----------------- --
HotspotMenuWidget = defclass(HotspotMenuWidget, overlay.OverlayWidget)
HotspotMenuWidget.ATTRS{
default_pos={x=1,y=3},
hotspot=true,
viewscreens={'dwarfmode'},
overlay_onupdate_max_freq_seconds=0,
frame={w=2, h=1}
}
function HotspotMenuWidget:init()
self:addviews{widgets.Label{text='!!'}}
self.mouseover = false
end
function HotspotMenuWidget:overlay_onupdate()
local hasMouse = self:getMousePos()
if hasMouse and not self.mouseover then
self.mouseover = true
return true
end
self.mouseover = hasMouse
end
function HotspotMenuWidget:overlay_trigger()
local hotkeys, bindings = getHotkeys()
return MenuScreen{
hotspot_frame=self.frame,
hotkeys=hotkeys,
bindings=bindings}:show()
end
-- register the menu hotspot with the overlay
OVERLAY_WIDGETS = {menu=HotspotMenuWidget}
-- ---------- --
-- MenuScreen --
-- ---------- --
local ARROW = string.char(26)
local MAX_LIST_WIDTH = 45
local MAX_LIST_HEIGHT = 15
MenuScreen = defclass(MenuScreen, gui.Screen)
MenuScreen.ATTRS{
focus_path='hotkeys/menu',
hotspot_frame=DEFAULT_NIL,
hotkeys=DEFAULT_NIL,
bindings=DEFAULT_NIL,
}
-- get a map from the binding string to a list of hotkey strings that all
-- point to that binding
local function get_bindings_to_hotkeys(hotkeys, bindings)
local bindings_to_hotkeys = {}
for _,hotkey in ipairs(hotkeys) do
local binding = bindings[hotkey]
table.insert(ensure_key(bindings_to_hotkeys, binding), hotkey)
end
return bindings_to_hotkeys
end
-- number of non-text tiles: icon, space, space between cmd and hk, scrollbar
local LIST_BUFFER = 2 + 1 + 1
local function get_choices(hotkeys, bindings, is_inverted)
local choices, max_width, seen = {}, 0, {}
local bindings_to_hotkeys = get_bindings_to_hotkeys(hotkeys, bindings)
-- build list choices
for _,hotkey in ipairs(hotkeys) do
local command = bindings[hotkey]
if seen[command] then goto continue end
seen[command] = true
local hk_width, tokens = 0, {}
for _,hk in ipairs(bindings_to_hotkeys[command]) do
if hk_width ~= 0 then
table.insert(tokens, ', ')
hk_width = hk_width + 2
end
table.insert(tokens, {text=hk, pen=COLOR_LIGHTGREEN})
hk_width = hk_width + #hk
end
local command_str = command
if hk_width + #command + LIST_BUFFER > MAX_LIST_WIDTH then
local max_command_len = MAX_LIST_WIDTH - hk_width - LIST_BUFFER
command_str = command:sub(1, max_command_len - 3) .. '...'
end
table.insert(tokens, 1, {text=command_str})
local choice = {icon=ARROW, command=command, text=tokens,
hk_width=hk_width}
max_width = math.max(max_width, hk_width + #command_str + LIST_BUFFER)
table.insert(choices, is_inverted and 1 or #choices + 1, choice)
::continue::
end
-- adjust width of command fields so the hotkey tokens are right justified
for _,choice in ipairs(choices) do
local command_token = choice.text[1]
command_token.width = max_width - choice.hk_width - 3
end
return choices, max_width
end
function MenuScreen:init()
self.mouseover = false
local choices,list_width = get_choices(self.hotkeys, self.bindings,
self.hotspot_frame.b)
local list_frame = copyall(self.hotspot_frame)
list_frame.w = list_width + 2
list_frame.h = math.min(#choices, MAX_LIST_HEIGHT) + 2
if list_frame.t then
list_frame.t = math.max(0, list_frame.t - 1)
else
list_frame.b = math.max(0, list_frame.b - 1)
end
if list_frame.l then
list_frame.l = math.max(0, list_frame.l - 1)
else
list_frame.r = math.max(0, list_frame.r - 1)
end
local help_frame = {w=list_frame.w, l=list_frame.l, r=list_frame.r}
if list_frame.t then
help_frame.t = list_frame.t + list_frame.h + 1
else
help_frame.b = list_frame.b + list_frame.h + 1
end
self:addviews{
widgets.ResizingPanel{
view_id='list_panel',
autoarrange_subviews=true,
frame=list_frame,
frame_style=gui.GREY_LINE_FRAME,
frame_background=gui.CLEAR_PEN,
subviews={
widgets.List{
view_id='list',
choices=choices,
icon_width=2,
on_select=self:callback('onSelect'),
on_submit=self:callback('onSubmit'),
on_submit2=self:callback('onSubmit2'),
},
},
},
widgets.ResizingPanel{
view_id='help_panel',
autoarrange_subviews=true,
frame=help_frame,
frame_style=gui.GREY_LINE_FRAME,
frame_background=gui.CLEAR_PEN,
subviews={
widgets.WrappedLabel{
view_id='help',
text_to_wrap='',
scroll_keys={},
},
},
},
}
end
function MenuScreen:onDismiss()
cleanupHotkeys()
end
function MenuScreen:onSelect(_, choice)
if not choice or #self.subviews == 0 then return end
local first_word = choice.command:trim():split(' +')[1]
if first_word:startswith(':') then first_word = first_word:sub(2) end
self.subviews.help.text_to_wrap = helpdb.is_entry(first_word) and
helpdb.get_entry_short_help(first_word) or 'Command not found'
self.subviews.help_panel:updateLayout()
end
function MenuScreen:onSubmit(_, choice)
if not choice then return end
dfhack.screen.hideGuard(self, dfhack.run_command, choice.command)
self:dismiss()
end
function MenuScreen:onSubmit2(_, choice)
if not choice then return end
self:dismiss()
dfhack.run_script('gui/launcher', choice.command)
end
function MenuScreen:onInput(keys)
if keys.LEAVESCREEN then
self:dismiss()
return true
elseif keys.STANDARDSCROLL_RIGHT then
self:onSubmit2(self.subviews.list:getSelected())
return true
elseif keys._MOUSE_L then
local list = self.subviews.list
local x = list:getMousePos()
if x == 0 then -- clicked on icon
self:onSubmit2(list:getSelected())
return true
end
end
return self:inputToSubviews(keys)
end
function MenuScreen:onRenderFrame(dc, rect)
self:renderParent()
end
function MenuScreen:onRenderBody(dc)
local panel = self.subviews.list_panel
local list = self.subviews.list
local idx = list:getIdxUnderMouse()
if idx and idx ~= self.last_mouse_idx then
-- focus follows mouse, but if cursor keys were used to change the
-- selection, don't override the selection until the mouse moves to
-- another item
list:setSelected(idx)
self.mouseover = true
self.last_mouse_idx = idx
elseif not panel:getMousePos(gui.ViewRect{rect=panel.frame_rect})
and self.mouseover then
-- once the mouse has entered the list area, leaving the frame should
-- close the menu screen
self:dismiss()
end
end
return _ENV