diff --git a/data/init/dfhack.keybindings.init b/data/init/dfhack.keybindings.init index fd02635e2..330260b06 100644 --- a/data/init/dfhack.keybindings.init +++ b/data/init/dfhack.keybindings.init @@ -12,9 +12,8 @@ keybinding add ` gui/launcher keybinding add Ctrl-Shift-D gui/launcher -# show all current key bindings -keybinding add Ctrl-F1 hotkeys -keybinding add Alt-F1 hotkeys +# show hotkey popup menu +keybinding add Ctrl-Shift-C hotkeys # on-screen keyboard keybinding add Ctrl-Shift-K gui/cp437-table diff --git a/docs/changelog.txt b/docs/changelog.txt index 9c6940cd0..68b12dc77 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -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 - `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. +- `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 - 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. diff --git a/docs/images/hotkeys.png b/docs/images/hotkeys.png deleted file mode 100644 index 524ce9a52..000000000 Binary files a/docs/images/hotkeys.png and /dev/null differ diff --git a/docs/plugins/hotkeys.rst b/docs/plugins/hotkeys.rst index 6780efca1..6b2ef3f0c 100644 --- a/docs/plugins/hotkeys.rst +++ b/docs/plugins/hotkeys.rst @@ -2,7 +2,7 @@ hotkeys ======= .. dfhack-tool:: - :summary: Show all dfhack keybindings for the current context. + :summary: Show all DFHack keybindings for the current context. :tags: dfhack 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 ----- -:: +``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. diff --git a/plugins/hotkeys.cpp b/plugins/hotkeys.cpp index e92bc61fe..85c304d82 100644 --- a/plugins/hotkeys.cpp +++ b/plugins/hotkeys.cpp @@ -1,94 +1,116 @@ -#include "uicommon.h" -#include "listcolumn.h" +#include +#include +#include -#include "df/viewscreen_dwarfmodest.h" -#include "df/ui.h" - -#include "modules/Maps.h" -#include "modules/World.h" #include "modules/Gui.h" +#include "modules/Screen.h" +#include "Debug.h" #include "LuaTools.h" #include "PluginManager.h" 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 current_bindings; static vector 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 cmd_parts; split_string(&cmd_parts, cmdline, " "); - if (toLower(cmd_parts[0]) == "hotkeys") - return false; 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)) 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; sorted_keys.push_back(sym); - string keyspec = sym + "@dfhack/viewscreen_hotkeys"; - Core::getInstance().AddKeyBinding(keyspec, "hotkeys invoke " + int_to_string(sorted_keys.size() - 1)); + string keyspec = sym + "@" + MENU_SCREEN_FOCUS_STRING; + 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) -{ - current_bindings.clear(); - sorted_keys.clear(); +static void find_active_keybindings(df::viewscreen *screen, bool filtermenu) { + DEBUG(log).print("scanning for active keybindings\n"); + if (valid) + cleanupHotkeys(NULL); vector valid_keys; - for (char c = 'A'; c <= 'Z'; c++) - { + for (char c = 'A'; c <= 'Z'; c++) { 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("`"); - auto current_focus = Gui::getFocusString(screen); - for (int shifted = 0; shifted < 2; shifted++) - { - for (int ctrl = 0; ctrl < 2; ctrl++) - { - for (int alt = 0; alt < 2; alt++) - { - for (auto it = valid_keys.begin(); it != valid_keys.end(); it++) - { + current_focus = Gui::getFocusString(screen); + for (int shifted = 0; shifted < 2; shifted++) { + for (int alt = 0; alt < 2; alt++) { + for (int ctrl = 0; ctrl < 2; ctrl++) { + for (auto it = valid_keys.begin(); it != valid_keys.end(); it++) { string sym; - if (shifted) sym += "Shift-"; if (ctrl) sym += "Ctrl-"; if (alt) sym += "Alt-"; + if (shifted) sym += "Shift-"; sym += *it; auto list = Core::getInstance().ListKeyBindings(sym); - for (auto invoke_cmd = list.begin(); invoke_cmd != list.end(); invoke_cmd++) - { - if (invoke_cmd->find(":") == string::npos) - { - add_binding_if_valid(sym, *invoke_cmd, screen); + for (auto invoke_cmd = list.begin(); invoke_cmd != list.end(); invoke_cmd++) { + string::size_type colon_pos = invoke_cmd->find(":"); + // 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, filtermenu); } - else - { + else { vector tokens; split_string(&tokens, *invoke_cmd, ":"); string focus = tokens[0].substr(1); - if (prefix_matches(focus, current_focus)) - { + if (prefix_matches(focus, current_focus)) { 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()); - for_each_(sorted_keys, [] (const string &sym) - { Core::getInstance().ClearKeyBindings(sym + "@dfhack/viewscreen_hotkeys"); }); - sorted_keys.clear(); - return true; + valid = true; } - -static void invoke_command(const size_t index) -{ - if (sorted_keys.size() <= index) - return; - - auto cmd = current_bindings[sorted_keys[index]]; - if (close_hotkeys_screen()) - { - Core::getInstance().setHotkeyCmd(cmd); - } +static int getHotkeys(lua_State *L) { + find_active_keybindings(Gui::getCurViewscreen(true), true); + Lua::PushVector(L, sorted_keys); + Lua::Push(L, current_bindings); + return 2; } -static std::string get_help(const std::string &command, bool full_help) -{ - auto L = Lua::Core::State; - color_ostream_proxy out(Core::getInstance().getConsole()); - 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); +DFHACK_PLUGIN_LUA_COMMANDS { + DFHACK_LUA_COMMAND(getHotkeys), + DFHACK_LUA_COMMAND(cleanupHotkeys), + DFHACK_LUA_END +}; - if (!Lua::SafeCall(out, L, 1, 1)) - return "Help text unavailable."; +static void list(color_ostream &out) { + 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); - if (!s) - return "Help text unavailable."; + out.print("Valid keybindings for the current screen (%s)\n", + current_focus.c_str()); + 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 -{ -public: - ViewscreenHotkeys(df::viewscreen *top_screen) : top_screen(top_screen) - { - 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 *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 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; +static bool invoke_command(color_ostream &out, const size_t index) { + auto screen = Core::getTopViewscreen(); + if (sorted_keys.size() <= index || + Gui::getFocusString(screen) != MENU_SCREEN_FOCUS_STRING) + return false; - OutputString(COLOR_BROWN, x, y, "Help", true, help_start); - string help_text = get_help(first, show_usage); - vector 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); - } - } - } + auto cmd = current_bindings[sorted_keys[index]]; + DEBUG(log).print("invoking command: '%s'\n", cmd.c_str()); - virtual std::string getFocusString() { - return "viewscreen_hotkeys"; + Screen::Hide hideGuard(screen, Screen::Hide::RESTORE_AT_TOP); + Core::getInstance().runCommand(out, cmd); } -private: - ListColumn hotkeys_column; - df::viewscreen *top_screen; - string focus; - - int32_t help_start; + Screen::dismiss(screen); + return true; +} - void resize(int32_t x, int32_t y) - { - dfhack_viewscreen::resize(x, y); - hotkeys_column.resize(); +static command_result hotkeys_cmd(color_ostream &out, vector & parameters) { + if (!parameters.size()) { + DEBUG(log).print("invoking command: '%s'\n", INVOKE_MENU_COMMAND.c_str()); + return Core::getInstance().runCommand(out, INVOKE_MENU_COMMAND ); } - static vector wrapString(string str, int width) - { - vector result; - string excess; - 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); - excess = str.substr(excess_start); - result.push_back(line); - auto excess_lines = wrapString(excess, width); - result.insert(result.end(), excess_lines.begin(), excess_lines.end()); - } - else - { - result.push_back(str); - } - - return result; + if (parameters[0] == "list") { + list(out); + return CR_OK; } -}; + // internal command -- intentionally undocumented + if (parameters.size() != 2 || parameters[0] != "invoke") + return CR_WRONG_USAGE; -static command_result hotkeys_cmd(color_ostream &out, vector & parameters) -{ - 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(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; - } - } + CoreSuspender guard; - return CR_OK; + int index = string_to_int(parameters[1], -1); + if (index < 0) + return CR_WRONG_USAGE; + return invoke_command(out, index) ? CR_OK : CR_WRONG_USAGE; } - -DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) -{ - if (!gps) - out.printerr("Could not insert hotkeys hooks!\n"); - +DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { commands.push_back( PluginCommand( "hotkeys", - "Show all dfhack keybindings in current context.", - hotkeys_cmd)); - - 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; - } + "Invoke hotkeys from the interactive menu.", + hotkeys_cmd, + Gui::anywhere_hotkey)); return CR_OK; } diff --git a/plugins/lua/hotkeys.lua b/plugins/lua/hotkeys.lua new file mode 100644 index 000000000..7a1a39115 --- /dev/null +++ b/plugins/lua/hotkeys.lua @@ -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