Merge pull request #2371 from myk002/myk_overlay_hotkeys
[hotkeys] implement hotspot menu widgetdevelop
						commit
						35cea1b5e1
					
				
											
												Binary file not shown.
											
										
									
								| Before Width: | Height: | Size: 32 KiB | 
| @ -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 | ||||
		Loading…
	
		Reference in New Issue