diff options
117 files changed, 8006 insertions, 3227 deletions
diff --git a/docs/source/commandline/sdlconfig.rst b/docs/source/commandline/sdlconfig.rst index eea4b959e60..df04865a390 100644 --- a/docs/source/commandline/sdlconfig.rst +++ b/docs/source/commandline/sdlconfig.rst @@ -61,9 +61,17 @@ SDL Keyboard Mapping Keymap file name. Default is ``keymap.dat``. -SDL Joystick Mapping +SDL Input Options -------------------- +.. _mame-scommandline-enabletouch: + +**-enable_touch** + + Enable support for touch input. If this option is switched off, mouse input + simulated from touch devices will be used instead. Default is OFF + (**-noenable_touch**) + .. _mame-scommandline-sixaxis: **-sixaxis** diff --git a/docs/source/luascript/ref-input.rst b/docs/source/luascript/ref-input.rst index 7390d46743f..f146d6c78ff 100644 --- a/docs/source/luascript/ref-input.rst +++ b/docs/source/luascript/ref-input.rst @@ -750,16 +750,6 @@ uiinput:reset() Clears pending events and UI input states. Should be called when leaving a modal state where input is handled directly (e.g. configuring an input combination). -uiinput:find_mouse() - Returns host system mouse pointer X position, Y position, button state, and - the :ref:`render target <luascript-ref-rendertarget>` it falls in. The - position is in host pixels, where zero is at the top/left. The button state - is a Boolean indicating whether the primary mouse button is pressed. - - If the mouse pointer is not over one of MAME’s windows, this may return the - position and render target from when the mouse pointer was most recently - over one of MAME’s windows. The render target may be ``nil`` if the mouse - pointer is not over one of MAME’s windows. uiinput:pressed(type) Returns a Boolean indicating whether the specified UI input has been pressed. The input type is an enumerated value. diff --git a/docs/source/luascript/ref-render.rst b/docs/source/luascript/ref-render.rst index 265e52b5016..eee432660dd 100644 --- a/docs/source/luascript/ref-render.rst +++ b/docs/source/luascript/ref-render.rst @@ -1057,6 +1057,66 @@ view:set_recomputed_callback(cb) View coordinates are recomputed in various events, including the window being resized, entering or leaving full-screen mode, and changing the zoom to screen area setting. +view:set_pointer_updated_callback(cb) + Set a function to receive notifications when a pointer enters, moves or + changes button states over the view. The function must accept nine + arguments: + + * The pointer type (``mouse``, ``pen``, ``touch`` or ``unknown``). + * The pointer ID (a non-negative integer that will not change for the + lifetime of a pointer). + * The device ID for grouping pointers to recognise multi-touch gestures + (non-negative integer). + * Horizontal position in layout coordinates. + * Vertical position in layout coordinates. + * A bit mask representing the currently pressed buttons. + * A bit mask representing the buttons that were pressed in this update. + * A bit mask representing the buttons that were released in this update. + * The click count (positive for multi-click actions, or negative if a click + is turned into a hold or drag). + + Call with ``nil`` to remove the callback. +view:set_pointer_left_callback(cb) + Set a function to receive notifications when a pointer leaves the view + normally. The function must accept seven arguments: + + * The pointer type (``mouse``, ``pen``, ``touch`` or ``unknown``). + * The pointer ID (a non-negative integer that will not change for the + lifetime of a pointer). The ID may be reused for a new pointer after + receiving this notification. + * The device ID for grouping pointers to recognise multi-touch gestures + (non-negative integer). + * Horizontal position in layout coordinates. + * Vertical position in layout coordinates. + * A bit mask representing the buttons that were released in this update. + * The click count (positive for multi-click actions, or negative if a click + is turned into a hold or drag). + + Call with ``nil`` to remove the callback. +view:set_pointer_aborted_callback(cb) + Set a function to receive notifications when a pointer leaves the view + abnormally. The function must accept seven arguments: + + * The pointer type (``mouse``, ``pen``, ``touch`` or ``unknown``). + * The pointer ID (a non-negative integer that will not change for the + lifetime of a pointer). The ID may be reused for a new pointer after + receiving this notification. + * The device ID for grouping pointers to recognise multi-touch gestures + (non-negative integer). + * Horizontal position in layout coordinates. + * Vertical position in layout coordinates. + * A bit mask representing the buttons that were released in this update. + * The click count (positive for multi-click actions, or negative if a click + is turned into a hold or drag). + + Call with ``nil`` to remove the callback. +view:set_forget_pointers_callback(cb) + Set a function to receive notifications when the view should stop processing + pointer input. The function must accept no arguments. Call with ``nil`` to + remove the callback. + + This can happen in a number of situations, including the view configuration + changing or a menu taking over input handling. Properties ~~~~~~~~~~ diff --git a/docs/source/techspecs/layout_files.rst b/docs/source/techspecs/layout_files.rst index d9976faf00d..297521bf945 100644 --- a/docs/source/techspecs/layout_files.rst +++ b/docs/source/techspecs/layout_files.rst @@ -1188,7 +1188,7 @@ Clickable items If a view item (``element`` or ``screen`` element) has ``inputtag`` and ``inputmask`` attribute values that correspond to a digital switch field in the emulated system, clicking the element will activate the switch. The switch -will remain active as long as the mouse button is held down and the pointer is +will remain active as long as the primary button is held down and the pointer is within the item’s current bounds. (Note that the bounds may change depending on the item’s animation state, see :ref:`layfile-interact-itemanim`). @@ -1197,6 +1197,12 @@ device that caused the layout file to be loaded. The ``inputmask`` attribute must be an integer specifying the bits of the I/O port field that the item should activate. This sample shows instantiation of clickable buttons: +The ``clickthrough`` attribute controls whether clicks can pass through the view +item to other view items drawn above it. The ``clickthrough`` attribute must be +``yes`` or ``no`` if present. The default is ``no`` (clicks do not pass +through) for view items with ``inputtag`` and ``inputmask`` attributes, and +``yes`` (clicks pass through) for other view items. + .. code-block:: XML <element ref="btn_3" inputtag="X2" inputmask="0x10"> @@ -1209,9 +1215,8 @@ should activate. This sample shows instantiation of clickable buttons: <bounds x="1.775" y="5.375" width="1.0" height="1.0" /> </element> -When handling mouse input, MAME treats all layout elements as being rectangular, -and only activates the first clickable item whose area includes the location of -the mouse pointer. +When handling pointer input, MAME treats all layout elements as being +rectangular. .. _layfile-interact-elemstate: diff --git a/docs/source/techspecs/layout_script.rst b/docs/source/techspecs/layout_script.rst index be6166a7146..7e5b7419bb7 100644 --- a/docs/source/techspecs/layout_script.rst +++ b/docs/source/techspecs/layout_script.rst @@ -563,6 +563,96 @@ Dimensions recomputed The callback function has no return value and takes no parameters. Call with ``nil`` as the argument to remove the event handler. +Pointer updated + ``view:set_pointer_updated_callback(cb)`` + + Called when a pointer enters, moves or changes button state over the view. + + The callback function is passed nine arguments: + + * The pointer type as a string. This will be ``mouse``, ``pen``, ``touch`` + or ``unknown``, and will not change for the lifetime of a pointer. + * The pointer ID. This will be a non-negative integer that will not change + for the lifetime of a pointer. Pointer ID values are recycled + aggressively. + * The device ID. This will be a non-negative integer that can be used to + group pointers for recognising multi-touch gestures. + * The horizontal position of the pointer in layout coordinates. + * The vertical position of the pointer in layout coordinates. + * A bit mask representing the currently pressed buttons. The primary button + is the least significant bit. + * A bit mask representing the buttons that were pressed in this update. The + primary button is the least significant bit. + * A bit mask representing the buttons that were released in this update. + The primary button is the least significant bit. + * The click count. This is positive for multi-click actions, or negative if + a click is turned into a hold or drag. This only applies to the primary + button. + + The callback function has no return value. Call with ``nil`` as the + argument to remove the event handler. +Pointer left + ``view:set_pointer_left_callback(cb)`` + + Called when a pointer leaves the view normally. After receiving this event, + the pointer ID may be reused for a new pointer. + + The callback function is passed seven arguments: + + * The pointer type as a string. This will be ``mouse``, ``pen``, ``touch`` + or ``unknown``, and will not change for the lifetime of a pointer. + * The pointer ID. This will be a non-negative integer that will not change + for the lifetime of a pointer. Pointer ID values are recycled + aggressively. + * The device ID. This will be a non-negative integer that can be used to + group pointers for recognising multi-touch gestures. + * The horizontal position of the pointer in layout coordinates. + * The vertical position of the pointer in layout coordinates. + * A bit mask representing the buttons that were released in this update. + The primary button is the least significant bit. + * The click count. This is positive for multi-click actions, or negative if + a click is turned into a hold or drag. This only applies to the primary + button. + + The callback function has no return value. Call with ``nil`` as the + argument to remove the event handler. +Pointer aborted + ``view:set_pointer_aborted_callback(cb)`` + + Called when a pointer leaves the view abnormally. After receiving this + event, the pointer ID may be reused for a new pointer. + + The callback function is passed seven arguments: + + * The pointer type as a string. This will be ``mouse``, ``pen``, ``touch`` + or ``unknown``, and will not change for the lifetime of a pointer. + * The pointer ID. This will be a non-negative integer that will not change + for the lifetime of a pointer. Pointer ID values are recycled + aggressively. + * The device ID. This will be a non-negative integer that can be used to + group pointers for recognising multi-touch gestures. + * The horizontal position of the pointer in layout coordinates. + * The vertical position of the pointer in layout coordinates. + * A bit mask representing the buttons that were released in this update. + The primary button is the least significant bit. + * The click count. This is positive for multi-click actions, or negative if + a click is turned into a hold or drag. This only applies to the primary + button. + + The callback function has no return value. Call with ``nil`` as the + argument to remove the event handler. +Forget pointers + ``view:set_forget_pointers_callback(cb)`` + + Called when the view should stop processing pointer input. This can happen + in a number of situations, including: + + * The user activated a menu. + * The view configuration will change. + * The view will be deactivated. + + The callback function has no return value and takes no parameters. Call + with ``nil`` as the argument to remove the event handler. .. _layscript-events-item: @@ -692,4 +782,5 @@ Draw The callback is passed two arguments: the element state (an integer) and the 32-bit ARGB bitmap at the required size. The callback must not attempt to - resize the bitmap. + resize the bitmap. Call with ``nil`` as the argument to remove the event + handler. diff --git a/docs/source/usingmame/ui.rst b/docs/source/usingmame/ui.rst index 60bb55618a2..94c7eba413d 100644 --- a/docs/source/usingmame/ui.rst +++ b/docs/source/usingmame/ui.rst @@ -136,9 +136,9 @@ pointing device: * Click menu items to highlight them. * Double-click menu items to select them. * Click the left- or right-pointing triangle to adjust settings. -* For menus with too many items to fit on the screen, click the upward- or - downward-pointing triangle at the top or bottom to scroll up or down by one - screen at a time. +* For menus or text boxes with too many items or lines to fit on the screen, + press on the upward- or downward-pointing triangle at the top or bottom to + scroll up or down. * Use vertical scrolling gestures to scroll menus or text boxes with too many items or lines to fit on the screen. * Click toolbar items to select them, or hover over them to see a description. @@ -148,6 +148,28 @@ combinations to the **Show/Hide Menu**, **Pause**, **UI Back** and/or **UI Cancel** inputs to make it possible to use MAME without a keyboard. +.. _ui-menus-touch: + +Using a touch screen +~~~~~~~~~~~~~~~~~~~~ + +MAME has basic support for navigating menus using a touch screen: + +* Tap menu items to highlight them. +* Double-tap menu items to select them. +* Swipe left or right (horizontally) on the highlighted menu item to adjust the + setting if applicable. +* Swipe up or down (vertically) to scroll menus or text boxes with too many + items to fit on the screen. +* For menus or text boxes with too many items or lines to fit on the screen, + press on the upward- or downward-pointing triangle at the top or bottom to + scroll up or down. + +Note that for SDL-based MAME, the +:ref:`enable_touch <mame-scommandline-enabletouch>` option must be switched on +to use touch screen support. + + .. _ui-inptcfg: Configuring inputs @@ -493,6 +515,8 @@ or info source with left/right. When focus is on the info/image tabs, left/right switch between tabs. When focus is on the image/info tabs or source, you can scroll the info using up, down, page up, page down, home and end. +You can move focus to an area by clicking on it with the middle mouse button. + .. _ui-simpleselmenu: diff --git a/scripts/build/complay.py b/scripts/build/complay.py index 475806c0e65..f0e330240a1 100755 --- a/scripts/build/complay.py +++ b/scripts/build/complay.py @@ -174,6 +174,17 @@ class LayoutChecker(Minifyer): self.handle_error('Element %s attribute %s "%s" is not a number' % (name, key, val)) return None + def check_bool_attribute(self, name, attrs, key, default): + if key not in attrs: + return default + val = attrs[key] + if self.VARPATTERN.match(val): + return None + elif val in self.YESNO: + return 'yes' == val + self.handle_error('Element %s attribute %s "%s" is not "yes" or "no"' % (name, key, val)) + return None + def check_parameter(self, attrs): if 'name' not in attrs: self.handle_error('Element param missing attribute name') @@ -253,8 +264,7 @@ class LayoutChecker(Minifyer): if self.check_int_attribute('orientation', attrs, 'rotate', 0) not in self.ORIENTATIONS: self.handle_error('Element orientation attribute rotate "%s" is unsupported' % (attrs['rotate'], )) for name in ('swapxy', 'flipx', 'flipy'): - if (attrs.get(name, 'no') not in self.YESNO) and (not self.VARPATTERN.match(attrs[name])): - self.handle_error('Element orientation attribute %s "%s" is not "yes" or "no"' % (name, attrs[name])) + self.check_bool_attribute('orientation', attrs, name, None) def check_color(self, attrs): self.check_color_channel(attrs, 'red') @@ -324,8 +334,8 @@ class LayoutChecker(Minifyer): self.handle_error('Element %s has inputraw attribute without inputtag attribute' % (name, )) inputmask = self.check_int_attribute(name, attrs, 'inputmask', None) if (inputmask is not None) and (not inputmask): - if (inputraw is None) or (not inputraw): - self.handle_error('Element %s attribute inputmask "%s" is zero' % (name, attrs['inputmask'])) + self.handle_error('Element %s attribute inputmask "%s" is zero' % (name, attrs['inputmask'])) + self.check_bool_attribute(name, attrs, 'clickthrough', None) def startViewItem(self, name): self.handlers.append((self.viewItemStartHandler, self.viewItemEndHandler)) @@ -676,8 +686,7 @@ class LayoutChecker(Minifyer): else: have_scroll[-1] = self.format_location() self.check_float_attribute(name, attrs, 'size', 1.0) - if (attrs.get('wrap', 'no') not in self.YESNO) and (not self.VARPATTERN.match(attrs['wrap'])): - self.handle_error('Element %s attribute wrap "%s" is not "yes" or "no"' % (name, attrs['wrap'])) + self.check_bool_attribute(name, attrs, 'wrap', False) if 'inputtag' in attrs: if 'name' in attrs: self.handle_error('Element %s has both attribute inputtag and attribute name' % (name, )) diff --git a/scripts/src/osd/modules.lua b/scripts/src/osd/modules.lua index 43cb768704d..db5e57ea570 100644 --- a/scripts/src/osd/modules.lua +++ b/scripts/src/osd/modules.lua @@ -63,6 +63,7 @@ function osdmodulesbuild() MAME_DIR .. "src/osd/interface/midiport.h", MAME_DIR .. "src/osd/interface/nethandler.cpp", MAME_DIR .. "src/osd/interface/nethandler.h", + MAME_DIR .. "src/osd/interface/uievents.h", MAME_DIR .. "src/osd/modules/debugger/debug_module.h", MAME_DIR .. "src/osd/modules/debugger/debuggdbstub.cpp", MAME_DIR .. "src/osd/modules/debugger/debugimgui.cpp", diff --git a/scripts/src/osd/windows_cfg.lua b/scripts/src/osd/windows_cfg.lua index 01d441c2214..85b0cd810f3 100644 --- a/scripts/src/osd/windows_cfg.lua +++ b/scripts/src/osd/windows_cfg.lua @@ -33,7 +33,7 @@ if _OPTIONS["MODERN_WIN_API"]=="1" then } else defines { - "_WIN32_WINNT=0x0600", + "_WIN32_WINNT=0x0602", "NTDDI_VERSION=0x06000000", } end diff --git a/src/emu/render.cpp b/src/emu/render.cpp index 617b1bf3756..2b9efa1c7fa 100644 --- a/src/emu/render.cpp +++ b/src/emu/render.cpp @@ -57,6 +57,7 @@ #include "util/xmlfile.h" #include <algorithm> +#include <limits> @@ -99,6 +100,40 @@ struct render_target::object_transform }; +struct render_target::pointer_info +{ + pointer_info() noexcept + : type(osd::ui_event_handler::pointer::UNKNOWN) + , oldpos(std::numeric_limits<float>::min(), std::numeric_limits<float>::min()) + , newpos(std::numeric_limits<float>::min(), std::numeric_limits<float>::min()) + , oldbuttons(0U) + , newbuttons(0U) + , edges(0U, 0U) + { + } + + osd::ui_event_handler::pointer type; + std::pair<float, float> oldpos; + std::pair<float, float> newpos; + u32 oldbuttons, newbuttons; + std::pair<size_t, size_t> edges; +}; + + +struct render_target::hit_test +{ + hit_test() noexcept + : inbounds(0U, 0U) + , hit(0U) + { + } + + std::pair<u64, u64> inbounds; + u64 hit; +}; + + + //************************************************************************** // GLOBAL VARIABLES @@ -883,19 +918,21 @@ render_container::user_settings::user_settings() // render_target - constructor //------------------------------------------------- -render_target::render_target(render_manager &manager, const internal_layout *layoutfile, u32 flags) - : render_target(manager, layoutfile, flags, CONSTRUCTOR_IMPL) +render_target::render_target(render_manager &manager, render_container *ui, const internal_layout *layoutfile, u32 flags) + : render_target(manager, ui, layoutfile, flags, CONSTRUCTOR_IMPL) { } -render_target::render_target(render_manager &manager, util::xml::data_node const &layout, u32 flags) - : render_target(manager, layout, flags, CONSTRUCTOR_IMPL) +render_target::render_target(render_manager &manager, render_container *ui, util::xml::data_node const &layout, u32 flags) + : render_target(manager, ui, layout, flags, CONSTRUCTOR_IMPL) { } -template <typename T> render_target::render_target(render_manager &manager, T &&layout, u32 flags, constructor_impl_t) +template <typename T> +render_target::render_target(render_manager &manager, render_container *ui, T &&layout, u32 flags, constructor_impl_t) : m_next(nullptr) , m_manager(manager) + , m_ui_container(ui) , m_curview(0U) , m_flags(flags) , m_listindex(0) @@ -1030,9 +1067,12 @@ void render_target::set_view(unsigned viewindex) { if (m_views.size() > viewindex) { + forget_pointers(); m_curview = viewindex; current_view().recompute(visibility_mask(), m_layerconfig.zoom_to_screen()); current_view().preload(); + m_clickable_items.clear(); + m_clickable_items.resize(current_view().interactive_items().size()); } } @@ -1050,6 +1090,229 @@ void render_target::set_max_texture_size(int maxwidth, int maxheight) //------------------------------------------------- +// pointer_updated - pointer activity within +// target +//------------------------------------------------- + +void render_target::pointer_updated( + osd::ui_event_handler::pointer type, + u16 ptrid, + u16 device, + s32 x, + s32 y, + u32 buttons, + u32 pressed, + u32 released, + s16 clicks) +{ + auto const target_f(map_point_layout(x, y)); + current_view().pointer_updated(type, ptrid, device, target_f.first, target_f.second, buttons, pressed, released, clicks); + + // 64 pointers ought to be enough for anyone + if (64 <= ptrid) + return; + + // just store the updated pointer state + if (m_pointers.size() <= ptrid) + m_pointers.resize(ptrid + 1); + m_pointers[ptrid].type = type; + m_pointers[ptrid].newpos = target_f; + m_pointers[ptrid].newbuttons = buttons; +} + + +//------------------------------------------------- +// pointer_left - pointer left target normally +//------------------------------------------------- + +void render_target::pointer_left( + osd::ui_event_handler::pointer type, + u16 ptrid, + u16 device, + s32 x, + s32 y, + u32 released, + s16 clicks) +{ + auto const target_f(map_point_layout(x, y)); + current_view().pointer_left(type, ptrid, device, target_f.first, target_f.second, released, clicks); + + // store the updated state if relevant + if (m_pointers.size() > ptrid) + { + m_pointers[ptrid].newpos = std::make_pair(std::numeric_limits<float>::min(), std::numeric_limits<float>::min()); + m_pointers[ptrid].newbuttons = 0; + } +} + + +//------------------------------------------------- +// pointer_aborted - pointer left target +// abnormally +//------------------------------------------------- + +void render_target::pointer_aborted( + osd::ui_event_handler::pointer type, + u16 ptrid, + u16 device, + s32 x, + s32 y, + u32 released, + s16 clicks) +{ + // let layout scripts handle pointer input + auto const target_f(map_point_layout(x, y)); + current_view().pointer_aborted(type, ptrid, device, target_f.first, target_f.second, released, clicks); + + // store the updated state if relevant + if (m_pointers.size() > ptrid) + { + m_pointers[ptrid].newpos = std::make_pair(std::numeric_limits<float>::min(), std::numeric_limits<float>::min()); + m_pointers[ptrid].newbuttons = 0; + } +} + + +//------------------------------------------------- +// forget_pointers - stop processing pointer +// input +//------------------------------------------------- + +void render_target::forget_pointers() +{ + current_view().forget_pointers(); + m_pointers.clear(); + for (size_t i = 0; m_clickable_items.size() > i; ++i) + { + if (m_clickable_items[i].hit) + { + layout_view_item const &item(current_view().interactive_items()[i]); + auto const [port, mask] = item.input_tag_and_mask(); + ioport_field *const field(port ? port->field(mask) : nullptr); + if (field) + field->set_value(0); + } + m_clickable_items[i] = hit_test(); + } +} + + +//------------------------------------------------- +// update_pointer_fields - update inputs for new +// pointer state +//------------------------------------------------- + +void render_target::update_pointer_fields() +{ + auto const &x_edges(current_view().interactive_edges_x()); + auto const &y_edges(current_view().interactive_edges_y()); + + // update items bounds intersection checks for pointers + for (size_t ptr = 0; m_pointers.size() > ptr; ++ptr) + { + auto const [x, y] = m_pointers[ptr].newpos; + auto edges = m_pointers[ptr].edges; + + // check for moving across horizontal edges + if (x < m_pointers[ptr].oldpos.first) + { + while (edges.first && (x < x_edges[edges.first - 1].position())) + { + --edges.first; + auto const &edge(x_edges[edges.first]); + if (edge.trailing()) + m_clickable_items[edge.index()].inbounds.first |= u64(1) << ptr; + else + m_clickable_items[edge.index()].inbounds.first &= ~(u64(1) << ptr); + } + } + else if (x > m_pointers[ptr].oldpos.first) + { + while ((x_edges.size() > edges.first) && (x >= x_edges[edges.first].position())) + { + auto const &edge(x_edges[edges.first]); + if (edge.trailing()) + m_clickable_items[edge.index()].inbounds.first &= ~(u64(1) << ptr); + else + m_clickable_items[edge.index()].inbounds.first |= u64(1) << ptr; + ++edges.first; + } + } + + // check for moving across vertical edges + if (y < m_pointers[ptr].oldpos.second) + { + while (edges.second && (y < y_edges[edges.second - 1].position())) + { + --edges.second; + auto const &edge(y_edges[edges.second]); + if (edge.trailing()) + m_clickable_items[edge.index()].inbounds.second |= u64(1) << ptr; + else + m_clickable_items[edge.index()].inbounds.second &= ~(u64(1) << ptr); + } + } + else if (y > m_pointers[ptr].oldpos.second) + { + while ((y_edges.size() > edges.second) && (y >= y_edges[edges.second].position())) + { + auto const &edge(y_edges[edges.second]); + if (edge.trailing()) + m_clickable_items[edge.index()].inbounds.second &= ~(u64(1) << ptr); + else + m_clickable_items[edge.index()].inbounds.second |= u64(1) << ptr; + ++edges.second; + } + } + + // update the pointer's state + m_pointers[ptr].oldpos = m_pointers[ptr].newpos; + m_pointers[ptr].oldbuttons = m_pointers[ptr].newbuttons; + m_pointers[ptr].edges = edges; + } + + // update item hit states + u64 obscured(0U); + for (size_t i = 0; m_clickable_items.size() > i; ++i) + { + layout_view_item const &item(current_view().interactive_items()[i]); + u64 const inbounds(m_clickable_items[i].inbounds.first & m_clickable_items[i].inbounds.second); + u64 hit(m_clickable_items[i].hit); + for (unsigned ptr = 0; m_pointers.size() > ptr; ++ptr) + { + pointer_info const &pointer(m_pointers[ptr]); + bool const prefilter(BIT(~obscured & inbounds, ptr)); + if (!prefilter || !BIT(pointer.newbuttons, 0) || !item.bounds().includes(pointer.newpos.first, pointer.newpos.second)) + { + hit &= ~(u64(1) << ptr); + } + else + { + hit |= u64(1) << ptr; + if (!item.clickthrough()) + obscured |= u64(1) << ptr; + } + } + + // update field state + if (bool(hit) != bool(m_clickable_items[i].hit)) + { + auto const [port, mask] = item.input_tag_and_mask(); + ioport_field *const field(port ? port->field(mask) : nullptr); + if (field) + { + if (hit) + field->set_value(1); + else + field->clear_value(); + } + } + m_clickable_items[i].hit = hit; + } +} + + +//------------------------------------------------- // set_visibility_toggle - show or hide selected // parts of a view //------------------------------------------------- @@ -1061,7 +1324,7 @@ void render_target::set_visibility_toggle(unsigned index, bool enable) m_views[m_curview].second |= u32(1) << index; else m_views[m_curview].second &= ~(u32(1) << index); - current_view().recompute(visibility_mask(), m_layerconfig.zoom_to_screen()); + update_layer_config(); current_view().preload(); } @@ -1454,8 +1717,8 @@ render_primitive_list &render_target::get_primitives() } } - // process the UI if we are the UI target - if (is_ui_target()) + // process UI elements if applicable + if (m_ui_container) { // compute the transform for the UI object_transform ui_xform; @@ -1468,7 +1731,7 @@ render_primitive_list &render_target::get_primitives() ui_xform.no_center = false; // add UI elements - add_container_primitives(list, root_xform, ui_xform, m_manager.ui_container(), BLENDMODE_ALPHA); + add_container_primitives(list, root_xform, ui_xform, *m_ui_container, BLENDMODE_ALPHA); } // optimize the list before handing it off @@ -1489,7 +1752,7 @@ bool render_target::map_point_container(s32 target_x, s32 target_y, render_conta std::pair<float, float> target_f(map_point_internal(target_x, target_y)); // explicitly check for the UI container - if (&container == &m_manager.ui_container()) + if (&container == m_ui_container) { // this hit test went against the UI container if ((target_f.first >= 0.0f) && (target_f.first < 1.0f) && (target_f.second >= 0.0f) && (target_f.second < 1.0f)) @@ -1536,74 +1799,6 @@ bool render_target::map_point_container(s32 target_x, s32 target_y, render_conta //------------------------------------------------- -// map_point_input - attempts to map a point on -// the specified render_target to an input port -// field, if possible -//------------------------------------------------- - -bool render_target::map_point_input(s32 target_x, s32 target_y, ioport_port *&input_port, ioport_value &input_mask, float &input_x, float &input_y) -{ - std::pair<float, float> target_f(map_point_internal(target_x, target_y)); - if (m_orientation & ORIENTATION_FLIP_X) - target_f.first = 1.0f - target_f.first; - if (m_orientation & ORIENTATION_FLIP_Y) - target_f.second = 1.0f - target_f.second; - if (m_orientation & ORIENTATION_SWAP_XY) - std::swap(target_f.first, target_f.second); - - auto const &items(current_view().interactive_items()); - m_hit_test.resize(items.size() * 2); - std::fill(m_hit_test.begin(), m_hit_test.end(), false); - - for (auto const &edge : current_view().interactive_edges_x()) - { - if ((edge.position() > target_f.first) || ((edge.position() == target_f.first) && edge.trailing())) - break; - else - m_hit_test[edge.index()] = !edge.trailing(); - } - - for (auto const &edge : current_view().interactive_edges_y()) - { - if ((edge.position() > target_f.second) || ((edge.position() == target_f.second) && edge.trailing())) - break; - else - m_hit_test[items.size() + edge.index()] = !edge.trailing(); - } - - for (unsigned i = 0; items.size() > i; ++i) - { - if (m_hit_test[i] && m_hit_test[items.size() + i]) - { - layout_view_item &item(items[i]); - render_bounds const bounds(item.bounds()); - if (bounds.includes(target_f.first, target_f.second)) - { - if (item.has_input()) - { - // point successfully mapped - std::tie(input_port, input_mask) = item.input_tag_and_mask(); - input_x = (target_f.first - bounds.x0) / bounds.width(); - input_y = (target_f.second - bounds.y0) / bounds.height(); - return true; - } - else - { - break; - } - } - } - } - - // default to point not mapped - input_port = nullptr; - input_mask = 0; - input_x = input_y = -1.0f; - return false; -} - - -//------------------------------------------------- // invalidate_all - if any of our primitive lists // contain a reference to the given pointer, // clear them @@ -1632,7 +1827,7 @@ void render_target::resolve_tags() for (layout_file &file : m_filelist) file.resolve_tags(); - current_view().recompute(visibility_mask(), m_layerconfig.zoom_to_screen()); + update_layer_config(); current_view().preload(); } @@ -1644,7 +1839,10 @@ void render_target::resolve_tags() void render_target::update_layer_config() { + forget_pointers(); current_view().recompute(visibility_mask(), m_layerconfig.zoom_to_screen()); + m_clickable_items.clear(); + m_clickable_items.resize(current_view().interactive_items().size()); } @@ -2620,6 +2818,25 @@ std::pair<float, float> render_target::map_point_internal(s32 target_x, s32 targ //------------------------------------------------- +// map_point_layout - map point from screen +// coordinates to layout coordinates +//------------------------------------------------- + +std::pair<float, float> render_target::map_point_layout(s32 target_x, s32 target_y) +{ + using std::swap; + std::pair<float, float> result(map_point_internal(target_x, target_y)); + if (m_orientation & ORIENTATION_FLIP_X) + result.first = 1.0f - result.first; + if (m_orientation & ORIENTATION_FLIP_Y) + result.second = 1.0f - result.second; + if (m_orientation & ORIENTATION_SWAP_XY) + swap(result.first, result.second); + return result; +} + + +//------------------------------------------------- // view_name - return the name of the indexed // view, or nullptr if it doesn't exist //------------------------------------------------- @@ -2698,12 +2915,11 @@ void render_target::config_load(util::xml::data_node const *targetnode) set_orientation(orientation_add(rotate, orientation())); // apply the opposite orientation to the UI - if (is_ui_target()) + if (m_ui_container) { - render_container &ui_container = m_manager.ui_container(); - render_container::user_settings settings = ui_container.get_user_settings(); + render_container::user_settings settings = m_ui_container->get_user_settings(); settings.m_orientation = orientation_add(orientation_reverse(rotate), settings.m_orientation); - ui_container.set_user_settings(settings); + m_ui_container->set_user_settings(settings); } } @@ -2743,6 +2959,8 @@ void render_target::config_load(util::xml::data_node const *targetnode) { current_view().recompute(visibility_mask(), m_layerconfig.zoom_to_screen()); current_view().preload(); + m_clickable_items.clear(); + m_clickable_items.resize(current_view().interactive_items().size()); } } } @@ -3104,7 +3322,6 @@ render_manager::render_manager(running_machine &machine) , m_ui_target(nullptr) , m_live_textures(0) , m_texture_id(0) - , m_ui_container(std::make_unique<render_container>(*this)) { // register callbacks machine.configuration().config_register( @@ -3125,7 +3342,7 @@ render_manager::render_manager(running_machine &machine) render_manager::~render_manager() { // free all the containers since they may own textures - m_ui_container.reset(); + m_ui_containers.clear(); m_screen_container_list.clear(); // better not be any outstanding textures when we die @@ -3181,12 +3398,14 @@ float render_manager::max_update_rate() const render_target *render_manager::target_alloc(const internal_layout *layoutfile, u32 flags) { - return &m_targetlist.append(*new render_target(*this, layoutfile, flags)); + render_container *const ui = (flags & RENDER_CREATE_HIDDEN) ? nullptr : &m_ui_containers.emplace_back(*this); + return &m_targetlist.append(*new render_target(*this, ui, layoutfile, flags)); } render_target *render_manager::target_alloc(util::xml::data_node const &layout, u32 flags) { - return &m_targetlist.append(*new render_target(*this, layout, flags)); + render_container *const ui = (flags & RENDER_CREATE_HIDDEN) ? nullptr : &m_ui_containers.emplace_back(*this); + return &m_targetlist.append(*new render_target(*this, ui, layout, flags)); } @@ -3223,35 +3442,56 @@ render_target *render_manager::target_by_index(int index) const float render_manager::ui_aspect(render_container *rc) { - int orient; + // work out if this is a UI container + render_target *target = nullptr; + if (!rc) + { + target = &ui_target(); + rc = target->ui_container(); + assert(rc); + } + else + { + for (render_target &t : m_targetlist) + { + if (t.ui_container() == rc) + { + target = &t; + break; + } + } + } + float aspect; - if (rc == m_ui_container.get() || rc == nullptr) { - // ui container, aggregated multi-screen target + if (target) + { + // UI container, aggregated multi-screen target - orient = orientation_add(m_ui_target->orientation(), m_ui_container->orientation()); // based on the orientation of the target, compute height/width or width/height + int const orient = orientation_add(target->orientation(), rc->orientation()); if (!(orient & ORIENTATION_SWAP_XY)) - aspect = (float)m_ui_target->height() / (float)m_ui_target->width(); + aspect = float(target->height()) / float(target->width()); else - aspect = (float)m_ui_target->width() / (float)m_ui_target->height(); + aspect = float(target->width()) / float(target->height()); // if we have a valid pixel aspect, apply that and return - if (m_ui_target->pixel_aspect() != 0.0f) + if (target->pixel_aspect() != 0.0f) { - float pixel_aspect = m_ui_target->pixel_aspect(); + float pixel_aspect = target->pixel_aspect(); if (orient & ORIENTATION_SWAP_XY) pixel_aspect = 1.0f / pixel_aspect; return aspect /= pixel_aspect; } - - } else { + } + else + { // single screen container - orient = rc->orientation(); // based on the orientation of the target, compute height/width or width/height + int const orient = rc->orientation(); if (!(orient & ORIENTATION_SWAP_XY)) aspect = (float)rc->screen()->visible_area().height() / (float)rc->screen()->visible_area().width(); else diff --git a/src/emu/render.h b/src/emu/render.h index 2faa5f46394..c4c0f504edd 100644 --- a/src/emu/render.h +++ b/src/emu/render.h @@ -48,6 +48,8 @@ #include "rendertypes.h" +#include "interface/uievents.h" + #include <cmath> #include <list> #include <memory> @@ -492,14 +494,15 @@ class render_target friend class render_manager; // construction/destruction - render_target(render_manager &manager, const internal_layout *layoutfile = nullptr, u32 flags = 0); - render_target(render_manager &manager, util::xml::data_node const &layout, u32 flags = 0); + render_target(render_manager &manager, render_container *ui, const internal_layout *layoutfile, u32 flags); + render_target(render_manager &manager, render_container *ui, util::xml::data_node const &layout, u32 flags); ~render_target(); public: // getters render_target *next() const { return m_next; } render_manager &manager() const { return m_manager; } + render_container *ui_container() const { return m_ui_container; } u32 width() const { return m_width; } u32 height() const { return m_height; } float pixel_aspect() const { return m_pixel_aspect; } @@ -525,6 +528,13 @@ public: void set_keepaspect(bool keepaspect) { m_keepaspect = keepaspect; } void set_scale_mode(int scale_mode) { m_scale_mode = scale_mode; } + // pointer input handling + void pointer_updated(osd::ui_event_handler::pointer type, u16 ptrid, u16 device, s32 x, s32 y, u32 buttons, u32 pressed, u32 released, s16 clicks); + void pointer_left(osd::ui_event_handler::pointer type, u16 ptrid, u16 device, s32 x, s32 y, u32 released, s16 clicks); + void pointer_aborted(osd::ui_event_handler::pointer type, u16 ptrid, u16 device, s32 x, s32 y, u32 released, s16 clicks); + void forget_pointers(); + void update_pointer_fields(); + // layer config getters bool screen_overlay_enabled() const { return m_layerconfig.screen_overlay_enabled(); } bool zoom_to_screen() const { return m_layerconfig.zoom_to_screen(); } @@ -550,7 +560,6 @@ public: // hit testing bool map_point_container(s32 target_x, s32 target_y, render_container &container, float &container_x, float &container_y); - bool map_point_input(s32 target_x, s32 target_y, ioport_port *&input_port, ioport_value &input_mask, float &input_x, float &input_y); // reference tracking void invalidate_all(void *refptr); @@ -559,15 +568,24 @@ public: void resolve_tags(); private: + // constants + static inline constexpr int NUM_PRIMLISTS = 3; + static inline constexpr int MAX_CLEAR_EXTENTS = 1000; + using view_mask_pair = std::pair<layout_view &, u32>; using view_mask_vector = std::vector<view_mask_pair>; // private classes declared in render.cpp struct object_transform; + struct pointer_info; + struct hit_test; + + using pointer_info_vector = std::vector<pointer_info>; + using hit_test_vector = std::vector<hit_test>; // internal helpers enum constructor_impl_t { CONSTRUCTOR_IMPL }; - template <typename T> render_target(render_manager &manager, T&& layout, u32 flags, constructor_impl_t); + template <typename T> render_target(render_manager &manager, render_container *ui, T&& layout, u32 flags, constructor_impl_t); void update_layer_config(); void load_layout_files(const internal_layout *layoutfile, bool singlefile); void load_layout_files(util::xml::data_node const &rootnode, bool singlefile); @@ -578,6 +596,7 @@ private: void add_container_primitives(render_primitive_list &list, const object_transform &root_xform, const object_transform &xform, render_container &container, int blendmode); void add_element_primitives(render_primitive_list &list, const object_transform &xform, layout_view_item &item); std::pair<float, float> map_point_internal(s32 target_x, s32 target_y); + std::pair<float, float> map_point_layout(s32 target_x, s32 target_y); // config callbacks void config_load(util::xml::data_node const *targetnode); @@ -593,13 +612,10 @@ private: void add_clear_extents(render_primitive_list &list); void add_clear_and_optimize_primitive_list(render_primitive_list &list); - // constants - static constexpr int NUM_PRIMLISTS = 3; - static constexpr int MAX_CLEAR_EXTENTS = 1000; - // internal state render_target * m_next; // link to next target render_manager & m_manager; // reference to our owning manager + render_container *const m_ui_container; // container for drawing UI elements std::list<layout_file> m_filelist; // list of layout files view_mask_vector m_views; // views we consider unsigned m_curview; // current view index @@ -618,7 +634,8 @@ private: float m_max_refresh; // maximum refresh rate, 0 or if none int m_orientation; // orientation render_layer_config m_layerconfig; // layer configuration - std::vector<bool> m_hit_test; // used when mapping points to inputs + pointer_info_vector m_pointers; // state of pointers over this target + hit_test_vector m_clickable_items; // for tracking clicked elements layout_view * m_base_view; // the view at the time of first frame int m_base_orientation; // the orientation at the time of first frame render_layer_config m_base_layerconfig; // the layer configuration at the time of first frame @@ -665,7 +682,7 @@ public: float ui_aspect(render_container *rc = nullptr); // UI containers - render_container &ui_container() const { assert(m_ui_container != nullptr); return *m_ui_container; } + render_container &ui_container() const { assert(ui_target().ui_container()); return *ui_target().ui_container(); } // textures render_texture *texture_alloc(texture_scaler_func scaler = nullptr, void *param = nullptr); @@ -686,20 +703,20 @@ private: void config_save(config_type cfg_type, util::xml::data_node *parentnode); // internal state - running_machine & m_machine; // reference back to the machine + running_machine & m_machine; // reference back to the machine // array of live targets - simple_list<render_target> m_targetlist; // list of targets - render_target * m_ui_target; // current UI target + simple_list<render_target> m_targetlist; // list of targets + render_target * m_ui_target; // current UI target // texture lists - u32 m_live_textures; // number of live textures - u64 m_texture_id; // rolling texture ID counter - fixed_allocator<render_texture> m_texture_allocator;// texture allocator + u32 m_live_textures; // number of live textures + u64 m_texture_id; // rolling texture ID counter + fixed_allocator<render_texture> m_texture_allocator; // texture allocator - // containers for the UI and for screens - std::unique_ptr<render_container> m_ui_container; // UI container - std::list<render_container> m_screen_container_list; // list of containers for the screen + // containers for UI elements and for screens + std::list<render_container> m_ui_containers; // containers for drawing UI elements + std::list<render_container> m_screen_container_list; // list of containers for the screen }; #endif // MAME_EMU_RENDER_H diff --git a/src/emu/rendlay.cpp b/src/emu/rendlay.cpp index c6e053fc713..de17b5dae6f 100644 --- a/src/emu/rendlay.cpp +++ b/src/emu/rendlay.cpp @@ -4251,6 +4251,7 @@ void layout_view::recompute(u32 visibility_mask, bool zoom_to_screen) // sort edges of interactive items LOGMASKED(LOG_INTERACTIVE_ITEMS, "Recalculated view '%s' with %u interactive items\n", name(), m_interactive_items.size()); + //std::reverse(m_interactive_items.begin(), m_interactive_items.end()); TODO: flip hit test order to match visual order m_interactive_edges_x.reserve(m_interactive_items.size() * 2); m_interactive_edges_y.reserve(m_interactive_items.size() * 2); for (unsigned i = 0; m_interactive_items.size() > i; ++i) @@ -4315,6 +4316,50 @@ void layout_view::set_recomputed_callback(recomputed_delegate &&handler) //------------------------------------------------- +// set_pointer_updated_callback - set handler +// called for pointer input +//------------------------------------------------- + +void layout_view::set_pointer_updated_callback(pointer_updated_delegate &&handler) +{ + m_pointer_updated = std::move(handler); +} + + +//------------------------------------------------- +// set_pointer_left_callback - set handler for +// pointer leaving normally +//------------------------------------------------- + +void layout_view::set_pointer_left_callback(pointer_left_delegate &&handler) +{ + m_pointer_left = std::move(handler); +} + + +//------------------------------------------------- +// set_pointer_aborted_callback - set handler for +// pointer leaving abnormally +//------------------------------------------------- + +void layout_view::set_pointer_aborted_callback(pointer_left_delegate &&handler) +{ + m_pointer_aborted = std::move(handler); +} + + +//------------------------------------------------- +// set_forget_pointers_callback - set handler for +// abandoning pointer input +//------------------------------------------------- + +void layout_view::set_forget_pointers_callback(forget_pointers_delegate &&handler) +{ + m_forget_pointers = std::move(handler); +} + + +//------------------------------------------------- // preload - perform expensive loading upfront // for visible elements //------------------------------------------------- diff --git a/src/emu/rendlay.h b/src/emu/rendlay.h index 0391bc6e961..7edeb4b40c5 100644 --- a/src/emu/rendlay.h +++ b/src/emu/rendlay.h @@ -16,6 +16,8 @@ #include "rendertypes.h" #include "screen.h" +#include "interface/uievents.h" + #include <array> #include <functional> #include <map> @@ -436,6 +438,9 @@ public: using prepare_items_delegate = delegate<void ()>; using preload_delegate = delegate<void ()>; using recomputed_delegate = delegate<void ()>; + using pointer_updated_delegate = delegate<void (osd::ui_event_handler::pointer, u16, u16, float, float, u32, u32, u32, s16)>; + using pointer_left_delegate = delegate<void (osd::ui_event_handler::pointer, u16, u16, float, float, u32, s16)>; + using forget_pointers_delegate = delegate<void ()>; using item = layout_view_item; using item_list = std::list<item>; @@ -524,9 +529,13 @@ public: bool has_art() const { return m_has_art; } // set handlers - void set_prepare_items_callback(prepare_items_delegate &&handler); - void set_preload_callback(preload_delegate &&handler); - void set_recomputed_callback(recomputed_delegate &&handler); + void set_prepare_items_callback(prepare_items_delegate &&handler) ATTR_COLD; + void set_preload_callback(preload_delegate &&handler) ATTR_COLD; + void set_recomputed_callback(recomputed_delegate &&handler) ATTR_COLD; + void set_pointer_updated_callback(pointer_updated_delegate &&handler) ATTR_COLD; + void set_pointer_left_callback(pointer_left_delegate &&handler) ATTR_COLD; + void set_pointer_aborted_callback(pointer_left_delegate &&handler) ATTR_COLD; + void set_forget_pointers_callback(forget_pointers_delegate &&handler) ATTR_COLD; // operations void prepare_items(); @@ -536,6 +545,28 @@ public: // resolve tags, if any void resolve_tags(); + // pointer input + void pointer_updated(osd::ui_event_handler::pointer type, u16 ptrid, u16 device, float x, float y, u32 buttons, u32 pressed, u32 released, s16 clicks) + { + if (!m_pointer_updated.isnull()) + m_pointer_updated(type, ptrid, device, x, y, buttons, pressed, released, clicks); + } + void pointer_left(osd::ui_event_handler::pointer type, u16 ptrid, u16 device, float x, float y, u32 released, s16 clicks) + { + if (!m_pointer_left.isnull()) + m_pointer_left(type, ptrid, device, x, y, released, clicks); + } + void pointer_aborted(osd::ui_event_handler::pointer type, u16 ptrid, u16 device, float x, float y, u32 released, s16 clicks) + { + if (!m_pointer_aborted.isnull()) + m_pointer_aborted(type, ptrid, device, x, y, released, clicks); + } + void forget_pointers() + { + if (!m_forget_pointers.isnull()) + m_forget_pointers(); + } + private: struct layer_lists; @@ -576,6 +607,10 @@ private: prepare_items_delegate m_prepare_items; // prepare items for adding to render container preload_delegate m_preload; // additional actions when visible items change recomputed_delegate m_recomputed; // additional actions on resizing/visibility change + pointer_updated_delegate m_pointer_updated; // pointer state updated + pointer_left_delegate m_pointer_left; // pointer left normally + pointer_left_delegate m_pointer_aborted; // pointer left abnormally + forget_pointers_delegate m_forget_pointers; // stop processing pointer input // cold items std::string m_name; // display name for the view diff --git a/src/emu/ui/uimain.h b/src/emu/ui/uimain.h index bf10957d743..e45c71f9468 100644 --- a/src/emu/ui/uimain.h +++ b/src/emu/ui/uimain.h @@ -13,6 +13,8 @@ #pragma once +#include <functional> + /*************************************************************************** TYPE DEFINITIONS @@ -35,6 +37,8 @@ public: virtual void menu_reset() { } + virtual bool set_ui_event_handler(std::function<bool ()> &&handler) { return false; } + template <typename Format, typename... Params> void popup_time(int seconds, Format &&fmt, Params &&... args); protected: diff --git a/src/emu/uiinput.cpp b/src/emu/uiinput.cpp index 138bf67055d..12ba8a08bb0 100644 --- a/src/emu/uiinput.cpp +++ b/src/emu/uiinput.cpp @@ -36,19 +36,11 @@ enum ui_input_manager::ui_input_manager(running_machine &machine) : m_machine(machine) , m_presses_enabled(true) - , m_current_mouse_target(nullptr) - , m_current_mouse_x(-1) - , m_current_mouse_y(-1) - , m_current_mouse_down(false) - , m_current_mouse_field(nullptr) , m_events_start(0) , m_events_end(0) { std::fill(std::begin(m_next_repeat), std::end(m_next_repeat), 0); std::fill(std::begin(m_seqpressed), std::end(m_seqpressed), 0); - - // add a frame callback to poll inputs - machine.add_notifier(MACHINE_NOTIFY_FRAME, machine_notify_delegate(&ui_input_manager::frame_update, this)); } @@ -58,12 +50,12 @@ ui_input_manager::ui_input_manager(running_machine &machine) ***************************************************************************/ /*------------------------------------------------- - frame_update - looks through pressed - input as per events pushed our way and posts + check_ui_inputs - looks through pressed input + as per events pushed our way and posts corresponding IPT_UI_* events -------------------------------------------------*/ -void ui_input_manager::frame_update() +void ui_input_manager::check_ui_inputs() { // update the state of all the UI keys for (ioport_type code = ioport_type(IPT_UI_FIRST + 1); code < IPT_UI_LAST; ++code) @@ -80,22 +72,6 @@ void ui_input_manager::frame_update() m_seqpressed[code] = false; } } - - // perform mouse hit testing - ioport_field *mouse_field = m_current_mouse_down ? find_mouse_field() : nullptr; - if (m_current_mouse_field != mouse_field) - { - // clear the old field if there was one - if (m_current_mouse_field != nullptr) - m_current_mouse_field->set_value(0); - - // set the new field if it exists and isn't already being pressed - if (mouse_field != nullptr && !mouse_field->digital_value()) - mouse_field->set_value(1); - - // update internal state - m_current_mouse_field = mouse_field; - } } @@ -106,37 +82,6 @@ void ui_input_manager::frame_update() bool ui_input_manager::push_event(ui_event evt) { - // some pre-processing (this is an icky place to do this stuff!) - switch (evt.event_type) - { - case ui_event::type::MOUSE_MOVE: - m_current_mouse_target = evt.target; - m_current_mouse_x = evt.mouse_x; - m_current_mouse_y = evt.mouse_y; - break; - - case ui_event::type::MOUSE_LEAVE: - if (m_current_mouse_target == evt.target) - { - m_current_mouse_target = nullptr; - m_current_mouse_x = -1; - m_current_mouse_y = -1; - } - break; - - case ui_event::type::MOUSE_DOWN: - m_current_mouse_down = true; - break; - - case ui_event::type::MOUSE_UP: - m_current_mouse_down = false; - break; - - default: - /* do nothing */ - break; - } - // is the queue filled up? if ((m_events_end + 1) % std::size(m_events) == m_events_start) return false; @@ -186,64 +131,12 @@ void ui_input_manager::reset() } -/*------------------------------------------------- - find_mouse - retrieves the current - location of the mouse --------------------------------------------------*/ - -render_target *ui_input_manager::find_mouse(s32 *x, s32 *y, bool *button) const -{ - if (x != nullptr) - *x = m_current_mouse_x; - if (y != nullptr) - *y = m_current_mouse_y; - if (button != nullptr) - *button = m_current_mouse_down; - return m_current_mouse_target; -} - - -/*------------------------------------------------- - find_mouse_field - retrieves the input field - the mouse is currently pointing at --------------------------------------------------*/ - -ioport_field *ui_input_manager::find_mouse_field() const -{ - // map the point and determine what was hit - if (m_current_mouse_target != nullptr) - { - ioport_port *port = nullptr; - ioport_value mask; - float x, y; - if (m_current_mouse_target->map_point_input(m_current_mouse_x, m_current_mouse_y, port, mask, x, y)) - { - if (port != nullptr) - return port->field(mask); - } - } - return nullptr; -} - - /*************************************************************************** USER INTERFACE SEQUENCE READING ***************************************************************************/ /*------------------------------------------------- - pressed - return true if a key down - for the given user interface sequence is - detected --------------------------------------------------*/ - -bool ui_input_manager::pressed(int code) -{ - return pressed_repeat(code, 0); -} - - -/*------------------------------------------------- pressed_repeat - return true if a key down for the given user interface sequence is detected, or if autorepeat at the given speed @@ -272,7 +165,7 @@ bool ui_input_manager::pressed_repeat(int code, int speed) /* if this is an autorepeat case, set a 1x delay and leave pressed = 1 */ else if (speed > 0 && (osd_ticks() + tps - m_next_repeat[code]) >= tps) { - // In the autorepeatcase, we need to double check the key is still pressed + // In the autorepeat case, we need to double-check the key is still pressed // as there can be a delay between the key polling and our processing of the event m_seqpressed[code] = machine().ioport().type_pressed(ioport_type(code)); pressed = (m_seqpressed[code] == SEQ_PRESSED_TRUE); @@ -319,105 +212,84 @@ void ui_input_manager::push_window_defocus_event(render_target *target) } /*------------------------------------------------- - push_mouse_move_event - pushes a mouse - move event to the specified render_target --------------------------------------------------*/ - -void ui_input_manager::push_mouse_move_event(render_target *target, s32 x, s32 y) -{ - ui_event event = { ui_event::type::NONE }; - event.event_type = ui_event::type::MOUSE_MOVE; - event.target = target; - event.mouse_x = x; - event.mouse_y = y; - push_event(event); -} - -/*------------------------------------------------- - push_mouse_leave_event - pushes a - mouse leave event to the specified render_target --------------------------------------------------*/ - -void ui_input_manager::push_mouse_leave_event(render_target *target) -{ - ui_event event = { ui_event::type::NONE }; - event.event_type = ui_event::type::MOUSE_LEAVE; - event.target = target; - push_event(event); -} - -/*------------------------------------------------- - push_mouse_down_event - pushes a mouse - down event to the specified render_target --------------------------------------------------*/ - -void ui_input_manager::push_mouse_down_event(render_target *target, s32 x, s32 y) -{ - ui_event event = { ui_event::type::NONE }; - event.event_type = ui_event::type::MOUSE_DOWN; - event.target = target; - event.mouse_x = x; - event.mouse_y = y; - push_event(event); -} - -/*------------------------------------------------- - push_mouse_down_event - pushes a mouse - down event to the specified render_target --------------------------------------------------*/ - -void ui_input_manager::push_mouse_up_event(render_target *target, s32 x, s32 y) -{ - ui_event event = { ui_event::type::NONE }; - event.event_type = ui_event::type::MOUSE_UP; - event.target = target; - event.mouse_x = x; - event.mouse_y = y; - push_event(event); -} - -/*------------------------------------------------- -push_mouse_down_event - pushes a mouse -down event to the specified render_target + push_pointer_update - pushes a pointer update + event to the specified render_target -------------------------------------------------*/ -void ui_input_manager::push_mouse_rdown_event(render_target *target, s32 x, s32 y) +void ui_input_manager::push_pointer_update( + render_target *target, + pointer type, + u16 ptrid, + u16 device, + s32 x, + s32 y, + u32 buttons, + u32 pressed, + u32 released, + s16 clicks) { ui_event event = { ui_event::type::NONE }; - event.event_type = ui_event::type::MOUSE_RDOWN; + event.event_type = ui_event::type::POINTER_UPDATE; event.target = target; - event.mouse_x = x; - event.mouse_y = y; + event.pointer_type = type; + event.pointer_id = ptrid; + event.pointer_device = device; + event.pointer_x = x; + event.pointer_y = y; + event.pointer_buttons = buttons; + event.pointer_pressed = pressed; + event.pointer_released = released; + event.pointer_clicks = clicks; push_event(event); } -/*------------------------------------------------- -push_mouse_down_event - pushes a mouse -down event to the specified render_target --------------------------------------------------*/ - -void ui_input_manager::push_mouse_rup_event(render_target *target, s32 x, s32 y) +void ui_input_manager::push_pointer_leave( + render_target *target, + pointer type, + u16 ptrid, + u16 device, + s32 x, + s32 y, + u32 released, + s16 clicks) { ui_event event = { ui_event::type::NONE }; - event.event_type = ui_event::type::MOUSE_RUP; + event.event_type = ui_event::type::POINTER_LEAVE; event.target = target; - event.mouse_x = x; - event.mouse_y = y; + event.pointer_type = type; + event.pointer_id = ptrid; + event.pointer_device = device; + event.pointer_x = x; + event.pointer_y = y; + event.pointer_buttons = 0U; + event.pointer_pressed = 0U; + event.pointer_released = released; + event.pointer_clicks = clicks; push_event(event); } -/*------------------------------------------------- - push_mouse_double_click_event - pushes - a mouse double-click event to the specified - render_target --------------------------------------------------*/ -void ui_input_manager::push_mouse_double_click_event(render_target *target, s32 x, s32 y) +void ui_input_manager::push_pointer_abort( + render_target *target, + pointer type, + u16 ptrid, + u16 device, + s32 x, + s32 y, + u32 released, + s16 clicks) { ui_event event = { ui_event::type::NONE }; - event.event_type = ui_event::type::MOUSE_DOUBLE_CLICK; + event.event_type = ui_event::type::POINTER_ABORT; event.target = target; - event.mouse_x = x; - event.mouse_y = y; + event.pointer_type = type; + event.pointer_id = ptrid; + event.pointer_device = device; + event.pointer_x = x; + event.pointer_y = y; + event.pointer_buttons = 0U; + event.pointer_pressed = 0U; + event.pointer_released = released; + event.pointer_clicks = clicks; push_event(event); } @@ -439,7 +311,7 @@ void ui_input_manager::push_char_event(render_target *target, char32_t ch) wheel event to the specified render_target -------------------------------------------------*/ -void ui_input_manager::push_mouse_wheel_event(render_target *target, s32 x, s32 y, short delta, int ucNumLines) +void ui_input_manager::push_mouse_wheel_event(render_target *target, s32 x, s32 y, short delta, int lines) { ui_event event = { ui_event::type::NONE }; event.event_type = ui_event::type::MOUSE_WHEEL; @@ -447,7 +319,7 @@ void ui_input_manager::push_mouse_wheel_event(render_target *target, s32 x, s32 event.mouse_x = x; event.mouse_y = y; event.zdelta = delta; - event.num_lines = ucNumLines; + event.num_lines = lines; push_event(event); } diff --git a/src/emu/uiinput.h b/src/emu/uiinput.h index 3fc89d7a4ff..54170bad7ec 100644 --- a/src/emu/uiinput.h +++ b/src/emu/uiinput.h @@ -13,6 +13,8 @@ #pragma once +#include "interface/uievents.h" + /*************************************************************************** TYPE DEFINITIONS @@ -25,17 +27,15 @@ struct ui_event NONE, WINDOW_FOCUS, WINDOW_DEFOCUS, - MOUSE_MOVE, - MOUSE_LEAVE, - MOUSE_DOWN, - MOUSE_UP, - MOUSE_RDOWN, - MOUSE_RUP, - MOUSE_DOUBLE_CLICK, MOUSE_WHEEL, + POINTER_UPDATE, + POINTER_LEAVE, + POINTER_ABORT, IME_CHAR }; + using pointer = osd::ui_event_handler::pointer; + type event_type; render_target * target; s32 mouse_x; @@ -44,17 +44,27 @@ struct ui_event char32_t ch; short zdelta; int num_lines; + + pointer pointer_type; // type of input controlling this pointer + u16 pointer_id; // pointer ID - will be recycled aggressively + u16 pointer_device; // for grouping pointers for multi-touch gesture recognition + s32 pointer_x; // pointer X coordinate + s32 pointer_y; // pointer Y coordinate + u32 pointer_buttons; // currently depressed buttons + u32 pointer_pressed; // buttons pressed since last update (primary action in LSB) + u32 pointer_released; // buttons released since last update (primary action in LSB) + s16 pointer_clicks; // positive for multi-click, negative on release if turned into hold or drag }; // ======================> ui_input_manager -class ui_input_manager +class ui_input_manager final : public osd::ui_event_handler { public: // construction/destruction ui_input_manager(running_machine &machine); - void frame_update(); + void check_ui_inputs(); // pushes a single event onto the queue bool push_event(ui_event event); @@ -68,17 +78,13 @@ public: // clears all outstanding events void reset(); - // retrieves the current location of the mouse - render_target *find_mouse(s32 *x, s32 *y, bool *button) const; - ioport_field *find_mouse_field() const; - - // return true if a key down for the given user interface sequence is detected - bool pressed(int code); - // enable/disable UI key presses bool presses_enabled() const { return m_presses_enabled; } void set_presses_enabled(bool enabled) { m_presses_enabled = enabled; } + // return true if a key down for the given user interface sequence is detected + bool pressed(int code) { return pressed_repeat(code, 0); } + // return true if a key down for the given user interface sequence is detected, or if autorepeat at the given speed is triggered bool pressed_repeat(int code, int speed); @@ -86,44 +92,32 @@ public: running_machine &machine() const { return m_machine; } // queueing events - void push_window_focus_event(render_target *target); - void push_window_defocus_event(render_target *target); - void push_mouse_move_event(render_target *target, s32 x, s32 y); - void push_mouse_leave_event(render_target *target); - void push_mouse_down_event(render_target *target, s32 x, s32 y); - void push_mouse_up_event(render_target *target, s32 x, s32 y); - void push_mouse_rdown_event(render_target *target, s32 x, s32 y); - void push_mouse_rup_event(render_target *target, s32 x, s32 y); - void push_mouse_double_click_event(render_target *target, s32 x, s32 y); - void push_char_event(render_target *target, char32_t ch); - void push_mouse_wheel_event(render_target *target, s32 x, s32 y, short delta, int ucNumLines); + virtual void push_window_focus_event(render_target *target) override; + virtual void push_window_defocus_event(render_target *target) override; + virtual void push_mouse_wheel_event(render_target *target, s32 x, s32 y, short delta, int lines) override; + virtual void push_pointer_update(render_target *target, pointer type, u16 ptrid, u16 device, s32 x, s32 y, u32 buttons, u32 pressed, u32 released, s16 clicks) override; + virtual void push_pointer_leave(render_target *target, pointer type, u16 ptrid, u16 device, s32 x, s32 y, u32 released, s16 clicks) override; + virtual void push_pointer_abort(render_target *target, pointer type, u16 ptrid, u16 device, s32 x, s32 y, u32 released, s16 clicks) override; + virtual void push_char_event(render_target *target, char32_t ch) override; void mark_all_as_pressed(); private: - // constants - static constexpr unsigned EVENT_QUEUE_SIZE = 128; + static constexpr unsigned EVENT_QUEUE_SIZE = 256; // internal state - running_machine & m_machine; // reference to our machine - - // pressed states; retrieved with ui_input_pressed() - bool m_presses_enabled; - osd_ticks_t m_next_repeat[IPT_COUNT]; - u8 m_seqpressed[IPT_COUNT]; - - // mouse position/info - render_target * m_current_mouse_target; - s32 m_current_mouse_x; - s32 m_current_mouse_y; - bool m_current_mouse_down; - ioport_field * m_current_mouse_field; - - // popped states; ring buffer of ui_events - ui_event m_events[EVENT_QUEUE_SIZE]; - int m_events_start; - int m_events_end; + running_machine & m_machine; + + // pressed states; retrieved with pressed() or pressed_repeat() + bool m_presses_enabled; + osd_ticks_t m_next_repeat[IPT_COUNT]; + u8 m_seqpressed[IPT_COUNT]; + + // ring buffer of ui_events + ui_event m_events[EVENT_QUEUE_SIZE]; + int m_events_start; + int m_events_end; }; #endif // MAME_EMU_UIINPUT_H diff --git a/src/frontend/mame/luaengine_input.cpp b/src/frontend/mame/luaengine_input.cpp index 42fe2d49d78..04db2a13e84 100644 --- a/src/frontend/mame/luaengine_input.cpp +++ b/src/frontend/mame/luaengine_input.cpp @@ -513,15 +513,6 @@ void lua_engine::initialize_input(sol::table &emu) auto uiinput_type = sol().registry().new_usertype<ui_input_manager>("uiinput", sol::no_constructor); uiinput_type.set_function("reset", &ui_input_manager::reset); - uiinput_type.set_function( - "find_mouse", - [] (ui_input_manager &ui) - { - int32_t x, y; - bool button; - render_target *rt = ui.find_mouse(&x, &y, &button); - return std::make_tuple(x, y, button, rt); - }); uiinput_type.set_function("pressed", &ui_input_manager::pressed); uiinput_type.set_function("pressed_repeat", &ui_input_manager::pressed_repeat); uiinput_type["presses_enabled"] = sol::property(&ui_input_manager::presses_enabled, &ui_input_manager::set_presses_enabled); diff --git a/src/frontend/mame/luaengine_mem.cpp b/src/frontend/mame/luaengine_mem.cpp index 4e5fac3341f..ff2f406828b 100644 --- a/src/frontend/mame/luaengine_mem.cpp +++ b/src/frontend/mame/luaengine_mem.cpp @@ -132,7 +132,7 @@ void share_write(memory_share &share, offs_t address, T val) int sol_lua_push(sol::types<map_handler_type>, lua_State *L, map_handler_type &&value) { const char *typestr; - switch(value) + switch (value) { case AMH_NONE: typestr = "none"; diff --git a/src/frontend/mame/luaengine_render.cpp b/src/frontend/mame/luaengine_render.cpp index 2568f72f95d..3fd0bfdba10 100644 --- a/src/frontend/mame/luaengine_render.cpp +++ b/src/frontend/mame/luaengine_render.cpp @@ -18,6 +18,7 @@ #include "rendlay.h" #include "rendutil.h" +#include "interface/uievents.h" #include "ioprocs.h" #include <algorithm> @@ -449,6 +450,33 @@ public: } // namespace sol +//------------------------------------------------- +// sol_lua_push - automatically convert +// osd::ui_event_handler::pointer to a string +//------------------------------------------------- + +int sol_lua_push(sol::types<osd::ui_event_handler::pointer>, lua_State *L, osd::ui_event_handler::pointer &&value) +{ + const char *typestr = "invalid"; + switch (value) + { + case osd::ui_event_handler::pointer::UNKNOWN: + typestr = "unknown"; + break; + case osd::ui_event_handler::pointer::MOUSE: + typestr = "mouse"; + break; + case osd::ui_event_handler::pointer::PEN: + typestr = "pen"; + break; + case osd::ui_event_handler::pointer::TOUCH: + typestr = "touch"; + break; + } + return sol::stack::push(L, typestr); +} + + template <typename T> class lua_engine::bitmap_helper : public T { @@ -931,6 +959,34 @@ void lua_engine::initialize_render(sol::table &emu) nullptr, "set_recomputed_callback", "recomputed")); + layout_view_type.set_function( + "set_pointer_updated_callback", + make_simple_callback_setter( + &layout_view::set_pointer_updated_callback, + nullptr, + "set_pointer_updated_callback", + "pointer updated")); + layout_view_type.set_function( + "set_pointer_left_callback", + make_simple_callback_setter( + &layout_view::set_pointer_left_callback, + nullptr, + "set_pointer_left_callback", + "pointer left")); + layout_view_type.set_function( + "set_pointer_aborted_callback", + make_simple_callback_setter( + &layout_view::set_pointer_aborted_callback, + nullptr, + "set_pointer_aborted_callback", + "pointer aborted")); + layout_view_type.set_function( + "set_forget_pointers_callback", + make_simple_callback_setter( + &layout_view::set_forget_pointers_callback, + nullptr, + "set_forget_pointers_callback", + "forget pointers")); layout_view_type["items"] = sol::property([] (layout_view &v) { return layout_view_items(v); }); layout_view_type["name"] = sol::property(&layout_view::name); layout_view_type["unqualified_name"] = sol::property(&layout_view::unqualified_name); diff --git a/src/frontend/mame/ui/about.cpp b/src/frontend/mame/ui/about.cpp index afb2b73d5d3..7b737273c3a 100644 --- a/src/frontend/mame/ui/about.cpp +++ b/src/frontend/mame/ui/about.cpp @@ -80,7 +80,7 @@ void menu_about::recompute_metrics(uint32_t width, uint32_t height, float aspect // perform our special rendering //------------------------------------------------- -void menu_about::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) +void menu_about::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) { // draw the title draw_text_box( @@ -120,17 +120,4 @@ void menu_about::populate() { } - -//------------------------------------------------- -// handle - manages inputs in the about modal -//------------------------------------------------- - -bool menu_about::handle(event const *ev) -{ - if (ev) - return handle_key(ev->iptkey); - else - return false; -} - } // namespace ui diff --git a/src/frontend/mame/ui/about.h b/src/frontend/mame/ui/about.h index 3e84aff7a5e..90e9d738fa3 100644 --- a/src/frontend/mame/ui/about.h +++ b/src/frontend/mame/ui/about.h @@ -30,13 +30,12 @@ public: protected: virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; - virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; virtual void populate_text(std::optional<text_layout> &layout, float &width, int &lines) override; private: virtual void populate() override; - virtual bool handle(event const *ev) override; std::vector<std::string> const m_header; }; diff --git a/src/frontend/mame/ui/analogipt.cpp b/src/frontend/mame/ui/analogipt.cpp index f8767ad3c58..aa6ffa5a07f 100644 --- a/src/frontend/mame/ui/analogipt.cpp +++ b/src/frontend/mame/ui/analogipt.cpp @@ -13,6 +13,8 @@ #include "ui/textbox.h" +#include "uiinput.h" + #include <algorithm> #include <iterator> #include <string> @@ -74,9 +76,20 @@ menu_analog::menu_analog(mame_ui_manager &mui, render_container &container) , m_item_data() , m_field_data() , m_prompt() - , m_visible_fields(0U) + , m_bottom_fields(0U) + , m_visible_fields(0) , m_top_field(0) , m_hide_menu(false) + , m_box_left(1.0F) + , m_box_top(1.0F) + , m_box_right(0.0F) + , m_box_bottom(0.0F) + , m_pointer_action(pointer_action::NONE) + , m_scroll_repeat(std::chrono::steady_clock::time_point::min()) + , m_base_pointer(0.0F, 0.0F) + , m_last_pointer(0.0F, 0.0F) + , m_scroll_base(0) + , m_arrow_clicked_first(false) { set_process_flags(PROCESS_LR_REPEAT); set_heading(_("menu-analoginput", "Analog Input Adjustments")); @@ -92,51 +105,50 @@ void menu_analog::recompute_metrics(uint32_t width, uint32_t height, float aspec { menu::recompute_metrics(width, height, aspect); + m_box_left = m_box_top = 1.0F; + m_box_right = m_box_bottom = 0.0F; + // space for live display - set_custom_space(0.0f, (line_height() * m_visible_fields) + (tb_border() * 3.0f)); + set_custom_space(0.0F, (line_height() * m_bottom_fields) + (tb_border() * 3.0F)); } -void menu_analog::custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) +void menu_analog::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float x, float y, float x2, float y2) { // work out how much space to use for field names - float const aspect(machine().render().ui_aspect(&container())); - float const extrawidth(0.4f + (((ui().box_lr_border() * 2.0f) + ui().get_line_height()) * aspect)); - float const nameavail(1.0f - (lr_border() * 2.0f) - extrawidth); - float namewidth(0.0f); + float const extrawidth(0.4F + (((ui().box_lr_border() * 2.0F) + ui().get_line_height()) * x_aspect())); + float const nameavail(1.0F - (lr_border() * 2.0F) - extrawidth); + float namewidth(0.0F); for (field_data &data : m_field_data) namewidth = (std::min)((std::max)(get_string_width(data.field.get().name()), namewidth), nameavail); // make a box or two rgb_t const fgcolor(ui().colors().text_color()); - float const boxleft((1.0f - namewidth - extrawidth) / 2.0f); - float const boxright(boxleft + namewidth + extrawidth); - float boxtop; - float boxbottom; + m_box_left = (1.0F - namewidth - extrawidth) * 0.5F; + m_box_right = m_box_left + namewidth + extrawidth; float firstliney; - int visible_fields; if (m_hide_menu) { if (m_prompt.empty()) m_prompt = util::string_format(_("menu-analoginput", "Press %s to show settings"), ui().get_general_input_setting(IPT_UI_ON_SCREEN_DISPLAY)); draw_text_box( &m_prompt, &m_prompt + 1, - boxleft, boxright, y - top, y - top + line_height() + (tb_border() * 2.0f), + m_box_left, m_box_right, y - top, y - top + line_height() + (tb_border() * 2.0F), text_layout::text_justify::CENTER, text_layout::word_wrapping::TRUNCATE, false, fgcolor, ui().colors().background_color()); - boxtop = y - top + line_height() + (tb_border() * 3.0f); - firstliney = y - top + line_height() + (tb_border() * 4.0f); - visible_fields = std::min<int>(m_field_data.size(), int((y2 + bottom - tb_border() - firstliney) / line_height())); - boxbottom = firstliney + (line_height() * visible_fields) + tb_border(); + m_box_top = y - top + line_height() + (tb_border() * 3.0F); + firstliney = y - top + line_height() + (tb_border() * 4.0F); + m_visible_fields = std::min<int>(m_field_data.size(), int((y2 + bottom - tb_border() - firstliney) / line_height())); + m_box_bottom = firstliney + (line_height() * m_visible_fields) + tb_border(); } else { - boxtop = y2 + tb_border(); - boxbottom = y2 + bottom; - firstliney = y2 + (tb_border() * 2.0f); - visible_fields = m_visible_fields; + m_box_top = y2 + tb_border(); + m_box_bottom = y2 + bottom; + firstliney = y2 + (tb_border() * 2.0F); + m_visible_fields = m_bottom_fields; } - ui().draw_outlined_box(container(), boxleft, boxtop, boxright, boxbottom, ui().colors().background_color()); + ui().draw_outlined_box(container(), m_box_left, m_box_top, m_box_right, m_box_bottom, ui().colors().background_color()); // force the field being configured to be visible ioport_field *const selfield(selectedref ? &reinterpret_cast<item_data *>(selectedref)->field.get() : nullptr); @@ -152,35 +164,44 @@ void menu_analog::custom_render(void *selectedref, float top, float bottom, floa auto const i(std::distance(m_field_data.begin(), found)); if (m_top_field > i) m_top_field = i; - if ((m_top_field + visible_fields) <= i) - m_top_field = i - m_visible_fields + 1; + if ((m_top_field + m_visible_fields) <= i) + m_top_field = i - m_bottom_fields + 1; } } if (0 > m_top_field) m_top_field = 0; - if ((m_top_field + visible_fields) > m_field_data.size()) - m_top_field = m_field_data.size() - visible_fields; + if ((m_top_field + m_visible_fields) > m_field_data.size()) + m_top_field = m_field_data.size() - m_visible_fields; // show live fields - namewidth += line_height() * aspect; - float const nameleft(boxleft + lr_border()); + namewidth += line_height() * x_aspect(); + float const nameleft(m_box_left + lr_border()); float const indleft(nameleft + namewidth); - float const indright(indleft + 0.4f); - for (unsigned line = 0; visible_fields > line; ++line) + float const indright(indleft + 0.4F); + for (unsigned line = 0; m_visible_fields > line; ++line) { // draw arrows if scrolling is possible and menu is hidden - float const liney(firstliney + (line_height() * line)); + float const liney(firstliney + (line_height() * float(line))); if (m_hide_menu) { bool const uparrow(!line && m_top_field); - bool const downarrow(((visible_fields - 1) == line) && ((m_field_data.size() - 1) > (line + m_top_field))); + bool const downarrow(((m_visible_fields - 1) == line) && ((m_field_data.size() - 1) > (line + m_top_field))); if (uparrow || downarrow) { - float const arrowwidth = line_height() * aspect; + bool const active((uparrow && (pointer_action::SCROLL_UP == m_pointer_action)) || (downarrow && (pointer_action::SCROLL_DOWN == m_pointer_action))); + bool const hovered((active || pointer_idle()) && pointer_in_rect(nameleft, liney, indright, liney + line_height())); + float const arrowwidth(line_height() * x_aspect()); + rgb_t const arrowcolor(!(active || hovered) ? fgcolor : (active && hovered) ? ui().colors().selected_color() : ui().colors().mouseover_color()); + if (active || hovered) + { + highlight( + nameleft, liney, indright, liney + line_height(), + (active && hovered) ? ui().colors().selected_bg_color() : ui().colors().mouseover_bg_color()); + } draw_arrow( - 0.5f * (nameleft + indright - arrowwidth), liney + (0.25f * line_height()), - 0.5f * (nameleft + indright + arrowwidth), liney + (0.75f * line_height()), - fgcolor, line ? (ROT0 ^ ORIENTATION_FLIP_Y) : ROT0); + 0.5F * (nameleft + indright - arrowwidth), liney + (0.25F * line_height()), + 0.5F * (nameleft + indright + arrowwidth), liney + (0.75F * line_height()), + arrowcolor, line ? (ROT0 ^ ORIENTATION_FLIP_Y) : ROT0); continue; } } @@ -200,42 +221,201 @@ void menu_analog::custom_render(void *selectedref, float top, float bottom, floa cur = ((cur >> data.shift) - data.field.get().minval()) & (data.field.get().mask() >> data.shift); float fill(float(cur) / data.range); if (data.field.get().analog_reverse()) - fill = 1.0f - fill; + fill = 1.0F - fill; - float const indtop(liney + (line_height() * 0.2f)); - float const indbottom(liney + (line_height() * 0.8f)); + float const indtop(liney + (line_height() * 0.2F)); + float const indbottom(liney + (line_height() * 0.8F)); if (data.origin > fill) - container().add_rect(indleft + (fill * 0.4f), indtop, indleft + (data.origin * 0.4f), indbottom, fgcolor, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + container().add_rect(indleft + (fill * 0.4F), indtop, indleft + (data.origin * 0.4F), indbottom, fgcolor, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); else - container().add_rect(indleft + (data.origin * 0.4f), indtop, indleft + (fill * 0.4f), indbottom, fgcolor, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + container().add_rect(indleft + (data.origin * 0.4F), indtop, indleft + (fill * 0.4F), indbottom, fgcolor, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); container().add_line(indleft, indtop, indright, indtop, UI_LINE_WIDTH, fgcolor, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); container().add_line(indright, indtop, indright, indbottom, UI_LINE_WIDTH, fgcolor, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); container().add_line(indright, indbottom, indleft, indbottom, UI_LINE_WIDTH, fgcolor, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); container().add_line(indleft, indbottom, indleft, indtop, UI_LINE_WIDTH, fgcolor, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); if (data.show_neutral) - container().add_line(indleft + (data.neutral * 0.4f), indtop, indleft + (data.neutral * 0.4f), indbottom, UI_LINE_WIDTH, fgcolor, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + container().add_line(indleft + (data.neutral * 0.4F), indtop, indleft + (data.neutral * 0.4F), indbottom, UI_LINE_WIDTH, fgcolor, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); } } +std::tuple<int, bool, bool> menu_analog::custom_pointer_updated(bool changed, ui_event const &uievt) +{ + // no pointer input if we don't have up-to-date content on-screen + if ((m_box_left > m_box_right) || (ui_event::type::POINTER_ABORT == uievt.event_type)) + { + m_pointer_action = pointer_action::NONE; + return std::make_tuple(IPT_INVALID, false, false); + } + + // if nothing's happening, check for clicks + if (pointer_idle()) + { + if ((uievt.pointer_pressed & 0x01) && !(uievt.pointer_buttons & ~u32(0x01))) + { + if (1 == uievt.pointer_clicks) + m_arrow_clicked_first = false; + + float const firstliney(m_box_top + tb_border()); + float const fieldleft(m_box_left + lr_border()); + float const fieldright(m_box_right - lr_border()); + auto const [x, y] = pointer_location(); + bool const inwidth((x >= fieldleft) && (x < fieldright)); + if (m_hide_menu && m_top_field && inwidth && (y >= firstliney) && (y < (firstliney + line_height()))) + { + // scroll up arrow + if (1 == uievt.pointer_clicks) + m_arrow_clicked_first = true; + + --m_top_field; + m_pointer_action = pointer_action::SCROLL_UP; + m_scroll_repeat = std::chrono::steady_clock::now() + std::chrono::milliseconds(300); + m_last_pointer = std::make_pair(x, y); + return std::make_tuple(IPT_INVALID, true, true); + } + else if (m_hide_menu && ((m_top_field + m_visible_fields) < m_field_data.size()) && inwidth && (y >= (firstliney + (float(m_visible_fields - 1) * line_height()))) && (y < (firstliney + (float(m_visible_fields) * line_height())))) + { + // scroll down arrow + if (1 == uievt.pointer_clicks) + m_arrow_clicked_first = true; + + ++m_top_field; + m_pointer_action = pointer_action::SCROLL_DOWN; + m_scroll_repeat = std::chrono::steady_clock::now() + std::chrono::milliseconds(300); + m_last_pointer = std::make_pair(x, y); + return std::make_tuple(IPT_INVALID, true, true); + } + else if ((x >= m_box_left) && (x < m_box_right) && (y >= m_box_top) && (y < m_box_bottom)) + { + if (!m_arrow_clicked_first && (2 == uievt.pointer_clicks)) + { + // toggle menu display + // FIXME: this should really use the start point of the multi-click action + m_pointer_action = pointer_action::CHECK_TOGGLE_MENU; + m_base_pointer = std::make_pair(x, y); + return std::make_tuple(IPT_INVALID, true, false); + } + else if (ui_event::pointer::TOUCH == uievt.pointer_type) + { + m_pointer_action = pointer_action::SCROLL_DRAG; + m_base_pointer = std::make_pair(x, y); + m_last_pointer = m_base_pointer; + m_scroll_base = m_top_field; + return std::make_tuple(IPT_INVALID, true, false); + } + } + } + return std::make_tuple(IPT_INVALID, false, false); + } + + // handle in-progress actions + switch (m_pointer_action) + { + case pointer_action::NONE: + break; + + case pointer_action::SCROLL_UP: + case pointer_action::SCROLL_DOWN: + { + // check for re-entry + bool redraw(false); + float const linetop(m_box_top + tb_border() + ((pointer_action::SCROLL_DOWN == m_pointer_action) ? (float(m_visible_fields - 1) * line_height()) : 0.0F)); + float const linebottom(linetop + line_height()); + auto const [x, y] = pointer_location(); + bool const reentered(reentered_rect(m_last_pointer.first, m_last_pointer.second, x, y, m_box_left + lr_border(), linetop, m_box_right - lr_border(), linebottom)); + if (reentered) + { + auto const now(std::chrono::steady_clock::now()); + if (scroll_if_expired(now)) + { + redraw = true; + m_scroll_repeat = now + std::chrono::milliseconds(100); + } + } + m_last_pointer = std::make_pair(x, y); + if ((uievt.pointer_released & 0x01) || (uievt.pointer_pressed & ~u32(0x01))) + m_pointer_action = pointer_action::NONE; + return std::make_tuple(IPT_INVALID, pointer_action::NONE != m_pointer_action, redraw); + } + break; + + case pointer_action::SCROLL_DRAG: + { + bool const scrolled(update_scroll_drag(uievt)); + return std::make_tuple(IPT_INVALID, pointer_action::NONE != m_pointer_action, scrolled); + } + + case pointer_action::CHECK_TOGGLE_MENU: + if ((ui_event::pointer::TOUCH == uievt.pointer_type) && (0 > uievt.pointer_clicks)) + { + // converted to hold/drag - treat as scroll if it's touch + m_pointer_action = pointer_action::SCROLL_DRAG; + m_last_pointer = m_base_pointer; + m_scroll_base = m_top_field; + bool const scrolled(update_scroll_drag(uievt)); + return std::make_tuple(IPT_INVALID, pointer_action::NONE != m_pointer_action, scrolled); + } + else if (uievt.pointer_released & 0x01) + { + // primary button released - simulate the on-screen display key if it wasn't converted to a hold/drag + return std::make_tuple((2 == uievt.pointer_clicks) ? IPT_UI_ON_SCREEN_DISPLAY : IPT_INVALID, false, false); + } + else if ((2 != uievt.pointer_clicks) || (uievt.pointer_buttons & ~u32(0x01))) + { + // treat converting to a hold/drag or pressing another button as cancelling the action + return std::make_tuple(IPT_INVALID, false, false); + } + return std::make_tuple(IPT_INVALID, true, false); + } + return std::make_tuple(IPT_INVALID, false, false); +} + + void menu_analog::menu_activated() { // scripts could have changed something in the mean time m_item_data.clear(); m_field_data.clear(); reset(reset_options::REMEMBER_POSITION); + + m_box_left = m_box_top = 1.0F; + m_box_right = m_box_bottom = 0.0F; + m_pointer_action = pointer_action::NONE; + m_arrow_clicked_first = false; } bool menu_analog::handle(event const *ev) { + // deal with repeating scroll arrows + bool scrolled(false); + if ((pointer_action::SCROLL_UP == m_pointer_action) || (pointer_action::SCROLL_DOWN == m_pointer_action)) + { + float const linetop(m_box_top + tb_border() + ((pointer_action::SCROLL_DOWN == m_pointer_action) ? (float(m_visible_fields - 1) * line_height()) : 0.0F)); + float const linebottom(linetop + line_height()); + if (pointer_in_rect(m_box_left + lr_border(), linetop, m_box_right - lr_border(), linebottom)) + { + while (scroll_if_expired(std::chrono::steady_clock::now())) + { + scrolled = true; + m_scroll_repeat += std::chrono::milliseconds(100); + } + } + } + if (!ev) { - return false; + return scrolled; } else if (IPT_UI_ON_SCREEN_DISPLAY == ev->iptkey) { m_hide_menu = !m_hide_menu; + + m_box_left = m_box_top = 1.0F; + m_box_right = m_box_bottom = 0.0F; + m_pointer_action = pointer_action::NONE; + m_arrow_clicked_first = false; + set_process_flags(PROCESS_LR_REPEAT | (m_hide_menu ? (PROCESS_CUSTOM_NAV | PROCESS_CUSTOM_ONLY) : 0)); return true; } @@ -254,26 +434,53 @@ bool menu_analog::handle(event const *ev) ui().get_general_input_setting(IPT_UI_PREV_GROUP), ui().get_general_input_setting(IPT_UI_NEXT_GROUP), ui().get_general_input_setting(IPT_UI_BACK))); - return false; } else if (m_hide_menu) { switch (ev->iptkey) { case IPT_UI_UP: - --m_top_field; - return true; + if (m_top_field) + { + --m_top_field; + return true; + } + break; case IPT_UI_DOWN: - ++m_top_field; - return true; + if ((m_top_field + m_visible_fields) < m_field_data.size()) + { + ++m_top_field; + return true; + } + break; + case IPT_UI_PAGE_UP: + if (m_visible_fields) + { + m_top_field -= std::min<int>(m_visible_fields - 3, m_top_field); + return true; + } + break; + case IPT_UI_PAGE_DOWN: + if ((m_top_field + m_visible_fields) < m_field_data.size()) + { + m_top_field = std::min<int>(m_top_field + m_visible_fields - 3, m_field_data.size() - m_visible_fields); + return true; + } + break; case IPT_UI_HOME: - m_top_field = 0; - return true; + if (m_top_field) + { + m_top_field = 0; + return true; + } + break; case IPT_UI_END: - m_top_field = m_field_data.size(); - return true; - default: - return false; + if ((m_top_field + m_visible_fields) < m_field_data.size()) + { + m_top_field = m_field_data.size() - m_visible_fields; + return true; + } + break; } } else if (ev->itemref) @@ -384,15 +591,9 @@ bool menu_analog::handle(event const *ev) ev->item->set_flags((data.cur <= data.min) ? FLAG_RIGHT_ARROW : (data.cur >= data.max) ? FLAG_LEFT_ARROW : FLAG_LEFT_ARROW | FLAG_RIGHT_ARROW); return true; } - else - { - return false; - } - } - else - { - return false; } + + return scrolled; } @@ -466,7 +667,7 @@ void menu_analog::populate() item_append(menu_item_type::SEPARATOR); // space for live display - set_custom_space(0.0f, (line_height() * m_visible_fields) + (tb_border() * 3.0f)); + set_custom_space(0.0F, (line_height() * m_bottom_fields) + (tb_border() * 3.0F)); } @@ -518,10 +719,72 @@ void menu_analog::find_fields() } // restrict live display to 40% height plus borders - if ((line_height() * m_field_data.size()) > 0.4f) - m_visible_fields = unsigned(0.4f / line_height()); + if ((line_height() * m_field_data.size()) > 0.4F) + m_bottom_fields = unsigned(0.4F / line_height()); + else + m_bottom_fields = m_field_data.size(); +} + + +bool menu_analog::scroll_if_expired(std::chrono::steady_clock::time_point now) +{ + if (now < m_scroll_repeat) + return false; + + if (pointer_action::SCROLL_DOWN == m_pointer_action) + { + if ((m_top_field + m_visible_fields) < m_field_data.size()) + ++m_top_field; + if ((m_top_field + m_visible_fields) == m_field_data.size()) + m_pointer_action = pointer_action::NONE; + } else - m_visible_fields = m_field_data.size(); + { + if (0 < m_top_field) + --m_top_field; + if (!m_top_field) + m_pointer_action = pointer_action::NONE; + } + return true; +} + + +bool menu_analog::update_scroll_drag(ui_event const &uievt) +{ + // set thresholds depending on the direction for hysteresis + float const y(pointer_location().second); + float const base(m_base_pointer.second + (line_height() * ((y > m_last_pointer.second) ? -0.3F : 0.3F))); + auto const target(int((base - y) / line_height())); + m_last_pointer.second = base + (float(target) * line_height()); + + // scroll if it moved + int newtop(std::clamp<int>(m_scroll_base + target, 0, m_field_data.size() - m_visible_fields)); + if (!m_hide_menu && (newtop != m_top_field)) + { + // if the menu is visible, keep the highlighted field on-screen + void *const selectedref(get_selection_ref()); + ioport_field *const selfield(selectedref ? &reinterpret_cast<item_data *>(selectedref)->field.get() : nullptr); + if (selfield) + { + auto const found( + std::find_if( + m_field_data.begin(), + m_field_data.end(), + [selfield] (field_data const &d) { return &d.field.get() == selfield; })); + if (m_field_data.end() != found) + { + auto const i(std::distance(m_field_data.begin(), found)); + newtop = std::clamp<int>(newtop, i + 1 - m_visible_fields, i); + } + } + } + bool const scrolled(newtop != m_top_field); + m_top_field = newtop; + + // catch the end of the gesture + if ((uievt.pointer_released & 0x01) || (uievt.pointer_pressed & ~u32(0x01))) + m_pointer_action = pointer_action::NONE; + return scrolled; } diff --git a/src/frontend/mame/ui/analogipt.h b/src/frontend/mame/ui/analogipt.h index d60aafd6cc4..ee59954911c 100644 --- a/src/frontend/mame/ui/analogipt.h +++ b/src/frontend/mame/ui/analogipt.h @@ -14,7 +14,10 @@ #include "ui/menu.h" +#include <chrono> #include <functional> +#include <tuple> +#include <utility> #include <vector> @@ -28,10 +31,20 @@ public: protected: virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; - virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; + virtual std::tuple<int, bool, bool> custom_pointer_updated(bool changed, ui_event const &uievt) override; virtual void menu_activated() override; private: + enum class pointer_action + { + NONE, + SCROLL_UP, + SCROLL_DOWN, + SCROLL_DRAG, + CHECK_TOGGLE_MENU + }; + enum { ANALOG_ITEM_KEYSPEED = 0, @@ -71,15 +84,30 @@ private: virtual bool handle(event const *ev) override; void find_fields(); + bool scroll_if_expired(std::chrono::steady_clock::time_point now); + bool update_scroll_drag(ui_event const &uievt); static std::string item_text(int type, int value); item_data_vector m_item_data; field_data_vector m_field_data; std::string m_prompt; - unsigned m_visible_fields; + unsigned m_bottom_fields; + int m_visible_fields; int m_top_field; bool m_hide_menu; + + float m_box_left; + float m_box_top; + float m_box_right; + float m_box_bottom; + + pointer_action m_pointer_action; + std::chrono::steady_clock::time_point m_scroll_repeat; + std::pair<float, float> m_base_pointer; + std::pair<float, float> m_last_pointer; + int m_scroll_base; + bool m_arrow_clicked_first; }; } // namespace ui diff --git a/src/frontend/mame/ui/auditmenu.cpp b/src/frontend/mame/ui/auditmenu.cpp index d225535c4e7..180c3005b73 100644 --- a/src/frontend/mame/ui/auditmenu.cpp +++ b/src/frontend/mame/ui/auditmenu.cpp @@ -83,7 +83,7 @@ void menu_audit::recompute_metrics(uint32_t width, uint32_t height, float aspect } -void menu_audit::custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) +void menu_audit::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float x, float y, float x2, float y2) { switch (m_phase) { diff --git a/src/frontend/mame/ui/auditmenu.h b/src/frontend/mame/ui/auditmenu.h index 5d71b63fd2f..3a1c236549d 100644 --- a/src/frontend/mame/ui/auditmenu.h +++ b/src/frontend/mame/ui/auditmenu.h @@ -30,7 +30,7 @@ public: protected: virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; - virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; virtual bool custom_ui_back() override; private: diff --git a/src/frontend/mame/ui/confswitch.cpp b/src/frontend/mame/ui/confswitch.cpp index 946062b9a1d..f2f8a241d98 100644 --- a/src/frontend/mame/ui/confswitch.cpp +++ b/src/frontend/mame/ui/confswitch.cpp @@ -11,6 +11,8 @@ #include "emu.h" #include "ui/confswitch.h" +#include "uiinput.h" + #include <algorithm> #include <cstring> @@ -176,6 +178,13 @@ bool menu_confswitch::handle(event const *ev) if (!ev || !ev->itemref) return false; + if (IPT_CUSTOM == ev->iptkey) + { + // clicked a switch + reset(reset_options::REMEMBER_REF); + return true; + } + if (uintptr_t(ev->itemref) == 1U) { // reset @@ -339,16 +348,15 @@ void menu_settings_dip_switches::recompute_metrics(uint32_t width, uint32_t heig } -void menu_settings_dip_switches::custom_render(void *selectedref, float top, float bottom, float x1, float y1, float x2, float y2) +void menu_settings_dip_switches::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float x1, float y1, float x2, float y2) { // catch if no DIP locations have to be drawn if (!m_visible_switch_groups) return; // calculate optimal width - float const aspect(machine().render().ui_aspect(&container())); float const maxwidth(1.0f - (lr_border() * 2.0f)); - m_single_width = (line_height() * SINGLE_TOGGLE_SWITCH_FIELD_WIDTH * aspect); + m_single_width = (line_height() * SINGLE_TOGGLE_SWITCH_FIELD_WIDTH * x_aspect()); float width(0.0f); unsigned maxswitches(0U); for (switch_group_descriptor const &group : switch_groups()) @@ -358,7 +366,7 @@ void menu_settings_dip_switches::custom_render(void *selectedref, float top, flo maxswitches = (std::max)(group.switch_count(), maxswitches); float const namewidth(get_string_width(group.name)); float const switchwidth(m_single_width * maxswitches); - width = (std::min)((std::max)(namewidth + switchwidth + (line_height() * aspect), width), maxwidth); + width = (std::min)((std::max)(namewidth + switchwidth + (line_height() * x_aspect()), width), maxwidth); } } @@ -370,12 +378,12 @@ void menu_settings_dip_switches::custom_render(void *selectedref, float top, flo // calculate centred layout float const nameleft((1.0f - width) * 0.5f); float const switchleft(nameleft + width - (m_single_width * maxswitches)); - float const namewidth(width - (m_single_width * maxswitches) - (line_height() * aspect)); + float const namewidth(width - (m_single_width * maxswitches) - (line_height() * x_aspect())); // iterate over switch groups ioport_field *const field((uintptr_t(selectedref) != 1U) ? reinterpret_cast<ioport_field *>(selectedref) : nullptr); float const nubheight(line_height() * SINGLE_TOGGLE_SWITCH_HEIGHT); - m_nub_width = line_height() * SINGLE_TOGGLE_SWITCH_WIDTH * aspect; + m_nub_width = line_height() * SINGLE_TOGGLE_SWITCH_WIDTH * x_aspect(); float const ygap(line_height() * ((DIP_SWITCH_HEIGHT * 0.5f) - SINGLE_TOGGLE_SWITCH_HEIGHT) * 0.5f); float const xgap((m_single_width + (UI_LINE_WIDTH * 0.5f) - m_nub_width) * 0.5f); m_first_nub = switchleft + xgap; @@ -449,13 +457,16 @@ void menu_settings_dip_switches::custom_render(void *selectedref, float top, flo } -bool menu_settings_dip_switches::custom_mouse_down() +std::tuple<int, bool, bool> menu_settings_dip_switches::custom_pointer_updated(bool changed, ui_event const &uievt) { - if (!m_visible_switch_groups || (get_mouse_x() < m_first_nub)) - return false; + if (!m_visible_switch_groups || !(uievt.pointer_pressed & 0x01) || (uievt.pointer_buttons & ~u32(0x01))) + return std::make_tuple(IPT_INVALID, false, false); + + auto const [cx, y] = pointer_location(); + if (cx < m_first_nub) + return std::make_tuple(IPT_INVALID, false, false); - float const x(get_mouse_x() - m_first_nub); - float const y(get_mouse_y()); + float const x(cx - m_first_nub); for (unsigned n = 0U, line = 0U; (switch_groups().size() > n) && (m_visible_switch_groups > line); ++n) { switch_group_descriptor const &group(switch_groups()[n]); @@ -476,8 +487,7 @@ bool menu_settings_dip_switches::custom_mouse_down() group.toggles[i].field->get_user_settings(settings); settings.value ^= group.toggles[i].mask; group.toggles[i].field->set_user_settings(settings); - reset(reset_options::REMEMBER_REF); - return true; + return std::make_tuple(IPT_CUSTOM, true, true); } } } @@ -485,7 +495,7 @@ bool menu_settings_dip_switches::custom_mouse_down() } } - return false; + return std::make_tuple(IPT_INVALID, false, false); } diff --git a/src/frontend/mame/ui/confswitch.h b/src/frontend/mame/ui/confswitch.h index 2d025f7dfc8..7f00783b0f1 100644 --- a/src/frontend/mame/ui/confswitch.h +++ b/src/frontend/mame/ui/confswitch.h @@ -83,8 +83,8 @@ public: protected: virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; - virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; - virtual bool custom_mouse_down() override; + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; + virtual std::tuple<int, bool, bool> custom_pointer_updated(bool changed, ui_event const &uievt) override; private: virtual void populate() override; diff --git a/src/frontend/mame/ui/custui.cpp b/src/frontend/mame/ui/custui.cpp index 1db4a64ba13..9cfd7284d9b 100644 --- a/src/frontend/mame/ui/custui.cpp +++ b/src/frontend/mame/ui/custui.cpp @@ -548,7 +548,7 @@ void menu_font_ui::recompute_metrics(uint32_t width, uint32_t height, float aspe // perform our special rendering //------------------------------------------------- -void menu_font_ui::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) +void menu_font_ui::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) { if (uintptr_t(selectedref) == INFOS_SIZE) { @@ -670,7 +670,7 @@ void menu_colors_ui::recompute_metrics(uint32_t width, uint32_t height, float as // perform our special rendering //------------------------------------------------- -void menu_colors_ui::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) +void menu_colors_ui::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) { // get the text for 'UI Select' std::string const bottomtext[] = { util::string_format(_("Double-click or press %1$s to change color"), ui().get_general_input_setting(IPT_UI_SELECT)) }; @@ -963,7 +963,7 @@ void menu_rgb_ui::recompute_metrics(uint32_t width, uint32_t height, float aspec // perform our special rendering //------------------------------------------------- -void menu_rgb_ui::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) +void menu_rgb_ui::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) { float maxwidth = origx2 - origx1; diff --git a/src/frontend/mame/ui/custui.h b/src/frontend/mame/ui/custui.h index 981546c64e3..be9df136f7e 100644 --- a/src/frontend/mame/ui/custui.h +++ b/src/frontend/mame/ui/custui.h @@ -60,7 +60,7 @@ public: protected: virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; - virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; virtual void menu_dismissed() override; private: @@ -96,7 +96,7 @@ public: protected: virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; - virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; virtual void menu_dismissed() override; private: @@ -145,7 +145,7 @@ public: protected: virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; - virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; private: enum diff --git a/src/frontend/mame/ui/datmenu.cpp b/src/frontend/mame/ui/datmenu.cpp index a4f919a7f62..492bdd38e41 100644 --- a/src/frontend/mame/ui/datmenu.cpp +++ b/src/frontend/mame/ui/datmenu.cpp @@ -13,7 +13,6 @@ #include "ui/systemlist.h" #include "ui/ui.h" -#include "ui/utils.h" #include "luaengine.h" #include "mame.h" @@ -31,7 +30,8 @@ namespace ui { //------------------------------------------------- -// ctor / dtor +// construct for currently running or specified +// system //------------------------------------------------- menu_dats_view::menu_dats_view(mame_ui_manager &mui, render_container &container, const ui_system_info *system) @@ -39,8 +39,9 @@ menu_dats_view::menu_dats_view(mame_ui_manager &mui, render_container &container , m_system(!system ? &system_list::instance().systems()[driver_list::find(mui.machine().system().name)] : system) , m_swinfo(nullptr) , m_issoft(false) - , m_actual(0) - + , m_current_tab(0) + , m_tab_line(1.0F, 0.0F) + , m_clicked_tab(-1) { set_process_flags(PROCESS_LR_ALWAYS | PROCESS_CUSTOM_NAV); for (device_image_interface& image : image_interface_enumerator(mui.machine().root_device())) @@ -54,15 +55,17 @@ menu_dats_view::menu_dats_view(mame_ui_manager &mui, render_container &container break; } } + std::vector<std::string> lua_list; if (mame_machine_manager::instance()->lua()->call_plugin("data_list", system ? system->driver->name : "", lua_list)) { int count = 0; + m_items_list.reserve(lua_list.size()); for (std::string& item : lua_list) { std::string version; mame_machine_manager::instance()->lua()->call_plugin("data_version", count, version); - m_items_list.emplace_back(item.c_str(), count, std::move(version)); + m_items_list.emplace_back(std::move(item), count, std::move(version)); count++; } } @@ -77,25 +80,33 @@ menu_dats_view::menu_dats_view(mame_ui_manager &mui, render_container &container , m_system(nullptr) , m_swinfo(&swinfo) , m_issoft(true) - , m_actual(0) + , m_current_tab(0) , m_list(swinfo.listname) , m_short(swinfo.shortname) , m_long(swinfo.longname) , m_parent(swinfo.parentname) - + , m_tab_line(1.0F, 0.0F) + , m_clicked_tab(-1) { set_process_flags(PROCESS_LR_ALWAYS | PROCESS_CUSTOM_NAV); - if (!swinfo.infotext.empty()) - m_items_list.emplace_back(_("Software List Info"), 0, ""); + std::vector<std::string> lua_list; - if (mame_machine_manager::instance()->lua()->call_plugin("data_list", std::string(m_short).append(1, ',').append(m_list).c_str(), lua_list)) + bool const retrieved(mame_machine_manager::instance()->lua()->call_plugin("data_list", std::string(m_short).append(1, ',').append(m_list).c_str(), lua_list)); + + if (!swinfo.infotext.empty() || retrieved) + m_items_list.reserve((!swinfo.infotext.empty() ? 1 : 0) + (retrieved ? lua_list.size() : 0)); + + if (!swinfo.infotext.empty()) + m_items_list.emplace_back(_("Software List Info"), -1, ""); + + if (retrieved) { - int count = 1; + int count = 0; for (std::string &item : lua_list) { std::string version; - mame_machine_manager::instance()->lua()->call_plugin("data_version", count - 1, version); - m_items_list.emplace_back(item.c_str(), count, std::move(version)); + mame_machine_manager::instance()->lua()->call_plugin("data_version", count, version); + m_items_list.emplace_back(std::move(item), count, std::move(version)); count++; } } @@ -169,34 +180,36 @@ void menu_dats_view::add_info_text(text_layout &layout, std::string_view text, r bool menu_dats_view::handle(event const *ev) { - if (!ev) - return false; - - switch (ev->iptkey) + if (ev) { - case IPT_UI_LEFT: - if (m_actual > 0) + // don't bother with parent event handling if we need to redraw anyway + switch (ev->iptkey) { - m_actual--; - reset_layout(); - return true; - } - break; + case IPT_UI_LEFT: + if (m_current_tab > 0) + { + m_current_tab--; + m_tab_line = std::make_pair(1.0F, 0.0F); + m_clicked_tab = -1; + reset_layout(); + return true; + } + break; - case IPT_UI_RIGHT: - if ((m_actual + 1) < m_items_list.size()) - { - m_actual++; - reset_layout(); - return true; + case IPT_UI_RIGHT: + if ((m_current_tab + 1) < m_items_list.size()) + { + m_current_tab++; + m_tab_line = std::make_pair(1.0F, 0.0F); + m_clicked_tab = -1; + reset_layout(); + return true; + } + break; } - break; - - default: - return handle_key(ev->iptkey); } - return false; + return menu_textbox::handle(ev); } //------------------------------------------------- @@ -215,6 +228,9 @@ void menu_dats_view::recompute_metrics(uint32_t width, uint32_t height, float as { menu_textbox::recompute_metrics(width, height, aspect); + m_tab_line = std::make_pair(1.0F, 0.0F); + m_clicked_tab = -1; + set_custom_space(2.0F * line_height() + 4.0F * tb_border(), line_height() + 3.0F * tb_border()); } @@ -222,103 +238,112 @@ void menu_dats_view::recompute_metrics(uint32_t width, uint32_t height, float as // perform our special rendering //------------------------------------------------- -void menu_dats_view::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) +void menu_dats_view::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) { - float maxwidth = origx2 - origx1; - float width; + float maxwidth; std::string_view const driver = m_issoft ? m_swinfo->longname : m_system->description; - width = get_string_width(driver); - width += 2 * lr_border(); - maxwidth = std::max(maxwidth, width); + maxwidth = std::max(origx2 - origx1, get_string_width(driver) + (2.0F * lr_border())); // compute our bounds - float x1 = 0.5F - 0.5F * maxwidth; + float x1 = 0.5F - (0.5F * maxwidth); float x2 = x1 + maxwidth; float y1 = origy1 - top; - float y2 = origy1 - 2.0F * tb_border() - line_height(); + float y2 = y1 + (2.0F * tb_border()) + line_height(); // draw a box ui().draw_outlined_box(container(), x1, y1, x2, y2, UI_GREEN_COLOR); - // take off the borders - x1 += lr_border(); - x2 -= lr_border(); - y1 += tb_border(); - draw_text_normal( driver, - x1, y1, x2 - x1, + x1 + lr_border(), y1 + tb_border(), x2 - x1 - (2.0F * lr_border()), text_layout::text_justify::CENTER, ui::text_layout::word_wrapping::NEVER, ui().colors().text_color()); - maxwidth = 0; - for (auto const &elem : m_items_list) - { - width = get_string_width(elem.label); - maxwidth += width; - } - - float space = (1.0F - maxwidth) / (m_items_list.size() * 2); + // draw a box + ui().draw_outlined_box(container(), x1, origy1 - line_height() - tb_border(), x2, origy1, ui().colors().background_color()); - // compute our bounds - x1 -= lr_border(); - x2 += lr_border(); - y1 = y2 + tb_border(); - y2 += line_height() + 2.0F * tb_border(); + // calculate geometry of tab line + if (m_tab_line.first >= m_tab_line.second) + { + m_tab_line = std::make_pair(origy1 - line_height(), origy1); - // draw a box - ui().draw_outlined_box(container(), x1, y1, x2, y2, ui().colors().background_color()); + // FIXME: deal with overflow when there are a lot of tabs + float total(0.0F); + for (auto const &elem : m_items_list) + total += get_string_width(elem.label); + float const space((1.0F - total) / (m_items_list.size() * 2.0F)); - // take off the borders - y1 += tb_border(); + float left(x1 + (space * 0.5F)); + for (auto &elem : m_items_list) + { + float const width(get_string_width(elem.label)); + elem.bounds = std::make_pair(left, left + width + space); + left += width + (space * 2.0F); + } + } // draw the text within it - int x = 0; - for (auto const &elem : m_items_list) + for (int i = 0; m_items_list.size() > i; ++i) { - x1 += space; - if (mouse_in_rect(x1 - (space / 2), y1, x1 + width + (space / 2), y2)) - set_hover(HOVER_INFO_TEXT + 1 + x); + auto &elem(m_items_list[i]); - rgb_t const fcolor = (m_actual == x) ? rgb_t(0xff, 0xff, 0xff, 0x00) : ui().colors().text_color(); - rgb_t const bcolor = (m_actual == x) ? rgb_t(0xff, 0xff, 0xff, 0xff) : ui().colors().text_bg_color(); - width = get_string_width(elem.label); - - if (bcolor != ui().colors().text_bg_color()) + rgb_t fgcolor; + rgb_t bgcolor; + if (i == m_current_tab) { + fgcolor = rgb_t(0xff, 0xff, 0xff, 0x00); + bgcolor = rgb_t(0xff, 0xff, 0xff, 0xff); ui().draw_textured_box( container(), - x1 - (space / 2), y1, x1 + width + (space / 2), y2, - bcolor, rgb_t(255, 43, 43, 43), + elem.bounds.first, m_tab_line.first, elem.bounds.second, m_tab_line.second, + bgcolor, rgb_t(255, 43, 43, 43), hilight_main_texture(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXWRAP(1)); } + else + { + bool const hovered(pointer_in_rect(elem.bounds.first, m_tab_line.first, elem.bounds.second, m_tab_line.second)); + if ((i == m_clicked_tab) && hovered) + { + fgcolor = ui().colors().selected_color(); + bgcolor = ui().colors().selected_bg_color(); + highlight(elem.bounds.first, m_tab_line.first, elem.bounds.second, m_tab_line.second, bgcolor); + } + else if ((i == m_clicked_tab) || (hovered && pointer_idle())) + { + fgcolor = ui().colors().mouseover_color(); + bgcolor = ui().colors().mouseover_bg_color(); + highlight(elem.bounds.first, m_tab_line.first, elem.bounds.second, m_tab_line.second, bgcolor); + } + else + { + fgcolor = ui().colors().text_color(); + bgcolor = ui().colors().text_bg_color(); + } + } ui().draw_text_full( container(), elem.label, - x1, y1, 1.0F, - text_layout::text_justify::LEFT, text_layout::word_wrapping::NEVER, - mame_ui_manager::NORMAL, fcolor, bcolor, - &width, nullptr, + elem.bounds.first, m_tab_line.first, elem.bounds.second - elem.bounds.first, + text_layout::text_justify::CENTER, text_layout::word_wrapping::NEVER, + mame_ui_manager::NORMAL, fgcolor, bgcolor, + nullptr, nullptr, line_height()); - x1 += width + space; - ++x; } // bottom if (!m_items_list.empty()) { - std::string const revision(util::string_format(_("Revision: %1$s"), m_items_list[m_actual].revision)); - width = get_text_width( + std::string const revision(util::string_format(_("Revision: %1$s"), m_items_list[m_current_tab].revision)); + float const width(get_text_width( revision, 0.0F, 0.0F, 1.0F, - text_layout::text_justify::CENTER, text_layout::word_wrapping::TRUNCATE); - width += 2 * lr_border(); - maxwidth = std::max(origx2 - origx1, width); + text_layout::text_justify::CENTER, text_layout::word_wrapping::TRUNCATE)); + maxwidth = std::max(origx2 - origx1, width + (2.0F * lr_border())); // compute our bounds - x1 = 0.5F - 0.5F * maxwidth; + x1 = 0.5F - (0.5F * maxwidth); x2 = x1 + maxwidth; y1 = origy2 + tb_border(); y2 = origy2 + bottom; @@ -326,39 +351,65 @@ void menu_dats_view::custom_render(void *selectedref, float top, float bottom, f // draw a box ui().draw_outlined_box(container(), x1, y1, x2, y2, UI_GREEN_COLOR); - // take off the borders - x1 += lr_border(); - x2 -= lr_border(); - y1 += tb_border(); - // draw the text within it draw_text_normal( revision, - x1, y1, x2 - x1, + x1 + lr_border(), y1 + tb_border(), x2 - x1 - (2.0F * lr_border()), text_layout::text_justify::CENTER, text_layout::word_wrapping::TRUNCATE, ui().colors().text_color()); } } //------------------------------------------------- -// custom mouse click handling +// custom pointer handling //------------------------------------------------- -bool menu_dats_view::custom_mouse_down() +std::tuple<int, bool, bool> menu_dats_view::custom_pointer_updated(bool changed, ui_event const &uievt) { - if ((hover() > HOVER_INFO_TEXT) && ((hover() - HOVER_INFO_TEXT) <= m_items_list.size())) + if (0 <= m_clicked_tab) { - if ((hover() - HOVER_INFO_TEXT - 1) != m_actual) + if ((ui_event::type::POINTER_ABORT != uievt.event_type) && uievt.pointer_released & 0x01) + { + // primary button released - take action if still on the tab + bool const hit(pointer_in_rect( + m_items_list[m_clicked_tab].bounds.first, m_tab_line.first, + m_items_list[m_clicked_tab].bounds.second, m_tab_line.second)); + if (hit && (m_current_tab != m_clicked_tab)) + { + m_current_tab = m_clicked_tab; + m_tab_line = std::make_pair(1.0F, 0.0F); + reset_layout(); + } + m_clicked_tab = -1; + return std::make_tuple(hit ? IPT_CUSTOM : IPT_INVALID, false, true); + } + else if ((ui_event::type::POINTER_ABORT == uievt.event_type) || (uievt.pointer_buttons & ~u32(0x01))) { - m_actual = hover() - HOVER_INFO_TEXT - 1; - reset_layout(); + // treat pressing another button as cancellation + m_clicked_tab = -1; + return std::make_tuple(IPT_INVALID, false, true); } - return true; + return std::make_tuple(IPT_INVALID, true, false); } - else + else if (pointer_idle() && (uievt.pointer_pressed & 0x01) && !(uievt.pointer_buttons & ~u32(0x01))) { - return false; + // primary click - see if it's over a tab + auto const [x, y] = pointer_location(); + if ((y >= m_tab_line.first) && (y < m_tab_line.second)) + { + for (int i = 0; m_items_list.size() > i; ++i) + { + if ((x >= m_items_list[i].bounds.first) && (x < m_items_list[i].bounds.second)) + { + m_clicked_tab = i; + return std::make_tuple(IPT_INVALID, true, true); + } + } + } } + + // let the base class have a look + return menu_textbox::custom_pointer_updated(changed, uievt); } //------------------------------------------------- @@ -369,13 +420,16 @@ void menu_dats_view::populate_text(std::optional<text_layout> &layout, float &wi { if (!layout || (layout->width() != width)) { + m_tab_line = std::make_pair(1.0F, 0.0F); + m_clicked_tab = -1; + std::string buffer; if (!m_items_list.empty()) { - if (m_issoft) - get_data_sw(buffer); + if (0 > m_items_list[m_current_tab].option) + buffer = m_swinfo->infotext; else - get_data(buffer); + mame_machine_manager::instance()->lua()->call_plugin("data", m_items_list[m_current_tab].option, buffer); } layout.emplace(create_layout(width)); add_info_text(*layout, buffer, ui().colors().text_color()); @@ -383,21 +437,4 @@ void menu_dats_view::populate_text(std::optional<text_layout> &layout, float &wi } } -//------------------------------------------------- -// load data from DATs -//------------------------------------------------- - -void menu_dats_view::get_data(std::string &buffer) -{ - mame_machine_manager::instance()->lua()->call_plugin("data", m_items_list[m_actual].option, buffer); -} - -void menu_dats_view::get_data_sw(std::string &buffer) -{ - if (m_items_list[m_actual].option == 0) - buffer = m_swinfo->infotext; - else - mame_machine_manager::instance()->lua()->call_plugin("data", m_items_list[m_actual].option - 1, buffer); -} - } // namespace ui diff --git a/src/frontend/mame/ui/datmenu.h b/src/frontend/mame/ui/datmenu.h index 5c255c5fbff..e8e7cea2887 100644 --- a/src/frontend/mame/ui/datmenu.h +++ b/src/frontend/mame/ui/datmenu.h @@ -44,33 +44,35 @@ public: protected: virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; - virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; - virtual bool custom_mouse_down() override; + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; + virtual std::tuple<int, bool, bool> custom_pointer_updated(bool changed, ui_event const &uievt) override; virtual void populate_text(std::optional<text_layout> &layout, float &width, int &lines) override; private: struct list_items { - list_items(std::string &&l, int i, std::string &&rev) : label(std::move(l)), option(i), revision(std::move(rev)) { } + list_items(std::string &&l, int i, std::string &&rev) : label(std::move(l)), revision(std::move(rev)), option(i), bounds(1.0F, 0.0F) { } std::string label; - int option; std::string revision; + int option; + + std::pair<float, float> bounds; }; virtual void populate() override; virtual bool handle(event const *ev) override; - void get_data(std::string &buffer); - void get_data_sw(std::string &buffer); - ui_system_info const *const m_system; ui_software_info const *const m_swinfo; bool const m_issoft; - int m_actual; + int m_current_tab; std::string m_list, m_short, m_long, m_parent; std::vector<list_items> m_items_list; + + std::pair<float, float> m_tab_line; + int m_clicked_tab; }; } // namespace ui diff --git a/src/frontend/mame/ui/devopt.cpp b/src/frontend/mame/ui/devopt.cpp index 2dd7a758a0f..8d841c7d131 100644 --- a/src/frontend/mame/ui/devopt.cpp +++ b/src/frontend/mame/ui/devopt.cpp @@ -368,12 +368,4 @@ void menu_device_config::populate() { } -bool menu_device_config::handle(event const *ev) -{ - if (ev) - return handle_key(ev->iptkey); - else - return false; -} - } // namespace ui diff --git a/src/frontend/mame/ui/devopt.h b/src/frontend/mame/ui/devopt.h index 67500c81a41..2e67cd4524f 100644 --- a/src/frontend/mame/ui/devopt.h +++ b/src/frontend/mame/ui/devopt.h @@ -29,7 +29,6 @@ protected: private: virtual void populate() override; - virtual bool handle(event const *ev) override; device_slot_interface::slot_option const *const m_option; bool const m_mounted; diff --git a/src/frontend/mame/ui/dirmenu.cpp b/src/frontend/mame/ui/dirmenu.cpp index bd4ddb0cfdd..083dfbeba79 100644 --- a/src/frontend/mame/ui/dirmenu.cpp +++ b/src/frontend/mame/ui/dirmenu.cpp @@ -165,7 +165,7 @@ public: protected: virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; - virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; virtual bool custom_ui_back() override { return !m_search.empty(); } @@ -356,7 +356,7 @@ void menu_add_change_folder::recompute_metrics(uint32_t width, uint32_t height, // perform our special rendering //------------------------------------------------- -void menu_add_change_folder::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) +void menu_add_change_folder::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) { std::string const toptext[] = { util::string_format( @@ -447,7 +447,7 @@ public: protected: virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; - virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; private: enum @@ -557,7 +557,7 @@ void menu_display_actual::recompute_metrics(uint32_t width, uint32_t height, flo // perform our special rendering //------------------------------------------------- -void menu_display_actual::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) +void menu_display_actual::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) { float const maxwidth(draw_text_box( std::begin(m_folders), std::end(m_folders), diff --git a/src/frontend/mame/ui/filecreate.cpp b/src/frontend/mame/ui/filecreate.cpp index 75e78702567..828e59cbb74 100644 --- a/src/frontend/mame/ui/filecreate.cpp +++ b/src/frontend/mame/ui/filecreate.cpp @@ -153,7 +153,7 @@ void menu_file_create::recompute_metrics(uint32_t width, uint32_t height, float // custom_render - perform our special rendering //------------------------------------------------- -void menu_file_create::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) +void menu_file_create::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) { extra_text_render(top, bottom, origx1, origy1, origx2, origy2, m_current_directory, diff --git a/src/frontend/mame/ui/filecreate.h b/src/frontend/mame/ui/filecreate.h index 9537003a6f7..a5f1ab92857 100644 --- a/src/frontend/mame/ui/filecreate.h +++ b/src/frontend/mame/ui/filecreate.h @@ -48,7 +48,7 @@ public: protected: virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; - virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; virtual bool custom_ui_back() override; private: diff --git a/src/frontend/mame/ui/filemngr.cpp b/src/frontend/mame/ui/filemngr.cpp index 92b68ccee1d..f9a99474a15 100644 --- a/src/frontend/mame/ui/filemngr.cpp +++ b/src/frontend/mame/ui/filemngr.cpp @@ -74,7 +74,7 @@ void menu_file_manager::recompute_metrics(uint32_t width, uint32_t height, float // custom_render - perform our special rendering //------------------------------------------------- -void menu_file_manager::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) +void menu_file_manager::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) { // access the path std::string_view path = m_selected_device && m_selected_device->exists() ? m_selected_device->filename() : std::string_view(); diff --git a/src/frontend/mame/ui/filemngr.h b/src/frontend/mame/ui/filemngr.h index bae4546201e..a8c68ceb211 100644 --- a/src/frontend/mame/ui/filemngr.h +++ b/src/frontend/mame/ui/filemngr.h @@ -32,7 +32,7 @@ public: protected: virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; - virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; private: virtual void populate() override; diff --git a/src/frontend/mame/ui/filesel.cpp b/src/frontend/mame/ui/filesel.cpp index 14d62c206b3..5f4dddc65fc 100644 --- a/src/frontend/mame/ui/filesel.cpp +++ b/src/frontend/mame/ui/filesel.cpp @@ -19,6 +19,8 @@ #include "imagedev/floppy.h" +#include "uiinput.h" + #include "util/corestr.h" #include "util/zippath.h" @@ -48,15 +50,26 @@ namespace ui { // ctor //------------------------------------------------- -menu_file_selector::menu_file_selector(mame_ui_manager &mui, render_container &container, device_image_interface *image, std::string ¤t_directory, std::string ¤t_file, bool has_empty, bool has_softlist, bool has_create, menu_file_selector::result &result) +menu_file_selector::menu_file_selector( + mame_ui_manager &mui, + render_container &container, + device_image_interface *image, + std::string_view directory, + std::string_view file, + bool has_empty, + bool has_softlist, + bool has_create, + handler_function &&handler) : menu(mui, container) + , m_handler(std::move(handler)) , m_image(image) - , m_current_directory(current_directory) - , m_current_file(current_file) + , m_current_directory(directory) + , m_current_file(file) + , m_result(result::INVALID) , m_has_empty(has_empty) , m_has_softlist(has_softlist) , m_has_create(has_create) - , m_result(result) + , m_clicked_directory(std::string::npos, std::string::npos) { (void)m_image; set_process_flags(PROCESS_IGNOREPAUSE); @@ -69,6 +82,8 @@ menu_file_selector::menu_file_selector(mame_ui_manager &mui, render_container &c menu_file_selector::~menu_file_selector() { + if (m_handler) + m_handler(m_result, std::move(m_current_directory), std::move(m_current_file)); } @@ -80,7 +95,9 @@ void menu_file_selector::recompute_metrics(uint32_t width, uint32_t height, floa { menu::recompute_metrics(width, height, aspect); - // set up custom render proc + m_path_layout.reset(); + m_clicked_directory = std::make_pair(std::string::npos, std::string::npos); + set_custom_space(line_height() + 3.0F * tb_border(), 0.0F); } @@ -89,63 +106,131 @@ void menu_file_selector::recompute_metrics(uint32_t width, uint32_t height, floa // custom_render - perform our special rendering //------------------------------------------------- -void menu_file_selector::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) +void menu_file_selector::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) { // lay out extra text - auto layout = create_layout(); - layout.add_text(m_current_directory); + if (!m_path_layout) + { + m_path_layout.emplace(create_layout()); + m_path_layout->add_text(m_current_directory); + } + else + { + rgb_t const fgcolor = ui().colors().text_color(); + rgb_t const bgcolor = rgb_t::transparent(); + m_path_layout->restyle(0, m_current_directory.length(), &fgcolor, &bgcolor); + } // position this extra text - float x1, y1, x2, y2; - extra_text_position(origx1, origx2, origy1, top, layout, -1, x1, y1, x2, y2); + float x2, y2; + extra_text_position(origx1, origx2, origy1, top, *m_path_layout, -1, m_path_position.first, m_path_position.second, x2, y2); // draw a box - ui().draw_outlined_box(container(), x1, y1, x2, y2, ui().colors().background_color()); + ui().draw_outlined_box(container(), m_path_position.first, m_path_position.second, x2, y2, ui().colors().background_color()); // take off the borders - x1 += lr_border(); - y1 += tb_border(); + m_path_position.first += lr_border(); + m_path_position.second += tb_border(); - size_t hit_start = 0, hit_span = 0; - if (is_mouse_hit() - && layout.hit_test(get_mouse_x() - x1, get_mouse_y() - y1, hit_start, hit_span) - && m_current_directory.substr(hit_start, hit_span) != PATH_SEPARATOR) + if (m_clicked_directory.second > m_clicked_directory.first) { - // we're hovering over a directory! highlight it - auto target_dir_start = m_current_directory.rfind(PATH_SEPARATOR, hit_start) + 1; - auto target_dir_end = m_current_directory.find(PATH_SEPARATOR, hit_start + hit_span); - m_hover_directory = m_current_directory.substr(0, target_dir_end + strlen(PATH_SEPARATOR)); - - // highlight the text in question - rgb_t fgcolor = ui().colors().mouseover_color(); - rgb_t bgcolor = ui().colors().mouseover_bg_color(); - layout.restyle(target_dir_start, target_dir_end - target_dir_start, &fgcolor, &bgcolor); + // see if it's still over the clicked path component + auto const [x, y] = pointer_location(); + size_t start = 0, span = 0; + if (m_path_layout->hit_test(x - m_path_position.first, y - m_path_position.second, start, span)) + { + if ((start >= m_clicked_directory.first) && ((start + span) <= m_clicked_directory.second)) + { + rgb_t const fgcolor = ui().colors().selected_color(); + rgb_t const bgcolor = ui().colors().selected_bg_color(); + m_path_layout->restyle(m_clicked_directory.first, m_clicked_directory.second - m_clicked_directory.first, &fgcolor, &bgcolor); + } + } } - else + else if (pointer_idle()) { - // we are not hovering over anything - m_hover_directory.clear(); + // see if it's hovering over a path component + auto const [x, y] = pointer_location(); + auto const [target_dir_start, target_dir_end] = get_directory_range(x, y); + if (target_dir_end > target_dir_start) + { + rgb_t const fgcolor = ui().colors().mouseover_color(); + rgb_t const bgcolor = ui().colors().mouseover_bg_color(); + m_path_layout->restyle(target_dir_start, target_dir_end - target_dir_start, &fgcolor, &bgcolor); + } } // draw the text within it - layout.emit(container(), x1, y1); + m_path_layout->emit(container(), m_path_position.first, m_path_position.second); } //------------------------------------------------- -// custom_mouse_down - perform our special mouse down +// custom_pointer_updated - perform our special +// pointer handling //------------------------------------------------- -bool menu_file_selector::custom_mouse_down() +std::tuple<int, bool, bool> menu_file_selector::custom_pointer_updated(bool changed, ui_event const &uievt) { - if (m_hover_directory.length() > 0) + // track pointer after clicking a path component + if (m_clicked_directory.second > m_clicked_directory.first) { - m_current_directory = m_hover_directory; - reset(reset_options::SELECT_FIRST); - return true; + if (ui_event::type::POINTER_ABORT == uievt.event_type) + { + // abort always cancels + m_clicked_directory = std::make_pair(std::string::npos, std::string::npos); + return std::make_tuple(IPT_INVALID, false, true); + } + else if (uievt.pointer_released & 0x01) + { + // releasing the primary button - check for dragging out + auto const [x, y] = pointer_location(); + size_t start = 0, span = 0; + if (m_path_layout->hit_test(x - m_path_position.first, y - m_path_position.second, start, span)) + { + // abuse IPT_CUSTOM to change to the clicked directory + if ((start >= m_clicked_directory.first) && ((start + span) <= m_clicked_directory.second)) + return std::make_tuple(IPT_CUSTOM, false, true); + } + m_clicked_directory = std::make_pair(std::string::npos, std::string::npos); + return std::make_tuple(IPT_INVALID, false, true); + } + else if (uievt.pointer_buttons & ~u32(1)) + { + // pressing more buttons cancels + m_clicked_directory = std::make_pair(std::string::npos, std::string::npos); + return std::make_tuple(IPT_INVALID, false, true); + } + else + { + // keep tracking the pointer + return std::make_tuple(IPT_INVALID, true, false); + } } - return false; + // check for clicks if we have up-to-date content on-screen + if (m_path_layout && pointer_idle() && (uievt.pointer_buttons & 0x01) && !(uievt.pointer_buttons & ~u32(0x01))) + { + auto const [x, y] = pointer_location(); + auto const [target_dir_start, target_dir_end] = get_directory_range(x, y); + if (target_dir_end > target_dir_start) + { + m_clicked_directory = std::make_pair(target_dir_start, target_dir_end); + return std::make_tuple(IPT_INVALID, true, true); + } + } + + return std::make_tuple(IPT_INVALID, false, false); +} + + +//------------------------------------------------- +// menu_activated - menu has gained focus +//------------------------------------------------- + +void menu_file_selector::menu_activated() +{ + m_clicked_directory = std::make_pair(std::string::npos, std::string::npos); } @@ -299,7 +384,9 @@ void menu_file_selector::select_item(const file_selector_entry &entry) break; } } - m_current_directory.assign(entry.fullpath); + m_current_directory = entry.fullpath; + m_path_layout.reset(); + m_clicked_directory = std::make_pair(std::string::npos, std::string::npos); reset(reset_options::SELECT_FIRST); break; @@ -314,7 +401,7 @@ void menu_file_selector::select_item(const file_selector_entry &entry) //------------------------------------------------- -// type_search_char +// update_search //------------------------------------------------- void menu_file_selector::update_search() @@ -357,6 +444,35 @@ void menu_file_selector::update_search() //------------------------------------------------- +// get_directory_range +//------------------------------------------------- + +std::pair<size_t, size_t> menu_file_selector::get_directory_range(float x, float y) +{ + size_t start = 0, span = 0; + if (m_path_layout->hit_test(x - m_path_position.first, y - m_path_position.second, start, span)) + { + if (std::string_view(m_current_directory).substr(start, span) != PATH_SEPARATOR) + { + auto target_start = m_current_directory.rfind(PATH_SEPARATOR, start); + if (std::string::npos == target_start) + target_start = 0; + else + target_start += 1; + + auto target_end = m_current_directory.find(PATH_SEPARATOR, start + span); + if (std::string::npos == target_end) + target_end = m_current_directory.length(); + + return std::make_pair(target_start, target_end); + } + } + + return std::make_pair(std::string::npos, std::string::npos); +} + + +//------------------------------------------------- // populate //------------------------------------------------- @@ -479,6 +595,18 @@ bool menu_file_selector::handle(event const *ev) return true; } } + else if (ev->iptkey == IPT_CUSTOM) + { + // clicked a path component + if (m_clicked_directory.second > m_clicked_directory.first) + { + m_current_directory.resize(m_clicked_directory.second + strlen(PATH_SEPARATOR)); + m_path_layout.reset(); + m_clicked_directory = std::make_pair(std::string::npos, std::string::npos); + reset(reset_options::SELECT_FIRST); + return true; + } + } else if (ev->itemref && (ev->iptkey == IPT_UI_SELECT)) { // handle selections @@ -502,11 +630,15 @@ bool menu_file_selector::handle(event const *ev) // ctor //------------------------------------------------- -menu_select_rw::menu_select_rw(mame_ui_manager &mui, render_container &container, - bool can_in_place, result &result) - : menu(mui, container), - m_can_in_place(can_in_place), - m_result(result) +menu_select_rw::menu_select_rw( + mame_ui_manager &mui, + render_container &container, + bool can_in_place, + handler_function &&handler) + : menu(mui, container) + , m_handler(std::move(handler)) + , m_can_in_place(can_in_place) + , m_result(result::INVALID) { } @@ -517,6 +649,8 @@ menu_select_rw::menu_select_rw(mame_ui_manager &mui, render_container &container menu_select_rw::~menu_select_rw() { + if (m_handler) + m_handler(m_result); } @@ -526,7 +660,8 @@ menu_select_rw::~menu_select_rw() void menu_select_rw::populate() { - item_append(_("Select access mode"), FLAG_DISABLE, nullptr); + set_heading(_("Select access mode")); + item_append(_("Read-only"), 0, itemref_from_result(result::READONLY)); if (m_can_in_place) item_append(_("Read-write"), 0, itemref_from_result(result::READWRITE)); diff --git a/src/frontend/mame/ui/filesel.h b/src/frontend/mame/ui/filesel.h index 8bef06e53ca..6ca9cafbe68 100644 --- a/src/frontend/mame/ui/filesel.h +++ b/src/frontend/mame/ui/filesel.h @@ -15,6 +15,15 @@ #include "ui/menu.h" +#include <functional> +#include <optional> +#include <string> +#include <string_view> +#include <tuple> +#include <utility> +#include <vector> + + namespace ui { // ======================> menu_file_selector @@ -31,23 +40,26 @@ public: FILE }; + using handler_function = std::function<void (result result, std::string &&directory, std::string &&file)>; + menu_file_selector( mame_ui_manager &mui, render_container &container, device_image_interface *image, - std::string ¤t_directory, - std::string ¤t_file, + std::string_view directory, + std::string_view file, bool has_empty, bool has_softlist, bool has_create, - result &result); + handler_function &&handler); virtual ~menu_file_selector() override; protected: virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; - virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; virtual bool custom_ui_back() override { return !m_filename.empty(); } - virtual bool custom_mouse_down() override; + virtual std::tuple<int, bool, bool> custom_pointer_updated(bool changed, ui_event const &uievt) override; + virtual void menu_activated() override; private: enum file_selector_entry_type @@ -72,16 +84,19 @@ private: }; // internal state - device_image_interface *const m_image; - std::string & m_current_directory; - std::string & m_current_file; - bool const m_has_empty; - bool const m_has_softlist; - bool const m_has_create; - result & m_result; + handler_function const m_handler; + device_image_interface *const m_image; + std::string m_current_directory; + std::string m_current_file; + std::optional<text_layout> m_path_layout; + std::pair<float, float> m_path_position; + result m_result; + bool const m_has_empty; + bool const m_has_softlist; + bool const m_has_create; std::vector<file_selector_entry> m_entrylist; - std::string m_hover_directory; - std::string m_filename; + std::string m_filename; + std::pair<size_t, size_t> m_clicked_directory; virtual void populate() override; virtual bool handle(event const *ev) override; @@ -93,6 +108,7 @@ private: void append_entry_menu_item(const file_selector_entry *entry); void select_item(const file_selector_entry &entry); void update_search(); + std::pair<size_t, size_t> get_directory_range(float x, float y); }; @@ -109,23 +125,27 @@ public: WRITE_OTHER, WRITE_DIFF }; + + using handler_function = std::function<void (result result)>; + menu_select_rw( mame_ui_manager &mui, render_container &container, bool can_in_place, - result &result); + handler_function &&handler); virtual ~menu_select_rw() override; - static void *itemref_from_result(result result); - static result result_from_itemref(void *itemref); - private: virtual void populate() override; virtual bool handle(event const *ev) override; + static void *itemref_from_result(result result); + static result result_from_itemref(void *itemref); + // internal state - bool m_can_in_place; - result & m_result; + handler_function const m_handler; + bool const m_can_in_place; + result m_result; }; } // namespace ui diff --git a/src/frontend/mame/ui/floppycntrl.cpp b/src/frontend/mame/ui/floppycntrl.cpp index 83317129ca8..3f6f9bdfb61 100644 --- a/src/frontend/mame/ui/floppycntrl.cpp +++ b/src/frontend/mame/ui/floppycntrl.cpp @@ -86,7 +86,8 @@ void menu_control_floppy_image::hook_load(const std::string &filename) else { bool can_in_place = input_format->supports_save(); - if(can_in_place) { + if (can_in_place) + { std::string tmp_path; util::core_file::ptr tmp_file; // attempt to open the file for writing but *without* create @@ -96,9 +97,41 @@ void menu_control_floppy_image::hook_load(const std::string &filename) else can_in_place = false; } - m_submenu_result.rw = menu_select_rw::result::INVALID; - menu::stack_push<menu_select_rw>(ui(), container(), can_in_place, m_submenu_result.rw); - m_state = SELECT_RW; + menu::stack_push<menu_select_rw>( + ui(), + container(), + can_in_place, + [this] (menu_select_rw::result result) + { + switch (result) + { + case menu_select_rw::result::READONLY: + do_load_create(); + fd.setup_write(nullptr); + stack_pop(); + break; + + case menu_select_rw::result::READWRITE: + output_format = input_format; + do_load_create(); + stack_pop(); + break; + + case menu_select_rw::result::WRITE_DIFF: + machine().popmessage("Sorry, diffs are not supported yet\n"); + stack_pop(); + break; + + case menu_select_rw::result::WRITE_OTHER: + menu::stack_push<menu_file_create>(ui(), container(), &m_image, m_current_directory, m_current_file, m_create_ok); + m_state = CHECK_CREATE; + break; + + case menu_select_rw::result::INVALID: + m_state = START_FILE; + break; + } + }); } } @@ -182,37 +215,6 @@ void menu_control_floppy_image::menu_activated() } break; - case SELECT_RW: - switch(m_submenu_result.rw) { - case menu_select_rw::result::READONLY: - do_load_create(); - fd.setup_write(nullptr); - stack_pop(); - break; - - case menu_select_rw::result::READWRITE: - output_format = input_format; - do_load_create(); - stack_pop(); - break; - - case menu_select_rw::result::WRITE_DIFF: - machine().popmessage("Sorry, diffs are not supported yet\n"); - stack_pop(); - break; - - case menu_select_rw::result::WRITE_OTHER: - menu::stack_push<menu_file_create>(ui(), container(), &m_image, m_current_directory, m_current_file, m_create_ok); - m_state = CHECK_CREATE; - break; - - case menu_select_rw::result::INVALID: - m_state = START_FILE; - menu_activated(); - break; - } - break; - default: menu_control_device_image::menu_activated(); } diff --git a/src/frontend/mame/ui/floppycntrl.h b/src/frontend/mame/ui/floppycntrl.h index 8be8e7ca02a..8bcb02d15b5 100644 --- a/src/frontend/mame/ui/floppycntrl.h +++ b/src/frontend/mame/ui/floppycntrl.h @@ -29,7 +29,7 @@ protected: virtual void menu_activated() override; private: - enum { SELECT_FORMAT = LAST_ID, SELECT_MEDIA, SELECT_INIT, SELECT_RW }; + enum { SELECT_FORMAT = LAST_ID, SELECT_MEDIA, SELECT_INIT }; floppy_image_device &fd; const floppy_image_format_t *input_format, *output_format; diff --git a/src/frontend/mame/ui/imgcntrl.cpp b/src/frontend/mame/ui/imgcntrl.cpp index 8f8aaffd3bb..6dd909f711c 100644 --- a/src/frontend/mame/ui/imgcntrl.cpp +++ b/src/frontend/mame/ui/imgcntrl.cpp @@ -9,7 +9,6 @@ ***************************************************************************/ #include "emu.h" - #include "ui/imgcntrl.h" #include "ui/filecreate.h" @@ -243,7 +242,6 @@ void menu_control_device_image::menu_activated() switch(m_state) { case START_FILE: - m_submenu_result.filesel = menu_file_selector::result::INVALID; menu::stack_push<menu_file_selector>( ui(), container(), &m_image, @@ -252,8 +250,35 @@ void menu_control_device_image::menu_activated() true, m_image.image_interface() != nullptr, m_image.is_creatable(), - m_submenu_result.filesel); - m_state = SELECT_FILE; + [this] (menu_file_selector::result result, std::string &&directory, std::string &&file) + { + m_current_directory = std::move(directory); + m_current_file = std::move(file); + switch (result) + { + case menu_file_selector::result::EMPTY: + m_image.unload(); + stack_pop(); + break; + + case menu_file_selector::result::FILE: + hook_load(m_current_file); + break; + + case menu_file_selector::result::CREATE: + menu::stack_push<menu_file_create>(ui(), container(), &m_image, m_current_directory, m_current_file, m_create_ok); + m_state = CHECK_CREATE; + break; + + case menu_file_selector::result::SOFTLIST: + m_state = START_SOFTLIST; + break; + + default: // return to system + stack_pop(); + break; + } + }); break; case START_SOFTLIST: @@ -344,34 +369,6 @@ void menu_control_device_image::menu_activated() } break; - case SELECT_FILE: - switch (m_submenu_result.filesel) - { - case menu_file_selector::result::EMPTY: - m_image.unload(); - stack_pop(); - break; - - case menu_file_selector::result::FILE: - hook_load(m_current_file); - break; - - case menu_file_selector::result::CREATE: - menu::stack_push<menu_file_create>(ui(), container(), &m_image, m_current_directory, m_current_file, m_create_ok); - m_state = CHECK_CREATE; - break; - - case menu_file_selector::result::SOFTLIST: - m_state = START_SOFTLIST; - menu_activated(); - break; - - default: // return to system - stack_pop(); - break; - } - break; - case CREATE_FILE: { bool can_create, need_confirm; diff --git a/src/frontend/mame/ui/imgcntrl.h b/src/frontend/mame/ui/imgcntrl.h index 22911f70141..c081291f2e1 100644 --- a/src/frontend/mame/ui/imgcntrl.h +++ b/src/frontend/mame/ui/imgcntrl.h @@ -4,7 +4,7 @@ ui/imgcntrl.h - MESS's clunky built-in file manager + MAME's clunky built-in file manager ***************************************************************************/ @@ -13,7 +13,6 @@ #pragma once -#include "ui/filesel.h" #include "ui/menu.h" #include "ui/swlist.h" @@ -33,7 +32,7 @@ protected: { START_FILE, START_OTHER_PART, START_SOFTLIST, SELECT_PARTLIST, SELECT_ONE_PART, SELECT_OTHER_PART, - SELECT_FILE, CREATE_FILE, CREATE_CONFIRM, CHECK_CREATE, DO_CREATE, SELECT_SOFTLIST, + CREATE_FILE, CREATE_CONFIRM, CHECK_CREATE, DO_CREATE, SELECT_SOFTLIST, LAST_ID }; @@ -41,9 +40,7 @@ protected: // results we could get from child menus union { - menu_file_selector::result filesel; menu_software_parts::result swparts; - menu_select_rw::result rw; int i; } m_submenu_result; diff --git a/src/frontend/mame/ui/info.cpp b/src/frontend/mame/ui/info.cpp index 813bbd4b8c8..945c18d32a2 100644 --- a/src/frontend/mame/ui/info.cpp +++ b/src/frontend/mame/ui/info.cpp @@ -576,11 +576,6 @@ void menu_game_info::populate() { } -bool menu_game_info::handle(event const *ev) -{ - return ev && handle_key(ev->iptkey); -} - /*------------------------------------------------- menu_warn_info - handle the emulation warnings menu @@ -640,11 +635,6 @@ void menu_warn_info::populate() { } -bool menu_warn_info::handle(event const *ev) -{ - return ev && handle_key(ev->iptkey); -} - /*------------------------------------------------- menu_image_info - handle the image information menu diff --git a/src/frontend/mame/ui/info.h b/src/frontend/mame/ui/info.h index 12eebc769c7..c1b5d318a69 100644 --- a/src/frontend/mame/ui/info.h +++ b/src/frontend/mame/ui/info.h @@ -104,7 +104,6 @@ protected: private: virtual void populate() override; - virtual bool handle(event const *ev) override; }; @@ -119,7 +118,6 @@ protected: private: virtual void populate() override; - virtual bool handle(event const *ev) override; }; diff --git a/src/frontend/mame/ui/inputdevices.cpp b/src/frontend/mame/ui/inputdevices.cpp index 0fe8de2a1c0..1ee03ad5c81 100644 --- a/src/frontend/mame/ui/inputdevices.cpp +++ b/src/frontend/mame/ui/inputdevices.cpp @@ -44,7 +44,7 @@ protected: set_custom_space(0.0F, (line_height() * (m_have_analog ? 2.0F : 1.0F)) + (tb_border() * 3.0F)); } - virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override { if (selectedref) { diff --git a/src/frontend/mame/ui/inputmap.cpp b/src/frontend/mame/ui/inputmap.cpp index b5b56b6e1fb..d8a4d9656f4 100644 --- a/src/frontend/mame/ui/inputmap.cpp +++ b/src/frontend/mame/ui/inputmap.cpp @@ -322,7 +322,7 @@ void menu_input::recompute_metrics(uint32_t width, uint32_t height, float aspect } -void menu_input::custom_render(void *selectedref, float top, float bottom, float x1, float y1, float x2, float y2) +void menu_input::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float x1, float y1, float x2, float y2) { if (pollingitem) { diff --git a/src/frontend/mame/ui/inputmap.h b/src/frontend/mame/ui/inputmap.h index e5947a9b649..585d8002a8c 100644 --- a/src/frontend/mame/ui/inputmap.h +++ b/src/frontend/mame/ui/inputmap.h @@ -65,7 +65,7 @@ protected: menu_input(mame_ui_manager &mui, render_container &container); virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; - virtual void custom_render(void *selectedref, float top, float bottom, float x1, float y1, float x2, float y2) override; + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float x1, float y1, float x2, float y2) override; void populate_sorted(); void toggle_none_default(input_seq &selected_seq, input_seq &original_seq, const input_seq &selected_defseq); diff --git a/src/frontend/mame/ui/menu.cpp b/src/frontend/mame/ui/menu.cpp index 71e15631879..a5c82aa90c2 100644 --- a/src/frontend/mame/ui/menu.cpp +++ b/src/frontend/mame/ui/menu.cpp @@ -13,7 +13,6 @@ #include "ui/ui.h" #include "ui/mainmenu.h" -#include "ui/utils.h" #include "ui/miscmenu.h" #include "cheat.h" @@ -27,9 +26,9 @@ #include "osdepend.h" -#include <algorithm> #include <cassert> -#include <cmath> +#include <cstdlib> +#include <limits> #include <type_traits> @@ -77,6 +76,12 @@ menu::global_state::global_state(mame_ui_manager &ui) , m_stack() , m_free() , m_hide(false) + , m_current_pointer(-1) + , m_pointer_type(ui_event::pointer::UNKNOWN) + , m_pointer_buttons(0U) + , m_pointer_x(-1.0F) + , m_pointer_y(-1.0F) + , m_pointer_hit(false) { render_manager &render(ui.machine().render()); @@ -122,6 +127,21 @@ void menu::global_state::stack_push(std::unique_ptr<menu> &&menu) } menu->m_parent = std::move(m_stack); m_stack = std::move(menu); + + ui_event uievt; + while (m_stack->machine().ui_input().pop_event(&uievt)) + { + switch (uievt.event_type) + { + case ui_event::type::POINTER_UPDATE: + case ui_event::type::POINTER_LEAVE: + case ui_event::type::POINTER_ABORT: + use_pointer(m_stack->machine().render().ui_target(), m_stack->container(), uievt); + break; + default: + break; + } + } m_stack->machine().ui_input().reset(); } @@ -142,7 +162,22 @@ void menu::global_state::stack_pop() m_stack = std::move(menu->m_parent); menu->m_parent = std::move(m_free); m_free = std::move(menu); - m_ui.machine().ui_input().reset(); + + ui_event uievt; + while (m_free->machine().ui_input().pop_event(&uievt)) + { + switch (uievt.event_type) + { + case ui_event::type::POINTER_UPDATE: + case ui_event::type::POINTER_LEAVE: + case ui_event::type::POINTER_ABORT: + use_pointer(m_free->machine().render().ui_target(), m_free->container(), uievt); + break; + default: + break; + } + } + m_free->machine().ui_input().reset(); } } @@ -187,39 +222,155 @@ uint32_t menu::global_state::ui_handler(render_container &container) if (!m_stack) stack_push(std::make_unique<menu_main>(m_ui, container)); - // ensure topmost menu is active - need a loop because it could push another menu - while (m_stack && !m_stack->is_active()) + while (true) { - m_stack->m_active = true; - m_stack->menu_activated(); - } + // ensure topmost menu is active - need a loop because it could push another menu + while (m_stack && !m_stack->is_active()) + { + m_stack->m_items_drawn = false; + m_stack->m_pointer_state = track_pointer::IDLE; + m_stack->m_active = true; + m_stack->menu_activated(); + if (m_stack && m_stack->is_active()) + { + // menu activated - draw it to ensure it's on-screen before it can process input + m_stack->check_metrics(); + m_stack->do_rebuild(); + m_stack->validate_selection(1); + m_stack->do_draw_menu(); + assert(m_stack); + assert(m_stack->is_active()); + + // display pointer if appropriate + mame_ui_manager::display_pointer pointers[1]{ { m_stack->machine().render().ui_target(), m_pointer_type, m_pointer_x, m_pointer_y } }; + if ((0 <= m_current_pointer) && (ui_event::pointer::TOUCH != m_pointer_type)) + m_ui.set_pointers(std::begin(pointers), std::end(pointers)); + else + m_ui.set_pointers(std::begin(pointers), std::begin(pointers)); + + return mame_ui_manager::HANDLER_UPDATE; + } + } - // update the menu state - m_hide = false; - bool need_update = m_stack && m_stack->do_handle(); + // update the menu state + m_hide = false; + bool need_update(m_stack && m_stack->do_handle()); - // clear up anything pending being released - clear_free_list(); + // clear up anything pending being released + clear_free_list(); - // if the menus are to be hidden, return a cancel here - if (m_ui.is_menu_active() && (m_hide || !m_stack)) - { - if (m_stack) + // if the menus are to be hidden, return a cancel here + if (m_ui.is_menu_active() && (m_hide || !m_stack)) { - if (m_stack->is_one_shot()) + if (m_stack) { - stack_pop(); + if (m_stack->is_one_shot()) + { + stack_pop(); + } + else if (m_stack->is_active()) + { + m_stack->m_active = false; + m_stack->menu_deactivated(); + } } - else if (m_stack->is_active()) + + // forget about pointers while menus aren't handling events + m_current_pointer = -1; + m_pointer_type = ui_event::pointer::UNKNOWN; + m_pointer_buttons = 0U; + m_pointer_x = -1.0F; + m_pointer_y = -1.0F; + m_pointer_hit = false; + + return mame_ui_manager::HANDLER_CANCEL; + } + + // if the menu is still active, draw it, otherwise try again + if (m_stack->is_active()) + { + m_stack->do_draw_menu(); + + // display pointer if appropriate + mame_ui_manager::display_pointer pointers[1]{ { m_stack->machine().render().ui_target(), m_pointer_type, m_pointer_x, m_pointer_y } }; + if ((0 <= m_current_pointer) && (ui_event::pointer::TOUCH != m_pointer_type)) + m_ui.set_pointers(std::begin(pointers), std::end(pointers)); + else + m_ui.set_pointers(std::begin(pointers), std::begin(pointers)); + + return need_update ? mame_ui_manager::HANDLER_UPDATE : 0; + } + } +} + + +std::pair<bool, bool> menu::global_state::use_pointer(render_target &target, render_container &container, ui_event const &uievt) +{ + if (&target != uievt.target) + return std::make_pair(false, false); + + switch (uievt.event_type) + { + case ui_event::type::POINTER_UPDATE: + // if it's our current pointer, just update it + if (uievt.pointer_id == m_current_pointer) + { + assert(uievt.pointer_type == m_pointer_type); + assert(uievt.pointer_buttons == ((m_pointer_buttons & ~uievt.pointer_released) | uievt.pointer_pressed)); + + m_pointer_buttons = uievt.pointer_buttons; + m_pointer_hit = target.map_point_container( + uievt.pointer_x, + uievt.pointer_y, + container, + m_pointer_x, + m_pointer_y); + return std::make_pair(true, false); + } + + // don't change if the current pointer has buttons pressed and this one doesn't + if ((0 > m_current_pointer) || (!m_pointer_buttons && (!m_pointer_hit || uievt.pointer_pressed))) + { + float x, y; + bool const hit(target.map_point_container(uievt.pointer_x, uievt.pointer_y, container, x, y)); + if ((0 > m_current_pointer) || uievt.pointer_pressed || (!m_pointer_hit && hit)) { - m_stack->m_active = false; - m_stack->menu_deactivated(); + m_current_pointer = uievt.pointer_id; + m_pointer_type = uievt.pointer_type; + m_pointer_buttons = uievt.pointer_buttons; + m_pointer_x = x; + m_pointer_y = y; + m_pointer_hit = hit; + return std::make_pair(true, true); } } - return mame_ui_manager::HANDLER_CANCEL; - } - return need_update ? mame_ui_manager::HANDLER_UPDATE : 0; + // keep current pointer + return std::make_pair(false, false); + + case ui_event::type::POINTER_LEAVE: + case ui_event::type::POINTER_ABORT: + // irrelevant if it isn't our current pointer + if (uievt.pointer_id != m_current_pointer) + return std::make_pair(false, false); + + assert(uievt.pointer_type == m_pointer_type); + assert(uievt.pointer_released == m_pointer_buttons); + + // keep the coordinates where we lost the pointer + m_current_pointer = -1; + m_pointer_buttons = 0U; + m_pointer_hit = target.map_point_container( + uievt.pointer_x, + uievt.pointer_y, + container, + m_pointer_x, + m_pointer_y); + return std::make_pair(true, false); + + default: + std::abort(); + } } @@ -248,22 +399,31 @@ menu::menu(mame_ui_manager &mui, render_container &container) , m_lr_border(0.0F) , m_lr_arrow_width(0.0F) , m_ud_arrow_width(0.0F) + , m_items_left(0.0F) + , m_items_right(0.0F) + , m_items_top(0.0F) + , m_adjust_top(0.0F) + , m_adjust_bottom(0.0F) + , m_decrease_left(0.0F) + , m_increase_left(0.0F) + , m_show_up_arrow(false) + , m_show_down_arrow(false) + , m_items_drawn(false) + , m_pointer_state(track_pointer::IDLE) + , m_pointer_down(0.0F, 0.0F) + , m_pointer_updated(0.0F, 0.0F) + , m_pointer_line(0) + , m_pointer_repeat(std::chrono::steady_clock::time_point::min()) , m_process_flags(0) , m_selected(0) - , m_hover(1) , m_special_main_menu(false) , m_one_shot(false) , m_needs_prev_menu_item(true) , m_active(false) - , m_event() , m_customtop(0.0F) , m_custombottom(0.0F) , m_resetpos(0) , m_resetref(nullptr) - , m_mouse_hit(false) - , m_mouse_button(false) - , m_mouse_x(-1.0F) - , m_mouse_y(-1.0F) { reset(reset_options::SELECT_FIRST); @@ -288,6 +448,10 @@ menu::~menu() void menu::reset(reset_options options) { + // don't accept pointer input until the menu has been redrawn + m_items_drawn = false; + m_pointer_state = track_pointer::IDLE; + // based on the reset option, set the reset info m_resetpos = 0; m_resetref = nullptr; @@ -393,62 +557,6 @@ void menu::set_custom_space(float top, float bottom) //------------------------------------------------- -// process - process a menu, drawing it -// and returning any interesting events -//------------------------------------------------- - -std::pair<const menu::event *, bool> menu::process() -{ - // reset the event - m_event.iptkey = IPT_INVALID; - - // first make sure our selection is valid - validate_selection(1); - - // if we're not running the emulation, draw parent menus in the background - auto const draw_parent = - [] (auto &self, menu *parent) -> bool - { - if (!parent || !(parent->is_special_main_menu() || self(self, parent->m_parent.get()))) - return false; - else - parent->draw(PROCESS_NOINPUT); - return true; - }; - if (draw_parent(draw_parent, m_parent.get())) - container().add_rect(0.0F, 0.0F, 1.0F, 1.0F, rgb_t(114, 0, 0, 0), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); - - // draw the menu proper - // FIXME: this should happen after processing events to fix the egregious latency - // The main thing preventing this is that mouse handling is mixed up with drawing - draw(m_process_flags); - - // process input - if (!(m_process_flags & (PROCESS_NOKEYS | PROCESS_NOINPUT))) - { - // read events - handle_events(m_process_flags, m_event); - - // handle the keys if we don't already have an event - if (m_event.iptkey == IPT_INVALID) - handle_keys(m_process_flags, m_event.iptkey); - } - - // update the selected item in the event - if ((m_event.iptkey != IPT_INVALID) && selection_valid()) - { - m_event.itemref = get_selection_ref(); - m_event.item = &m_items[m_selected]; - return std::make_pair(&m_event, false); - } - else - { - return std::make_pair(nullptr, false); - } -} - - -//------------------------------------------------- // set_selection - changes the index // of the currently selected menu item //------------------------------------------------- @@ -473,27 +581,37 @@ void menu::set_selection(void *selected_itemref) ***************************************************************************/ //------------------------------------------------- -// draw - draw a menu +// do_draw_menu - draw a menu //------------------------------------------------- -void menu::draw(uint32_t flags) +void menu::do_draw_menu() { - // first draw the FPS counter - // FIXME: provide a way to do this in the UI manager itself while menus are on-screen - if (ui().show_fps_counter()) - { - ui().draw_text_full( - container(), - machine().video().speed_text(), - 0.0F, 0.0F, 1.0F, - text_layout::text_justify::RIGHT, text_layout::word_wrapping::WORD, - mame_ui_manager::OPAQUE_, rgb_t::white(), rgb_t::black(), - nullptr, nullptr); - } + // if we're not running the emulation, draw parent menus in the background + auto const draw_parent = + [] (auto &self, menu *parent) -> bool + { + if (!parent || !(parent->is_special_main_menu() || self(self, parent->m_parent.get()))) + return false; + else + parent->draw(PROCESS_NOINPUT); + return true; + }; + if (draw_parent(draw_parent, m_parent.get())) + container().add_rect(0.0F, 0.0F, 1.0F, 1.0F, rgb_t(114, 0, 0, 0), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + + // draw the menu proper + draw(m_process_flags); +} + + +//------------------------------------------------- +// draw - draw the menu itself +//------------------------------------------------- +void menu::draw(uint32_t flags) +{ bool const customonly = (flags & PROCESS_CUSTOM_ONLY); - bool const noinput = (flags & PROCESS_NOINPUT); - float const max_width = 1.0F - ((lr_border() + (machine().render().ui_aspect(&container()) * UI_LINE_WIDTH)) * 2.0F); + float const max_width = 1.0F - ((lr_border() + (x_aspect() * UI_LINE_WIDTH)) * 2.0F); if (is_special_main_menu()) draw_background(); @@ -550,13 +668,13 @@ void menu::draw(uint32_t flags) // compute top/left of inner menu area by centering float const visible_left = (1.0F - visible_width) * 0.5F; - float const visible_top = std::round((((1.0F - visible_main_menu_height - visible_extra_menu_height) * 0.5F) + top_extra_menu_height) * float(m_last_size.second)) / float(m_last_size.second); + m_items_top = std::round((((1.0F - visible_main_menu_height - visible_extra_menu_height) * 0.5F) + top_extra_menu_height) * float(m_last_size.second)) / float(m_last_size.second); // first add us a box float const x1 = visible_left - lr_border(); - float const y1 = visible_top - tb_border(); + float const y1 = m_items_top - tb_border(); float const x2 = visible_left + visible_width + lr_border(); - float const y2 = visible_top + visible_main_menu_height + tb_border(); + float const y2 = m_items_top + visible_main_menu_height + tb_border(); if (!customonly) { if (heading_layout) @@ -586,29 +704,43 @@ void menu::draw(uint32_t flags) top_line = m_selected - m_visible_lines + ((m_selected == (m_items.size() - 1)) ? 1: 2); // if scrolling, show arrows - bool const show_top_arrow((m_items.size() > m_visible_lines) && !first_item_visible()); - bool const show_bottom_arrow((m_items.size() > m_visible_lines) && !last_item_visible()); + m_show_up_arrow = (m_items.size() > m_visible_lines) && !first_item_visible(); + m_show_down_arrow = (m_items.size() > m_visible_lines) && !last_item_visible(); // set the number of visible lines, minus 1 for top arrow and 1 for bottom arrow - m_visible_items = m_visible_lines - (show_top_arrow ? 1 : 0) - (show_bottom_arrow ? 1 : 0); + m_visible_items = m_visible_lines - (m_show_up_arrow ? 1 : 0) - (m_show_down_arrow ? 1 : 0); // determine effective positions taking into account the hilighting arrows float const effective_width = visible_width - 2.0F * gutter_width(); float const effective_left = visible_left + gutter_width(); - // locate mouse - if (!customonly && !noinput) - map_mouse(); - else - ignore_mouse(); - // loop over visible lines - m_hover = m_items.size() + 1; bool selected_subitem_too_big = false; - float const line_x0 = x1 + 0.5F * UI_LINE_WIDTH; - float const line_x1 = x2 - 0.5F * UI_LINE_WIDTH; - if (!customonly) + m_items_left = x1 + 0.5F * UI_LINE_WIDTH; + m_items_right = x2 - 0.5F * UI_LINE_WIDTH; + if (customonly) + { + m_items_drawn = false; + switch (m_pointer_state) + { + case track_pointer::IDLE: + case track_pointer::IGNORED: + case track_pointer::COMPLETED: + case track_pointer::CUSTOM: + break; + case track_pointer::TRACK_LINE: + case track_pointer::SCROLL: + case track_pointer::ADJUST: + m_pointer_state = track_pointer::COMPLETED; + } + } + else { + m_adjust_top = 1.0F; + m_adjust_bottom = 0.0F; + m_decrease_left = -1.0F; + m_increase_left = -1.0F; + m_items_drawn = true; for (int linenum = 0; linenum < m_visible_lines; linenum++) { auto const itemnum = top_line + linenum; @@ -618,63 +750,66 @@ void menu::draw(uint32_t flags) rgb_t bgcolor = ui().colors().text_bg_color(); rgb_t fgcolor2 = ui().colors().subitem_color(); rgb_t fgcolor3 = ui().colors().clone_color(); - float const line_y0 = visible_top + (float)linenum * line_height(); + float const line_y0 = m_items_top + (float(linenum) * line_height()); float const line_y1 = line_y0 + line_height(); // work out what we're dealing with - bool const uparrow = !linenum && show_top_arrow; - bool const downarrow = (linenum == (m_visible_lines - 1)) && show_bottom_arrow; - - // set the hover if this is our item - bool const hovered = mouse_in_rect(line_x0, line_y0, line_x1, line_y1); - if (hovered) - { - if (uparrow) - m_hover = HOVER_ARROW_UP; - else if (downarrow) - m_hover = HOVER_ARROW_DOWN; - else if (is_selectable(pitem)) - m_hover = itemnum; - } + bool const uparrow = !linenum && m_show_up_arrow; + bool const downarrow = (linenum == (m_visible_lines - 1)) && m_show_down_arrow; + // highlight if necessary if (is_selected(itemnum)) { // if we're selected, draw with a different background fgcolor = fgcolor2 = fgcolor3 = ui().colors().selected_color(); bgcolor = ui().colors().selected_bg_color(); } - else if (hovered && (uparrow || downarrow || is_selectable(pitem))) + else if (uparrow || downarrow || is_selectable(pitem)) { - // else if the mouse is over this item, draw with a different background - fgcolor = fgcolor2 = fgcolor3 = ui().colors().mouseover_color(); - bgcolor = ui().colors().mouseover_bg_color(); + bool pointerline(linenum == m_pointer_line); + if ((track_pointer::ADJUST == m_pointer_state) && pointerline) + { + // use the hover background if an adjust gesture is attempted on an item that isn't selected + fgcolor = fgcolor2 = fgcolor3 = ui().colors().mouseover_color(); + bgcolor = ui().colors().mouseover_bg_color(); + } + else if (have_pointer() && pointer_in_rect(m_items_left, line_y0, m_items_right, line_y1)) + { + if ((track_pointer::TRACK_LINE == m_pointer_state) && pointerline) + { + // use the selected background for an item being selected + fgcolor = fgcolor2 = fgcolor3 = ui().colors().selected_color(); + bgcolor = ui().colors().selected_bg_color(); + } + else if (track_pointer::IDLE == m_pointer_state) + { + // else if the pointer is over this item, draw with a different background + fgcolor = fgcolor2 = fgcolor3 = ui().colors().mouseover_color(); + bgcolor = ui().colors().mouseover_bg_color(); + } + } + else if ((track_pointer::TRACK_LINE == m_pointer_state) && pointerline) + { + // use the hover background if the pointer moved out of the tracked item + fgcolor = fgcolor2 = fgcolor3 = ui().colors().mouseover_color(); + bgcolor = ui().colors().mouseover_bg_color(); + } } // if we have some background hilighting to do, add a quad behind everything else if (bgcolor != ui().colors().text_bg_color()) - highlight(line_x0, line_y0, line_x1, line_y1, bgcolor); + highlight(m_items_left, line_y0, m_items_right, line_y1, bgcolor); - if (uparrow) - { - // if we're on the top line, display the up arrow - draw_arrow( - 0.5F * (x1 + x2) - 0.5F * ud_arrow_width(), - line_y0 + 0.25F * line_height(), - 0.5F * (x1 + x2) + 0.5F * ud_arrow_width(), - line_y0 + 0.75F * line_height(), - fgcolor, - ROT0); - } - else if (downarrow) + if (uparrow || downarrow) { - // if we're on the bottom line, display the down arrow + // if we're on the top or bottom line, display the up or down arrow draw_arrow( - 0.5F * (x1 + x2) - 0.5F * ud_arrow_width(), - line_y0 + 0.25F * line_height(), - 0.5F * (x1 + x2) + 0.5F * ud_arrow_width(), - line_y0 + 0.75F * line_height(), + 0.5F * (x1 + x2 - ud_arrow_width()), + line_y0 + (0.25F * line_height()), + 0.5F * (x1 + x2 + ud_arrow_width()), + line_y0 + (0.75F * line_height()), fgcolor, - ROT0 ^ ORIENTATION_FLIP_Y); + downarrow ? (ROT0 ^ ORIENTATION_FLIP_Y) : ROT0); } else if (pitem.type() == menu_item_type::SEPARATOR) { @@ -765,27 +900,22 @@ void menu::draw(uint32_t flags) } // apply arrows - if (is_selected(itemnum) && (pitem.flags() & FLAG_LEFT_ARROW)) + if (is_selected(itemnum) && (pitem.flags() & (FLAG_LEFT_ARROW | FLAG_RIGHT_ARROW))) { - float const l = effective_left + effective_width - subitem_width - gutter_width(); - float const r = l + lr_arrow_width(); - draw_arrow( - l, line_y0 + 0.1F * line_height(), r, line_y0 + 0.9F * line_height(), - fgcolor, - ROT90 ^ ORIENTATION_FLIP_X); - if (mouse_in_rect(l, line_y0 + 0.1F * line_height(), r, line_y0 + 0.9F * line_height())) - m_hover = HOVER_UI_LEFT; - } - if (is_selected(itemnum) && (pitem.flags() & FLAG_RIGHT_ARROW)) - { - float const r = effective_left + effective_width + gutter_width(); - float const l = r - lr_arrow_width(); - draw_arrow( - l, line_y0 + 0.1F * line_height(), r, line_y0 + 0.9F * line_height(), - fgcolor, - ROT90); - if (mouse_in_rect(l, line_y0 + 0.1F * line_height(), r, line_y0 + 0.9F * line_height())) - m_hover = HOVER_UI_RIGHT; + m_adjust_top = line_y0 + (0.1F * line_height()); + m_adjust_bottom = line_y0 + (0.9F * line_height()); + if (pitem.flags() & FLAG_LEFT_ARROW) + { + m_decrease_left = effective_left + effective_width - subitem_width - gutter_width(); + float const r = m_decrease_left + lr_arrow_width(); + draw_arrow(m_decrease_left, m_adjust_top, r, m_adjust_bottom, fgcolor, ROT90 ^ ORIENTATION_FLIP_X); + } + if (pitem.flags() & FLAG_RIGHT_ARROW) + { + float const r = effective_left + effective_width + gutter_width(); + m_increase_left = r - lr_arrow_width(); + draw_arrow(m_increase_left, m_adjust_top, r, m_adjust_bottom, fgcolor, ROT90); + } } } } @@ -797,7 +927,7 @@ void menu::draw(uint32_t flags) menu_item const &pitem = selected_item(); bool const subitem_invert(pitem.flags() & FLAG_INVERT); auto const linenum = m_selected - top_line; - float const line_y = visible_top + float(linenum) * line_height(); + float const line_y = m_items_top + float(linenum) * line_height(); // compute the multi-line target width/height auto const [target_width, target_height] = get_text_dimensions( @@ -828,7 +958,7 @@ void menu::draw(uint32_t flags) } // if there is something special to add, do it by calling the virtual method - custom_render(get_selection_ref(), m_customtop, m_custombottom, x1, y1, x2, y2); + custom_render(flags, get_selection_ref(), m_customtop, m_custombottom, x1, y1, x2, y2); } void menu::recompute_metrics(uint32_t width, uint32_t height, float aspect) @@ -843,42 +973,15 @@ void menu::recompute_metrics(uint32_t width, uint32_t height, float aspect) m_lr_arrow_width = 0.4F * m_line_height * aspect; m_ud_arrow_width = m_line_height * aspect; -} - -void menu::custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) -{ -} + // don't accept pointer input until the menu has been redrawn + m_items_drawn = false; + m_pointer_state = track_pointer::IDLE; -//------------------------------------------------- -// map_mouse - map mouse pointer location to menu -// coordinates -//------------------------------------------------- - -void menu::map_mouse() -{ - ignore_mouse(); - int32_t mouse_target_x, mouse_target_y; - render_target *const mouse_target = machine().ui_input().find_mouse(&mouse_target_x, &mouse_target_y, &m_mouse_button); - if (mouse_target) - { - if (mouse_target->map_point_container(mouse_target_x, mouse_target_y, container(), m_mouse_x, m_mouse_y)) - m_mouse_hit = true; - } } - -//------------------------------------------------- -// ignore_mouse - set members to ignore mouse -// input -//------------------------------------------------- - -void menu::ignore_mouse() +void menu::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float x, float y, float x2, float y2) { - m_mouse_hit = false; - m_mouse_button = false; - m_mouse_x = -1.0F; - m_mouse_y = -1.0F; } @@ -887,8 +990,9 @@ void menu::ignore_mouse() // input events for a menu //------------------------------------------------- -void menu::handle_events(uint32_t flags, event &ev) +bool menu::handle_events(uint32_t flags, event &ev) { + bool need_update = false; bool stop = false; ui_event local_menu_event; @@ -897,119 +1001,93 @@ void menu::handle_events(uint32_t flags, event &ev) { switch (local_menu_event.event_type) { - // if we are hovering over a valid item, select it with a single click - case ui_event::type::MOUSE_DOWN: - if (custom_mouse_down()) - return; - - if (!(flags & PROCESS_ONLYCHAR)) + // deal with pointer-like input (mouse, pen, touch, etc.) + case ui_event::type::POINTER_UPDATE: { - if (m_hover >= 0 && m_hover < m_items.size()) - { - m_selected = m_hover; - } - else if (m_hover == HOVER_ARROW_UP) - { - if (flags & PROCESS_CUSTOM_NAV) - { - ev.iptkey = IPT_UI_PAGE_UP; - stop = true; - } - else - { - m_selected -= m_visible_items; - if (m_selected < 0) - m_selected = 0; - top_line -= m_visible_items - (last_item_visible() ? 1 : 0); - } - } - else if (m_hover == HOVER_ARROW_DOWN) - { - if (flags & PROCESS_CUSTOM_NAV) - { - ev.iptkey = IPT_UI_PAGE_DOWN; - stop = true; - } - else - { - m_selected += m_visible_lines - 2 + is_first_selected(); - if (m_selected > m_items.size() - 1) - m_selected = m_items.size() - 1; - top_line += m_visible_lines - 2; - } - } - else if (m_hover == HOVER_UI_LEFT) + auto const [key, redraw] = handle_pointer_update(flags, local_menu_event); + need_update = need_update || redraw; + if (IPT_INVALID != key) { - ev.iptkey = IPT_UI_LEFT; + ev.iptkey = key; stop = true; } - else if (m_hover == HOVER_UI_RIGHT) + } + break; + + // pointer left the normal way, possibly releasing buttons + case ui_event::type::POINTER_LEAVE: + { + auto const [key, redraw] = handle_pointer_leave(flags, local_menu_event); + need_update = need_update || redraw; + if (IPT_INVALID != key) { - ev.iptkey = IPT_UI_RIGHT; + ev.iptkey = key; stop = true; } } break; - // if we are hovering over a valid item, fake a UI_SELECT with a double-click - case ui_event::type::MOUSE_DOUBLE_CLICK: - if (!(flags & PROCESS_ONLYCHAR) && m_hover >= 0 && m_hover < m_items.size()) + // pointer left in some abnormal way - cancel any associated actions + case ui_event::type::POINTER_ABORT: { - m_selected = m_hover; - ev.iptkey = IPT_UI_SELECT; - if (is_last_selected() && m_needs_prev_menu_item) + auto const [key, redraw] = handle_pointer_abort(flags, local_menu_event); + need_update = need_update || redraw; + if (IPT_INVALID != key) { - ev.iptkey = IPT_UI_BACK; - stack_pop(); - if (is_special_main_menu()) - machine().schedule_exit(); + ev.iptkey = key; + stop = true; } - stop = true; } break; // caught scroll event case ui_event::type::MOUSE_WHEEL: - if (!custom_mouse_scroll((0 < local_menu_event.zdelta) ? -local_menu_event.num_lines : local_menu_event.num_lines) && !(flags & (PROCESS_ONLYCHAR | PROCESS_CUSTOM_NAV))) + if ((track_pointer::IDLE == m_pointer_state) || (track_pointer::IGNORED == m_pointer_state)) { - if (local_menu_event.zdelta > 0) + if (!custom_mouse_scroll((0 < local_menu_event.zdelta) ? -local_menu_event.num_lines : local_menu_event.num_lines) && !(flags & (PROCESS_ONLYCHAR | PROCESS_CUSTOM_NAV))) { - if (is_first_selected()) + if (local_menu_event.zdelta > 0) { - select_last_item(); + if (is_first_selected()) + { + select_last_item(); + } + else + { + m_selected -= local_menu_event.num_lines; + validate_selection(-1); + } + top_line -= (m_selected <= top_line && top_line != 0); + if (m_selected <= top_line && m_visible_items != m_visible_lines) + top_line -= local_menu_event.num_lines; } else { - m_selected -= local_menu_event.num_lines; - validate_selection(-1); + if (is_last_selected()) + { + select_first_item(); + } + else + { + m_selected += local_menu_event.num_lines; + validate_selection(1); + } + top_line += (m_selected >= top_line + m_visible_items + (top_line != 0)); + if (m_selected >= (top_line + m_visible_items + (top_line != 0))) + top_line += local_menu_event.num_lines; } - top_line -= (m_selected <= top_line && top_line != 0); - if (m_selected <= top_line && m_visible_items != m_visible_lines) - top_line -= local_menu_event.num_lines; - } - else - { - if (is_last_selected()) - { - select_first_item(); - } - else - { - m_selected += local_menu_event.num_lines; - validate_selection(1); - } - top_line += (m_selected >= top_line + m_visible_items + (top_line != 0)); - if (m_selected >= (top_line + m_visible_items + (top_line != 0))) - top_line += local_menu_event.num_lines; } } break; // translate CHAR events into specials case ui_event::type::IME_CHAR: - ev.iptkey = IPT_SPECIAL; - ev.unichar = local_menu_event.ch; - stop = true; + if ((track_pointer::IDLE == m_pointer_state) || (track_pointer::IGNORED == m_pointer_state)) + { + ev.iptkey = IPT_SPECIAL; + ev.unichar = local_menu_event.ch; + stop = true; + } break; // ignore everything else @@ -1017,6 +1095,536 @@ void menu::handle_events(uint32_t flags, event &ev) break; } } + + // deal with repeating scroll arrows + if ((track_pointer::TRACK_LINE == m_pointer_state) && ((!m_pointer_line && m_show_up_arrow) || ((m_pointer_line == (m_visible_lines - 1)) && m_show_down_arrow))) + { + float const linetop(m_items_top + (float(m_pointer_line) * line_height())); + float const linebottom(m_items_top + (float(m_pointer_line + 1) * line_height())); + if (pointer_in_rect(m_items_left, linetop, m_items_right, linebottom)) + { + if (std::chrono::steady_clock::now() >= m_pointer_repeat) + { + if (!m_pointer_line) + { + // scroll up + assert(0 < top_line); + --top_line; + if (!top_line) + m_pointer_state = track_pointer::COMPLETED; + } + else + { + // scroll down + assert(m_items.size() > (top_line + m_visible_lines)); + ++top_line; + if (m_items.size() == (top_line + m_visible_lines)) + m_pointer_state = track_pointer::COMPLETED; + } + force_visible_selection(); + need_update = true; + m_pointer_repeat += std::chrono::milliseconds(100); + } + } + } + + return need_update; +} + + +//------------------------------------------------- +// handle_pointer_update - handle a regular +// pointer update +//------------------------------------------------- + +std::pair<int, bool> menu::handle_pointer_update(uint32_t flags, ui_event const &uievt) +{ + // decide whether to make this our current pointer + render_target &target(machine().render().ui_target()); + auto const [ours, changed] = m_global_state.use_pointer(target, container(), uievt); + if (!ours) + { + return std::make_pair(IPT_INVALID, false); + } + else if (changed) + { + // if the active pointer changed, ignore if any buttons were already down + if (uievt.pointer_buttons != uievt.pointer_pressed) + { + m_pointer_state = track_pointer::IGNORED; + return std::make_pair(IPT_INVALID, false); + } + else + { + m_pointer_state = track_pointer::IDLE; + } + } + else if ((track_pointer::IGNORED == m_pointer_state) || (track_pointer::COMPLETED == m_pointer_state)) + { + // stop ignoring the pointer if all buttons were released + if (uievt.pointer_buttons == uievt.pointer_pressed) + m_pointer_state = track_pointer::IDLE; + else + return std::make_pair(IPT_INVALID, false); + } + + // give derived class a chance to handle it + if ((track_pointer::IDLE == m_pointer_state) || (track_pointer::CUSTOM == m_pointer_state)) + { + auto const [key, take, redraw] = custom_pointer_updated(changed, uievt); + if (take) + { + m_pointer_state = track_pointer::CUSTOM; + return std::make_pair(key, redraw); + } + else if (track_pointer::CUSTOM == m_pointer_state) + { + if (uievt.pointer_buttons) + { + m_pointer_state = track_pointer::COMPLETED; + return std::make_pair(key, redraw); + } + else + { + m_pointer_state = track_pointer::IDLE; + } + } + + if (IPT_INVALID != key) + return std::make_pair(key, redraw); + } + + // ignore altogether if menu hasn't been drawn or flags say so + if (!m_items_drawn || (flags & (PROCESS_CUSTOM_ONLY | PROCESS_ONLYCHAR))) + { + if (uievt.pointer_pressed) + { + if (track_pointer::IDLE == m_pointer_state) + m_pointer_state = track_pointer::IGNORED; + } + else if (!uievt.pointer_buttons) + { + if ((track_pointer::IGNORED == m_pointer_state) || (track_pointer::COMPLETED == m_pointer_state)) + m_pointer_state = track_pointer::IDLE; + } + return std::make_pair(IPT_INVALID, false); + } + + switch (m_pointer_state) + { + case track_pointer::IDLE: + // ignore anything other than left click for now + if ((uievt.pointer_pressed & 0x01) && !(uievt.pointer_buttons & ~u32(0x1))) + return handle_primary_down(flags, uievt); + else if (uievt.pointer_pressed) + m_pointer_state = track_pointer::IGNORED; + break; + + case track_pointer::IGNORED: + case track_pointer::COMPLETED: + case track_pointer::CUSTOM: + std::abort(); // won't get here - handled earlier + + case track_pointer::TRACK_LINE: + { + auto const result(update_line_click(uievt)); + + // treat anything else being pressed as cancelling the click sequence + if (uievt.pointer_buttons & ~u32(0x01)) + m_pointer_state = track_pointer::COMPLETED; + else if (!uievt.pointer_buttons) + m_pointer_state = track_pointer::IDLE; + + return result; + } + + case track_pointer::SCROLL: + { + bool const redraw(update_drag_scroll(uievt)); + + // treat anything else being pressed as cancelling the drag + if (uievt.pointer_buttons & ~u32(0x01)) + m_pointer_state = track_pointer::COMPLETED; + else if (!uievt.pointer_buttons) + m_pointer_state = track_pointer::IDLE; + + return std::make_pair(IPT_INVALID, redraw); + } + + case track_pointer::ADJUST: + { + auto const result(update_drag_adjust(uievt)); + + // treat anything else being pressed as cancelling the drag + if (uievt.pointer_buttons & ~u32(0x01)) + m_pointer_state = track_pointer::COMPLETED; + else if (!uievt.pointer_buttons) + m_pointer_state = track_pointer::IDLE; + + return result; + } + } + + return std::make_pair(IPT_INVALID, false); +} + + +//------------------------------------------------- +// handle_pointer_leave - handle a pointer +// leaving the window +//------------------------------------------------- + +std::pair<int, bool> menu::handle_pointer_leave(uint32_t flags, ui_event const &uievt) +{ + // ignore pointer input in windows other than the one that displays the UI + render_target &target(machine().render().ui_target()); + auto const [ours, changed] = m_global_state.use_pointer(target, container(), uievt); + assert(!changed); + if (!ours) + return std::make_pair(IPT_INVALID, false); + + int key(IPT_INVALID); + bool redraw(false); + switch (m_pointer_state) + { + case track_pointer::IDLE: + case track_pointer::CUSTOM: + std::tie(key, std::ignore, redraw) = custom_pointer_updated(changed, uievt); + break; + + case track_pointer::IGNORED: + case track_pointer::COMPLETED: + break; // nothing to do + + case track_pointer::TRACK_LINE: + std::tie(key, redraw) = update_line_click(uievt); + break; + + case track_pointer::SCROLL: + redraw = update_drag_scroll(uievt); + break; + + case track_pointer::ADJUST: + std::tie(key, redraw) = update_drag_adjust(uievt); + break; + } + + m_pointer_state = track_pointer::IDLE; + return std::make_pair(key, redraw); +} + + +//------------------------------------------------- +// handle_pointer_abort - handle a pointer +// leaving in an abnormal way +//------------------------------------------------- + +std::pair<int, bool> menu::handle_pointer_abort(uint32_t flags, ui_event const &uievt) +{ + // ignore pointer input in windows other than the one that displays the UI + render_target &target(machine().render().ui_target()); + auto const [ours, changed] = m_global_state.use_pointer(target, container(), uievt); + assert(!changed); + if (!ours) + return std::make_pair(IPT_INVALID, false); + + int key(IPT_INVALID); + bool redraw(false); + if (track_pointer::CUSTOM == m_pointer_state) + std::tie(key, std::ignore, redraw) = custom_pointer_updated(false, uievt); + else if (track_pointer::TRACK_LINE == m_pointer_state) + redraw = true; + m_pointer_state = track_pointer::IDLE; + return std::make_pair(key, redraw); +} + + +//------------------------------------------------- +// handle_primary_down - handle the primary +// action for a pointer device +//------------------------------------------------- + +std::pair<int, bool> menu::handle_primary_down(uint32_t flags, ui_event const &uievt) +{ + // we handle touch differently to mouse or pen + bool const is_touch(ui_event::pointer::TOUCH == uievt.pointer_type); + auto const [x, y] = pointer_location(); // FIXME: need starting location for multi-click actions + + // check increase/decrease arrows first + // FIXME: should repeat if appropriate + if (!is_touch && (y >= m_adjust_top) && (y < m_adjust_bottom)) + { + if ((x >= m_decrease_left) && (x < (m_decrease_left + lr_arrow_width()))) + { + m_pointer_state = track_pointer::COMPLETED; + return std::make_pair(IPT_UI_LEFT, false); + } + else if ((x >= m_increase_left) && (x < (m_increase_left + lr_arrow_width()))) + { + m_pointer_state = track_pointer::COMPLETED; + return std::make_pair(IPT_UI_RIGHT, false); + } + } + + // work out if we’re pointing at an item + if ((x < m_items_left) || (x >= m_items_right) || (y < m_items_top) || (y >= (m_items_top + (float(m_visible_lines) * line_height())))) + { + m_pointer_state = track_pointer::IGNORED; + return std::make_pair(IPT_INVALID, false); + } + auto const lineno(int((y - m_items_top) / line_height())); + assert(lineno >= 0); + assert(lineno < m_visible_lines); + + // map to an action + if (!lineno && m_show_up_arrow) + { + // scroll up + assert(0 < top_line); + --top_line; + force_visible_selection(); + if (top_line) + { + m_pointer_state = track_pointer::TRACK_LINE; + m_pointer_down = std::make_pair(x, y); + m_pointer_updated = m_pointer_down; + m_pointer_line = lineno; + m_pointer_repeat = std::chrono::steady_clock::now() + std::chrono::milliseconds(300); + } + else + { + m_pointer_state = track_pointer::COMPLETED; + } + return std::make_pair(IPT_INVALID, true); + } + else if ((lineno == (m_visible_lines - 1)) && m_show_down_arrow) + { + // scroll down + assert(m_items.size() > (top_line + m_visible_lines)); + ++top_line; + force_visible_selection(); + if (m_items.size() > (top_line + m_visible_lines)) + { + m_pointer_state = track_pointer::TRACK_LINE; + m_pointer_down = std::make_pair(x, y); + m_pointer_updated = m_pointer_down; + m_pointer_line = lineno; + m_pointer_repeat = std::chrono::steady_clock::now() + std::chrono::milliseconds(300); + } + else + { + m_pointer_state = track_pointer::COMPLETED; + } + return std::make_pair(IPT_INVALID, true); + } + else + { + m_pointer_state = track_pointer::TRACK_LINE; + m_pointer_down = std::make_pair(x, y); + m_pointer_updated = m_pointer_down; + m_pointer_line = lineno; + + int const itemno(lineno + top_line); + assert(itemno >= 0); + assert(itemno < m_items.size()); + return std::make_pair(IPT_INVALID, is_selectable(m_items[itemno])); + } + + // nothing to do + m_pointer_state = track_pointer::IGNORED; + return std::make_pair(IPT_INVALID, false); +} + + +//------------------------------------------------- +// update_line_click - track pointer after +// clicking a menu line +//------------------------------------------------- + +std::pair<int, bool> menu::update_line_click(ui_event const &uievt) +{ + assert(track_pointer::TRACK_LINE == m_pointer_state); + assert((uievt.pointer_buttons | uievt.pointer_released) & 0x01); + + // arrows should scroll while held + if ((!m_pointer_line && m_show_up_arrow) || ((m_pointer_line == (m_visible_lines - 1)) && m_show_down_arrow)) + { + // check for re-entry + bool redraw(false); + auto const [x, y] = pointer_location(); + float const linetop(m_items_top + (float(m_pointer_line) * line_height())); + float const linebottom(m_items_top + (float(m_pointer_line + 1) * line_height())); + bool const reentered(reentered_rect(m_pointer_updated.first, m_pointer_updated.second, x, y, m_items_left, linetop, m_items_right, linebottom)); + if (reentered) + { + auto const now(std::chrono::steady_clock::now()); + if (now >= m_pointer_repeat) + { + if (!m_pointer_line) + { + // scroll up + assert(0 < top_line); + --top_line; + if (!top_line) + m_pointer_state = track_pointer::COMPLETED; + } + else + { + // scroll down + assert(m_items.size() > (top_line + m_visible_lines)); + ++top_line; + if (m_items.size() == (top_line + m_visible_lines)) + m_pointer_state = track_pointer::COMPLETED; + } + force_visible_selection(); + redraw = true; + m_pointer_repeat = now + std::chrono::milliseconds(100); + } + } + + // keep the pointer location where we updated + m_pointer_updated = std::make_pair(x, y); + return std::make_pair(IPT_INVALID, redraw); + } + + // check for conversion of a tap to a finger drag + auto const drag_result(check_touch_drag(uievt)); + if (track_pointer::TRACK_LINE != m_pointer_state) + return drag_result; + + // only take action if the primary button was released + if (!(uievt.pointer_released & 0x01)) + return std::make_pair(IPT_INVALID, false); + + // nothing to do if the item isn't selectable + int const itemno = m_pointer_line + top_line; + assert(itemno >= 0); + assert(itemno < m_items.size()); + if (!is_selectable(m_items[itemno])) + return std::make_pair(IPT_INVALID, false); + + // treat multi-click actions as not moving, otherwise check that pointer is still over the line + if (0 >= uievt.pointer_clicks) + { + auto const [x, y] = pointer_location(); + if ((x < m_items_left) || (x >= m_items_right) || (int((y - m_items_top) / line_height()) != m_pointer_line)) + return std::make_pair(IPT_INVALID, true); + } + + // anything other than a double-click just selects the item + m_selected = itemno; + if (2 != uievt.pointer_clicks) + return std::make_pair(IPT_INVALID, true); + + // activate regular items by simulating UI Select + if (!is_last_selected() || !m_needs_prev_menu_item) + return std::make_pair(IPT_UI_SELECT, true); + + // handle the magic final item that dismisses the menu + stack_pop(); + if (is_special_main_menu()) + machine().schedule_exit(); + return std::make_pair(IPT_UI_BACK, true); +} + + +//------------------------------------------------- +// update_drag_scroll - update menu position in +// response to a touch drag +//------------------------------------------------- + +bool menu::update_drag_scroll(ui_event const &uievt) +{ + assert(track_pointer::SCROLL == m_pointer_state); + assert((uievt.pointer_buttons | uievt.pointer_released) & 0x01); + + // get target location + int const newtop(drag_scroll( + pointer_location().second, m_pointer_down.second, m_pointer_updated.second, -line_height(), + m_pointer_line, 0, int(m_items.size() - m_visible_lines))); + if (newtop == top_line) + return false; + + // scroll and move the selection if necessary to keep it in the visible range + top_line = newtop; + force_visible_selection(); + return true; +} + + +//------------------------------------------------- +// update_drag_adjust - adjust value on +// horizontal drag +//------------------------------------------------- + +std::pair<int, bool> menu::update_drag_adjust(ui_event const &uievt) +{ + assert(track_pointer::ADJUST == m_pointer_state); + assert((uievt.pointer_buttons | uievt.pointer_released) & 0x01); + + // this is ugly because adjustment is implemented by faking keystrokes - can't give a count/distance + + // set thresholds depending on the direction for hysteresis + int const target(drag_scroll( + pointer_location().first, m_pointer_updated.first, m_pointer_updated.first, line_height() * x_aspect(), + 0, std::numeric_limits<int>::min(), std::numeric_limits<int>::max())); + + // ensure the item under the pointer is selected and adjustable + if ((top_line + m_pointer_line) == m_selected) + { + if (0 < target) + { + if (m_items[m_selected].flags() & FLAG_RIGHT_ARROW) + return std::make_pair(IPT_UI_RIGHT, true); + } + else if (0 > target) + { + if (m_items[m_selected].flags() & FLAG_LEFT_ARROW) + return std::make_pair(IPT_UI_LEFT, true); + } + } + + // looks like it wasn't to be + return std::make_pair(IPT_INVALID, false); +} + + +//------------------------------------------------- +// check_touch_drag - check for conversion of a +// touch to a scroll or adjust slide +//------------------------------------------------- + +std::pair<int, bool> menu::check_touch_drag(ui_event const &uievt) +{ + // we handle touch differently to mouse or pen + if (ui_event::pointer::TOUCH != uievt.pointer_type) + return std::make_pair(IPT_INVALID, false); + + // check distances + auto const [x, y] = pointer_location(); + auto const [h, v] = check_drag_conversion(x, y, m_pointer_down.first, m_pointer_down.second, line_height()); + if (h) + { + // only the selected line can be adjusted + if ((top_line + m_pointer_line) == m_selected) + { + m_pointer_state = track_pointer::ADJUST; + return update_drag_adjust(uievt); + } + else + { + m_pointer_state = track_pointer::COMPLETED; + } + } + else if (v) + { + m_pointer_state = track_pointer::SCROLL; + m_pointer_line = top_line; + return std::make_pair(IPT_INVALID, update_drag_scroll(uievt)); + } + + // no update needed + return std::make_pair(IPT_INVALID, false); } @@ -1025,13 +1633,13 @@ void menu::handle_events(uint32_t flags, event &ev) // keys for a menu //------------------------------------------------- -void menu::handle_keys(uint32_t flags, int &iptkey) +bool menu::handle_keys(uint32_t flags, int &iptkey) { - bool const ignorepause = (flags & PROCESS_IGNOREPAUSE) || stack_has_special_main_menu(); - - // bail if no items + // bail if no items (happens if event handling triggered an item reset) if (m_items.empty()) - return; + return false; + + bool const ignorepause = (flags & PROCESS_IGNOREPAUSE) || stack_has_special_main_menu(); // if we hit select, return true or pop the stack, depending on the item if (exclusive_input_pressed(iptkey, IPT_UI_SELECT, 0)) @@ -1043,7 +1651,7 @@ void menu::handle_keys(uint32_t flags, int &iptkey) if (is_special_main_menu()) machine().schedule_exit(); } - return; + return false; } // UI configure hides the menus @@ -1053,12 +1661,12 @@ void menu::handle_keys(uint32_t flags, int &iptkey) stack_pop(); else m_global_state.hide_menu(); - return; + return true; } // bail out if (flags & PROCESS_ONLYCHAR) - return; + return false; // hitting back also pops the stack if (exclusive_input_pressed(iptkey, IPT_UI_BACK, 0)) @@ -1070,7 +1678,7 @@ void menu::handle_keys(uint32_t flags, int &iptkey) if (is_special_main_menu()) machine().schedule_exit(); } - return; + return false; } // validate the current selection @@ -1082,16 +1690,19 @@ void menu::handle_keys(uint32_t flags, int &iptkey) // accept left/right/prev/next keys as-is with repeat if appropriate if (!ignoreleft && exclusive_input_pressed(iptkey, IPT_UI_LEFT, (flags & PROCESS_LR_REPEAT) ? 6 : 0)) - return; + return false; if (!ignoreright && exclusive_input_pressed(iptkey, IPT_UI_RIGHT, (flags & PROCESS_LR_REPEAT) ? 6 : 0)) - return; + return false; + + // keep track of whether we changed anything + bool updated(false); // up backs up by one item if (exclusive_input_pressed(iptkey, IPT_UI_UP, 6)) { if (flags & PROCESS_CUSTOM_NAV) { - return; + return updated; } else if (is_first_selected()) { @@ -1105,6 +1716,7 @@ void menu::handle_keys(uint32_t flags, int &iptkey) top_line -= (m_selected <= top_line && top_line != 0); if (m_selected <= top_line && m_visible_items != m_visible_lines) top_line--; + updated = true; } // down advances by one item @@ -1112,7 +1724,7 @@ void menu::handle_keys(uint32_t flags, int &iptkey) { if (flags & PROCESS_CUSTOM_NAV) { - return; + return updated; } else if (is_last_selected()) { @@ -1126,47 +1738,52 @@ void menu::handle_keys(uint32_t flags, int &iptkey) top_line += (m_selected >= top_line + m_visible_items + (top_line != 0)); if (m_selected >= (top_line + m_visible_items + (top_line != 0))) top_line++; + updated = true; } // page up backs up by m_visible_items if (exclusive_input_pressed(iptkey, IPT_UI_PAGE_UP, 6)) { if (flags & PROCESS_CUSTOM_NAV) - return; + return updated; m_selected -= m_visible_items; top_line -= m_visible_items - (last_item_visible() ? 1 : 0); if (m_selected < 0) m_selected = 0; validate_selection(1); + updated = true; } // page down advances by m_visible_items if (exclusive_input_pressed(iptkey, IPT_UI_PAGE_DOWN, 6)) { if (flags & PROCESS_CUSTOM_NAV) - return; + return updated; m_selected += m_visible_lines - 2 + is_first_selected(); top_line += m_visible_lines - 2; if (m_selected > m_items.size() - 1) m_selected = m_items.size() - 1; validate_selection(-1); + updated = true; } // home goes to the start if (exclusive_input_pressed(iptkey, IPT_UI_HOME, 0)) { if (flags & PROCESS_CUSTOM_NAV) - return; + return updated; select_first_item(); + updated = true; } // end goes to the last if (exclusive_input_pressed(iptkey, IPT_UI_END, 0)) { if (flags & PROCESS_CUSTOM_NAV) - return; + return updated; select_last_item(); + updated = true; } // pause enables/disables pause @@ -1202,6 +1819,39 @@ void menu::handle_keys(uint32_t flags, int &iptkey) break; } } + return updated; +} + + +//------------------------------------------------- +// default handler implementations +//------------------------------------------------- + +bool menu::custom_ui_back() +{ + return false; +} + +std::tuple<int, bool, bool> menu::custom_pointer_updated(bool changed, ui_event const &uievt) +{ + return std::make_tuple(IPT_INVALID, false, false); +} + +bool menu::custom_mouse_scroll(int lines) +{ + return false; +} + +void menu::menu_activated() +{ +} + +void menu::menu_deactivated() +{ +} + +void menu::menu_dismissed() +{ } @@ -1249,6 +1899,85 @@ void menu::validate_selection(int scandir) } +//------------------------------------------------- +// check_metrics - recompute metrics if target +// geometry has changed +//------------------------------------------------- + +bool menu::check_metrics() +{ + render_manager &render(machine().render()); + render_target &target(render.ui_target()); + std::pair<uint32_t, uint32_t> const uisize(target.width(), target.height()); + float const aspect = render.ui_aspect(&container()); + if ((uisize == m_last_size) && (aspect == m_last_aspect)) + return false; + + m_last_size = uisize; + m_last_aspect = aspect; + recompute_metrics(uisize.first, uisize.second, aspect); + return true; +} + + +//------------------------------------------------- +// do_rebuild - get the subclass to populate +// the menu items +//------------------------------------------------- + +bool menu::do_rebuild() +{ + if (!m_items.empty()) + return false; + + m_rebuilding = true; + try + { + // add an item to return - this is a really hacky way of doing this + if (m_needs_prev_menu_item) + item_append(_("Return to Previous Menu"), 0, nullptr); + + // let implementation add other items + populate(); + } + catch (...) + { + m_items.clear(); + m_rebuilding = false; + throw; + } + m_rebuilding = false; + return true; +} + + +//------------------------------------------------- +// force_visible_selection - if the selected item +// is not visible, move the selection it it's +// within the visible portion of the menu +//------------------------------------------------- + +void menu::force_visible_selection() +{ + int const first(top_line ? (top_line + 1) : 0); + int const last(top_line + m_visible_lines - ((m_items.size() > (top_line + m_visible_lines)) ? 1 : 0)); + if (first > m_selected) + { + m_selected = first; + while (!is_selectable(m_items[m_selected])) + ++m_selected; + assert(last > m_selected); + } + else if (last <= m_selected) + { + m_selected = last - 1; + while (!is_selectable(m_items[m_selected])) + --m_selected; + assert(first <= m_selected); + } +} + + /*************************************************************************** MENU STACK MANAGEMENT @@ -1262,43 +1991,74 @@ bool menu::do_handle() machine().osd().check_osd_inputs(); // recompute metrics if necessary - render_manager &render(machine().render()); - render_target &target(render.ui_target()); - std::pair<uint32_t, uint32_t> const uisize(target.width(), target.height()); - float const aspect = render.ui_aspect(&container()); - if ((uisize != m_last_size) || (aspect != m_last_aspect)) - { - m_last_size = uisize; - m_last_aspect = aspect; - recompute_metrics(uisize.first, uisize.second, aspect); + if (check_metrics()) need_update = true; - } - if (m_items.empty()) - { - m_rebuilding = true; - try - { - // add an item to return - this is a really hacky way of doing this - if (m_needs_prev_menu_item) - item_append(_("Return to Previous Menu"), 0, nullptr); + // get the implementation to rebuild the list of items if necessary + if (do_rebuild()) + need_update = true; + validate_selection(1); - // let implementation add other items - populate(); + // reset the event + std::optional<event> result; + result.emplace(); + result->itemref = nullptr; + result->item = nullptr; + result->iptkey = IPT_INVALID; + + // process input + uint32_t flags(m_process_flags); + if (!(flags & (PROCESS_NOKEYS | PROCESS_NOINPUT))) + { + // read events + if (handle_events(flags, *result)) need_update = true; - } - catch (...) + + switch (m_pointer_state) { - m_items.clear(); - m_rebuilding = false; - throw; + case track_pointer::IDLE: + case track_pointer::IGNORED: + // handle keys if we don't already have an event and we aren't tracking a pointer action + if ((IPT_INVALID == result->iptkey) && handle_keys(flags, result->iptkey)) + need_update = true; + break; + default: + // ignore keys pressed while tracking a pointer action + for (int code = IPT_UI_FIRST + 1; IPT_UI_LAST > code; ++code) + machine().ui_input().pressed(code); + break; } - m_rebuilding = false; } - auto [eventptr, changed] = process(); - need_update = need_update || changed; - need_update = handle(eventptr) || need_update; + // deal with stack push/pop and rebuild + if (!is_active()) + return false; + if (do_rebuild()) + { + validate_selection(1); + need_update = true; + } + + // update the selected item in the event and let the implementation handle it + if ((result->iptkey != IPT_INVALID) && selection_valid()) + { + result->itemref = get_selection_ref(); + result->item = &m_items[m_selected]; + } + else + { + result.reset(); + } + need_update = handle(result ? &*result : nullptr) || need_update; + + // the implementation had another chance to push/pop or rebuild + if (!is_active()) + return false; + if (do_rebuild()) + { + validate_selection(1); + return true; + } return need_update; } @@ -1328,7 +2088,7 @@ delegate<uint32_t (render_container &)> menu::get_ui_handler(mame_ui_manager &mu text_layout menu::create_layout(float width, text_layout::text_justify justify, text_layout::word_wrapping wrap) { - return text_layout(*ui().get_font(), line_height() * m_last_aspect, line_height(), width, justify, wrap); + return text_layout(*ui().get_font(), line_height() * x_aspect(), line_height(), width, justify, wrap); } diff --git a/src/frontend/mame/ui/menu.h b/src/frontend/mame/ui/menu.h index d383e31ab52..791432482b5 100644 --- a/src/frontend/mame/ui/menu.h +++ b/src/frontend/mame/ui/menu.h @@ -19,16 +19,29 @@ #include "language.h" #include "render.h" +#include "interface/uievents.h" + +#include <algorithm> +#include <chrono> +#include <cmath> #include <functional> #include <map> #include <memory> #include <mutex> #include <optional> #include <string_view> +#include <tuple> #include <utility> #include <vector> +/*************************************************************************** + FORWARD DECLARATIONS +***************************************************************************/ + +struct ui_event; + + namespace ui { /*************************************************************************** @@ -132,11 +145,10 @@ protected: // menu-related events struct event { - void *itemref; // reference for the selected item or nullptr - menu_item *item; // selected item or nullptr - int iptkey; // one of the IPT_* values from inptport.h - char32_t unichar; // unicode character if iptkey == IPT_SPECIAL - render_bounds mouse; // mouse position if iptkey == IPT_CUSTOM + void *itemref; // reference for the selected item or nullptr + menu_item *item; // selected item or nullptr + int iptkey; // one of the IPT_* values from inpttype.h + char32_t unichar; // unicode character if iptkey == IPT_SPECIAL }; menu(mame_ui_manager &mui, render_container &container); @@ -181,10 +193,6 @@ protected: void select_first_item(); void select_last_item(); - int hover() const { return m_hover; } - void set_hover(int index) { m_hover = index; } - void clear_hover() { m_hover = m_items.size() + 1; } - // scroll position control void set_top_line(int index) { top_line = (0 < index) ? (index - 1) : index; } void centre_selection() { top_line = m_selected - (m_visible_lines / 2); } @@ -196,6 +204,8 @@ protected: float get_customtop() const { return m_customtop; } float get_custombottom() const { return m_custombottom; } + std::pair<uint32_t, uint32_t> target_size() const { return m_last_size; } + float x_aspect() const { return m_last_aspect; } float line_height() const { return m_line_height; } float gutter_width() const { return m_gutter_width; } float tb_border() const { return m_tb_border; } @@ -281,7 +291,7 @@ protected: std::string_view const &line(*it); if (!line.empty()) { - text_layout layout(*ui().get_font(), text_size * m_last_aspect, text_size, 1.0, justify, wrap); + text_layout layout(*ui().get_font(), text_size * x_aspect(), text_size, 1.0, justify, wrap); layout.add_text(line, rgb_t::white(), rgb_t::black()); maxwidth = (std::max)(layout.actual_width(), maxwidth); } @@ -328,36 +338,32 @@ protected: // draw additional menu content virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect); - virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2); - - // map mouse to menu coordinates - void map_mouse(); + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float x, float y, float x2, float y2); - // clear the mouse position - void ignore_mouse(); + // access to pointer state + bool have_pointer() const noexcept { return m_global_state.have_pointer(); } + bool pointer_idle() const noexcept { return (track_pointer::IDLE == m_pointer_state) && have_pointer(); } + bool pointer_in_rect(float x0, float y0, float x1, float y1) const noexcept { return m_global_state.pointer_in_rect(x0, y0, x1, y1); } + std::pair<float, float> pointer_location() const noexcept { return m_global_state.pointer_location(); } + osd::ui_event_handler::pointer pointer_type() const noexcept { return m_global_state.pointer_type(); } - bool is_mouse_hit() const { return m_mouse_hit; } // is mouse pointer inside menu's render container? - float get_mouse_x() const { return m_mouse_x; } // mouse x location in menu coordinates - float get_mouse_y() const { return m_mouse_y; } // mouse y location in menu coordinates - - // mouse hit test - checks whether mouse_x is in [x0, x1) and mouse_y is in [y0, y1) - bool mouse_in_rect(float x0, float y0, float x1, float y1) const - { - return m_mouse_hit && (m_mouse_x >= x0) && (m_mouse_x < x1) && (m_mouse_y >= y0) && (m_mouse_y < y1); - } + // derived classes that override handle_events need to call these for pointer events + std::pair<int, bool> handle_pointer_update(uint32_t flags, ui_event const &uievt); + std::pair<int, bool> handle_pointer_leave(uint32_t flags, ui_event const &uievt); + std::pair<int, bool> handle_pointer_abort(uint32_t flags, ui_event const &uievt); // overridable event handling void set_process_flags(uint32_t flags) { m_process_flags = flags; } - virtual void handle_events(uint32_t flags, event &ev); - virtual void handle_keys(uint32_t flags, int &iptkey); - virtual bool custom_ui_back() { return false; } - virtual bool custom_mouse_down() { return false; } - virtual bool custom_mouse_scroll(int lines) { return false; } + virtual bool handle_events(uint32_t flags, event &ev); + virtual bool handle_keys(uint32_t flags, int &iptkey); + virtual bool custom_ui_back(); + virtual std::tuple<int, bool, bool> custom_pointer_updated(bool changed, ui_event const &uievt); + virtual bool custom_mouse_scroll(int lines); // event notifications - virtual void menu_activated() { } - virtual void menu_deactivated() { } - virtual void menu_dismissed() { } + virtual void menu_activated(); + virtual void menu_deactivated(); + virtual void menu_dismissed(); static bool is_selectable(menu_item const &item) { @@ -371,7 +377,47 @@ protected: return ((actual > min) ? FLAG_LEFT_ARROW : 0) | ((actual < max) ? FLAG_RIGHT_ARROW : 0); } + static bool reentered_rect(float x0, float y0, float x1, float y1, float l, float t, float r, float b) + { + return + ((x0 < l) || (x0 >= r) || (y0 < t) || (y0 >= b)) && + ((x1 >= l) && (x1 < r) && (y1 >= t) && (y1 < b)); + } + + std::pair<bool, bool> check_drag_conversion(float x, float y, float x_base, float y_base, float threshold) const + { + float const dx(std::abs((x - x_base) / x_aspect())); + float const dy(std::abs(y - y_base)); + if ((dx > dy) && (dx >= threshold)) + return std::make_pair(true, false); + else if ((dy >= dx) && (dy > threshold)) + return std::make_pair(false, true); + else + return std::make_pair(false, false); + } + + template <typename T> + static T drag_scroll(float location, float base, float &last, float unit, T start, T min, T max) + { + // set thresholds depending on the direction for hysteresis and clamp to valid range + T const target((location - (std::abs(unit) * ((location > last) ? 0.3F : -0.3F)) - base) / unit); + last = base + (float(target) * unit); + return std::clamp(start + target, min, max); + } + private: + // pointer tracking state + enum class track_pointer + { + IDLE, + IGNORED, + COMPLETED, + CUSTOM, + TRACK_LINE, + SCROLL, + ADJUST + }; + class global_state : public widgets_manager { public: @@ -396,17 +442,43 @@ private: uint32_t ui_handler(render_container &container); + bool have_pointer() const noexcept + { + return 0 <= m_current_pointer; + } + bool pointer_in_rect(float x0, float y0, float x1, float y1) const noexcept + { + return (m_pointer_x >= x0) && (m_pointer_x < x1) && (m_pointer_y >= y0) && (m_pointer_y < y1); + } + std::pair<float, float> pointer_location() const noexcept + { + return std::make_pair(m_pointer_x, m_pointer_y); + } + osd::ui_event_handler::pointer pointer_type() const noexcept + { + return m_pointer_type; + } + + std::pair<bool, bool> use_pointer(render_target &target, render_container &container, ui_event const &event); + protected: - mame_ui_manager &m_ui; + mame_ui_manager &m_ui; private: - bitmap_ptr m_bgrnd_bitmap; - texture_ptr m_bgrnd_texture; + bitmap_ptr m_bgrnd_bitmap; + texture_ptr m_bgrnd_texture; - std::unique_ptr<menu> m_stack; - std::unique_ptr<menu> m_free; + std::unique_ptr<menu> m_stack; + std::unique_ptr<menu> m_free; - bool m_hide; + bool m_hide; + + s32 m_current_pointer; // current active pointer ID or -1 if none + osd::ui_event_handler::pointer m_pointer_type; // current pointer type + u32 m_pointer_buttons; // depressed buttons for current pointer + float m_pointer_x; + float m_pointer_y; + bool m_pointer_hit; }; // this is to satisfy the std::any requirement that objects be copyable @@ -417,8 +489,15 @@ private: global_state_wrapper(global_state_wrapper const &that) : global_state(that.m_ui) { } }; - // process a menu, drawing it and returning any interesting events - std::pair<const event *, bool> process(); + // process a menu, returning any interesting events + std::pair<int, bool> handle_primary_down(uint32_t flags, ui_event const &uievt); + std::pair<int, bool> update_line_click(ui_event const &uievt); + bool update_drag_scroll(ui_event const &uievt); + std::pair<int, bool> update_drag_adjust(ui_event const &uievt); + std::pair<int, bool> check_touch_drag(ui_event const &uievt); + + // drawing the menu + void do_draw_menu(); virtual void draw(uint32_t flags); // request the specific handling of the game selection main menu @@ -432,8 +511,11 @@ private: void extra_text_draw_box(float origx1, float origx2, float origy, float yspan, std::string_view text, int direction); + bool check_metrics(); + bool do_rebuild(); bool first_item_visible() const { return top_line <= 0; } bool last_item_visible() const { return (top_line + m_visible_lines) >= m_items.size(); } + void force_visible_selection(); // push a new menu onto the stack static void stack_push(std::unique_ptr<menu> &&menu) { menu->m_global_state.stack_push(std::move(menu)); } @@ -464,26 +546,35 @@ private: float m_lr_arrow_width; float m_ud_arrow_width; + float m_items_left; // left of the area where the items are drawn + float m_items_right; // right of the area where the items are drawn + float m_items_top; // top of the area where the items are drawn + float m_adjust_top; // top of the "increase"/"decrease" arrows + float m_adjust_bottom; // bottom of the "increase"/"decrease" arrows + float m_decrease_left; // left of the "decrease" arrow + float m_increase_left; // left of the "increase" arrow + bool m_show_up_arrow; // are we showing the "scroll up" arrow? + bool m_show_down_arrow; // are we showing the "scroll down" arrow? + bool m_items_drawn; // have we drawn the items at least once? + + track_pointer m_pointer_state; // tracking state for currently active pointer + std::pair<float, float> m_pointer_down; // start location of tracked pointer action + std::pair<float, float> m_pointer_updated; // location where pointer tracking was updated + int m_pointer_line; // the line we're tracking pointer motion in + std::chrono::steady_clock::time_point m_pointer_repeat; + uint32_t m_process_flags; // event processing options int m_selected; // which item is selected - int m_hover; // which item is being hovered over bool m_special_main_menu; // true if no real emulation running under the menu bool m_one_shot; // true for menus outside the normal stack bool m_needs_prev_menu_item; // true to automatically create item to dismiss menu bool m_active; // whether the menu is currently visible and topmost - event m_event; // the UI event that occurred - float m_customtop; // amount of extra height to add at the top float m_custombottom; // amount of extra height to add at the bottom int m_resetpos; // item index to select after repopulating void *m_resetref; // item reference value to select after repopulating - - bool m_mouse_hit; - bool m_mouse_button; - float m_mouse_x; - float m_mouse_y; }; diff --git a/src/frontend/mame/ui/miscmenu.cpp b/src/frontend/mame/ui/miscmenu.cpp index 8ed4e64fac3..df5754a380e 100644 --- a/src/frontend/mame/ui/miscmenu.cpp +++ b/src/frontend/mame/ui/miscmenu.cpp @@ -311,7 +311,7 @@ bool menu_bookkeeping::handle(event const *ev) } else { - return ev && handle_key(ev->iptkey); + return menu_textbox::handle(ev); } } diff --git a/src/frontend/mame/ui/selector.cpp b/src/frontend/mame/ui/selector.cpp index e21026f6d44..53f68006c04 100644 --- a/src/frontend/mame/ui/selector.cpp +++ b/src/frontend/mame/ui/selector.cpp @@ -143,7 +143,7 @@ void menu_selector::recompute_metrics(uint32_t width, uint32_t height, float asp // perform our special rendering //------------------------------------------------- -void menu_selector::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) +void menu_selector::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) { // get the text for 'UI Select' std::string const tempbuf[] = { util::string_format(_("menu-selector", "Double-click or press %1$s to select"), ui().get_general_input_setting(IPT_UI_SELECT)) }; diff --git a/src/frontend/mame/ui/selector.h b/src/frontend/mame/ui/selector.h index 4b8043a990a..58b93c5185a 100644 --- a/src/frontend/mame/ui/selector.h +++ b/src/frontend/mame/ui/selector.h @@ -39,7 +39,7 @@ public: protected: virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; - virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; virtual bool custom_ui_back() override { return !m_search.empty(); } private: diff --git a/src/frontend/mame/ui/selgame.cpp b/src/frontend/mame/ui/selgame.cpp index f9b2c702503..e9cc85e9a43 100644 --- a/src/frontend/mame/ui/selgame.cpp +++ b/src/frontend/mame/ui/selgame.cpp @@ -223,38 +223,6 @@ bool menu_select_game::handle(event const *ev) } else switch (ev->iptkey) { - case IPT_UI_UP: - if ((get_focus() == focused_menu::LEFT) && (machine_filter::FIRST < m_filter_highlight)) - { - --m_filter_highlight; - changed = true; - } - break; - - case IPT_UI_DOWN: - if ((get_focus() == focused_menu::LEFT) && (machine_filter::LAST > m_filter_highlight)) - { - m_filter_highlight++; - changed = true; - } - break; - - case IPT_UI_HOME: - if (get_focus() == focused_menu::LEFT) - { - m_filter_highlight = machine_filter::FIRST; - changed = true; - } - break; - - case IPT_UI_END: - if (get_focus() == focused_menu::LEFT) - { - m_filter_highlight = machine_filter::LAST; - changed = true; - } - break; - case IPT_UI_EXPORT: inkey_export(); break; @@ -278,46 +246,6 @@ bool menu_select_game::handle(event const *ev) } break; - case IPT_CUSTOM: - // handle IPT_CUSTOM (mouse right click) - if (!m_populated_favorites) - { - menu::stack_push<menu_machine_configure>( - ui(), - container(), - *reinterpret_cast<ui_system_info const *>(m_prev_selected), - nullptr); - } - else - { - ui_software_info *sw = reinterpret_cast<ui_software_info *>(m_prev_selected); - ui_system_info const &sys = m_persistent_data.systems()[driver_list::find(sw->driver->name)]; - menu::stack_push<menu_machine_configure>( - ui(), - container(), - sys, - [this, empty = sw->startempty] (bool fav, bool changed) - { - if (changed) - reset(empty ? reset_options::SELECT_FIRST : reset_options::REMEMBER_REF); - }); - } - break; - - case IPT_UI_LEFT: - if (right_panel() == RP_IMAGES) - changed = previous_image_view(); // Images - else if (right_panel() == RP_INFOS) - changed = change_info_pane(-1); // Infos - break; - - case IPT_UI_RIGHT: - if (right_panel() == RP_IMAGES) - changed = next_image_view(); // Images - else if (right_panel() == RP_INFOS) - changed = change_info_pane(1); // Infos - break; - case IPT_UI_FAVORITES: if (uintptr_t(ev->itemref) > m_skip_main_items) { @@ -356,8 +284,6 @@ bool menu_select_game::handle(event const *ev) } } - // if we're in an error state, overlay an error message - draw_error_text(); return changed; } @@ -801,53 +727,6 @@ bool menu_select_game::isfavorite() const //------------------------------------------------- -// change what's displayed in the info box -//------------------------------------------------- - -bool menu_select_game::change_info_pane(int delta) -{ - auto const cap_delta = - [this, &delta] (uint8_t ¤t, uint8_t &total) -> bool - { - if ((0 > delta) && (-delta > current)) - delta = -int(unsigned(current)); - else if ((0 < delta) && ((current + unsigned(delta)) >= total)) - delta = int(unsigned(total - current - 1)); - if (delta) - { - current += delta; - m_topline_datsview = 0; - return true; - } - else - { - return false; - } - }; - ui_system_info const *sys; - ui_software_info const *soft; - get_selection(soft, sys); - if (!m_populated_favorites) - { - if (uintptr_t(sys) > m_skip_main_items) - return cap_delta(ui_globals::curdats_view, ui_globals::curdats_total); - else - return false; - } - else if (uintptr_t(soft) > m_skip_main_items) - { - if (soft->startempty) - return cap_delta(ui_globals::curdats_view, ui_globals::curdats_total); - else - return cap_delta(ui_globals::cur_sw_dats_view, ui_globals::cur_sw_dats_total); - } - else - { - return false; - } -} - -//------------------------------------------------- // populate search list //------------------------------------------------- @@ -1083,10 +962,10 @@ void menu_select_game::load_custom_filters() // draw left box //------------------------------------------------- -float menu_select_game::draw_left_panel(float x1, float y1, float x2, float y2) +void menu_select_game::draw_left_panel(u32 flags) { machine_filter_data &filter_data(m_persistent_data.filter_data()); - return menu_select_launch::draw_left_panel<machine_filter>(filter_data.get_current_filter_type(), filter_data.get_filters(), x1, y1, x2, y2); + menu_select_launch::draw_left_panel<machine_filter>(flags, filter_data.get_current_filter_type(), filter_data.get_filters()); } @@ -1108,6 +987,32 @@ void menu_select_game::get_selection(ui_software_info const *&software, ui_syste } } +void menu_select_game::show_config_menu(int index) +{ + if (!m_populated_favorites) + { + menu::stack_push<menu_machine_configure>( + ui(), + container(), + *reinterpret_cast<ui_system_info const *>(item(index).ref()), + nullptr); + } + else + { + ui_software_info *sw = reinterpret_cast<ui_software_info *>(item(index).ref()); + ui_system_info const &sys = m_persistent_data.systems()[driver_list::find(sw->driver->name)]; + menu::stack_push<menu_machine_configure>( + ui(), + container(), + sys, + [this, empty = sw->startempty] (bool fav, bool changed) + { + if (changed) + reset(empty ? reset_options::SELECT_FIRST : reset_options::REMEMBER_REF); + }); + } +} + void menu_select_game::make_topbox_text(std::string &line0, std::string &line1, std::string &line2) const { line0 = string_format(_("%1$s %2$s ( %3$d / %4$d systems (%5$d BIOS) )"), @@ -1142,30 +1047,29 @@ std::string menu_select_game::make_software_description(ui_software_info const & } -void menu_select_game::filter_selected() +void menu_select_game::filter_selected(int index) { - if ((machine_filter::FIRST <= m_filter_highlight) && (machine_filter::LAST >= m_filter_highlight)) - { - m_persistent_data.filter_data().get_filter(machine_filter::type(m_filter_highlight)).show_ui( - ui(), - container(), - [this] (machine_filter &filter) + assert((machine_filter::FIRST <= index) && (machine_filter::LAST >= index)); + + m_persistent_data.filter_data().get_filter(machine_filter::type(index)).show_ui( + ui(), + container(), + [this] (machine_filter &filter) + { + set_switch_image(); + machine_filter::type const new_type(filter.get_type()); + if (machine_filter::CUSTOM == new_type) { - set_switch_image(); - machine_filter::type const new_type(filter.get_type()); - if (machine_filter::CUSTOM == new_type) + emu_file file(ui().options().ui_path(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); + if (!file.open(util::string_format("custom_%s_filter.ini", emulator_info::get_configname()))) { - emu_file file(ui().options().ui_path(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); - if (!file.open(util::string_format("custom_%s_filter.ini", emulator_info::get_configname()))) - { - filter.save_ini(file, 0); - file.close(); - } + filter.save_ini(file, 0); + file.close(); } - m_persistent_data.filter_data().set_current_filter_type(new_type); - reset(reset_options::REMEMBER_REF); - }); - } + } + m_persistent_data.filter_data().set_current_filter_type(new_type); + reset(reset_options::REMEMBER_REF); + }); } } // namespace ui diff --git a/src/frontend/mame/ui/selgame.h b/src/frontend/mame/ui/selgame.h index 4aae7261687..8a9037a8864 100644 --- a/src/frontend/mame/ui/selgame.h +++ b/src/frontend/mame/ui/selgame.h @@ -62,11 +62,12 @@ private: virtual bool handle(event const *ev) override; // drawing - virtual float draw_left_panel(float x1, float y1, float x2, float y2) override; + virtual void draw_left_panel(u32 flags) override; virtual render_texture *get_icon_texture(int linenum, void *selectedref) override; // get selected software and/or driver virtual void get_selection(ui_software_info const *&software, ui_system_info const *&system) const override; + virtual void show_config_menu(int index) override; virtual bool accept_search() const override { return !isfavorite(); } // text for main top/bottom panels @@ -74,14 +75,11 @@ private: virtual std::string make_software_description(ui_software_info const &software, ui_system_info const *system) const override; // filter navigation - virtual void filter_selected() override; + virtual void filter_selected(int index) override; // toolbar virtual void inkey_export() override; - // internal methods - bool change_info_pane(int delta); - void build_available_list(); bool isfavorite() const; diff --git a/src/frontend/mame/ui/selmenu.cpp b/src/frontend/mame/ui/selmenu.cpp index 23176a0168c..7791aaff8d1 100644 --- a/src/frontend/mame/ui/selmenu.cpp +++ b/src/frontend/mame/ui/selmenu.cpp @@ -39,8 +39,6 @@ #include <algorithm> #include <cmath> #include <cstring> -#include <tuple> -#include <utility> // these hold static bitmap images @@ -107,6 +105,18 @@ char const *const hover_msg[] = { N_("Show DATs view"), }; +std::tuple<unsigned, int, bool> SYS_TOOLBAR_BITMAPS[] = { + { TOOLBAR_BITMAP_FAVORITE, IPT_UI_FAVORITES, true }, + { TOOLBAR_BITMAP_SAVE, IPT_UI_EXPORT, false }, + { TOOLBAR_BITMAP_AUDIT, IPT_UI_AUDIT, false }, + { TOOLBAR_BITMAP_INFO, IPT_UI_DATS, true } +}; + +std::tuple<unsigned, int, bool> SW_TOOLBAR_BITMAPS[] = { + { TOOLBAR_BITMAP_FAVORITE, IPT_UI_FAVORITES, true }, + { TOOLBAR_BITMAP_INFO, IPT_UI_DATS, true } +}; + void load_image(bitmap_argb32 &bitmap, emu_file &file, std::string const &base) { @@ -206,8 +216,8 @@ bool menu_select_launch::reselect_last::s_reselect = false; // instantiate possible variants of these so derived classes don't get link errors template bool menu_select_launch::select_bios(game_driver const &, bool); template bool menu_select_launch::select_bios(ui_software_info const &, bool); -template float menu_select_launch::draw_left_panel<machine_filter>(machine_filter::type current, std::map<machine_filter::type, machine_filter::ptr> const &filters, float x1, float y1, float x2, float y2); -template float menu_select_launch::draw_left_panel<software_filter>(software_filter::type current, std::map<software_filter::type, software_filter::ptr> const &filters, float x1, float y1, float x2, float y2); +template void menu_select_launch::draw_left_panel<machine_filter>(u32 flags, machine_filter::type current, std::map<machine_filter::type, machine_filter::ptr> const &filters); +template void menu_select_launch::draw_left_panel<software_filter>(u32 flags, software_filter::type current, std::map<software_filter::type, software_filter::ptr> const &filters); menu_select_launch::system_flags::system_flags(machine_static_info const &info) @@ -253,10 +263,6 @@ void menu_select_launch::reselect_last::set_software(game_driver const &driver, } -inline bool menu_select_launch::show_left_panel() const { return !(m_panels_status & HIDE_LEFT_PANEL); } -inline bool menu_select_launch::show_right_panel() const { return !(m_panels_status & HIDE_RIGHT_PANEL); } - - //------------------------------------------------- // ctor //------------------------------------------------- @@ -424,8 +430,8 @@ menu_select_launch::cache::cache(running_machine &machine) std::memcpy(&m_no_avail_bitmap.pix(0), no_avail_bmp, 256 * 256 * sizeof(u32)); - m_toolbar_bitmaps.resize(UI_TOOLBAR_BUTTONS); - m_toolbar_textures.reserve(UI_TOOLBAR_BUTTONS); + m_toolbar_bitmaps.resize(UI_TOOLBAR_BUTTONS * 2); + m_toolbar_textures.reserve(UI_TOOLBAR_BUTTONS * 2); } @@ -455,7 +461,7 @@ void menu_select_launch::cache::cache_toolbar(running_machine &machine, float wi float const drawscale((std::max)(xscale, yscale)); // rasterise the SVG and clear it out of memory - bitmap_argb32 &bitmap(m_toolbar_bitmaps[i]); + bitmap_argb32 &bitmap(m_toolbar_bitmaps[2 * i]); bitmap.resize(pix_size, pix_size); nsvgRasterize( rasterizer.get(), @@ -467,20 +473,28 @@ void menu_select_launch::cache::cache_toolbar(running_machine &machine, float wi svg.reset(); // correct colour format + bitmap_argb32 &disabled_bitmap(m_toolbar_bitmaps[(2 * i) + 1]); + disabled_bitmap.resize(pix_size, pix_size); for (s32 y = 0; bitmap.height() > y; ++y) { - u32 *dst(&bitmap.pix(y)); - for (s32 x = 0; bitmap.width() > x; ++x, ++dst) + u32 *cdst(&bitmap.pix(y)); + u32 *mdst(&disabled_bitmap.pix(y)); + for (s32 x = 0; bitmap.width() > x; ++x, ++cdst, ++mdst) { - u8 const *const src(reinterpret_cast<u8 const *>(dst)); - rgb_t const d(src[3], src[0], src[1], src[2]); - *dst = d; + u8 const *const src(reinterpret_cast<u8 const *>(cdst)); + rgb_t const c(src[3], src[0], src[1], src[2]); + u8 const l(std::clamp(std::lround((0.2126 * src[0]) + (0.7152 * src[1]) + (0.0722 * src[2])), 0L, 255L)); + rgb_t const m(src[3], l, l, l); + *cdst = c; + *mdst = m; } } - // make a texture + // make textures render_texture &texture(*m_toolbar_textures.emplace_back(render.texture_alloc(), render)); + render_texture &disabled_texture(*m_toolbar_textures.emplace_back(render.texture_alloc(), render)); texture.set_bitmap(bitmap, bitmap.cliprect(), TEXFORMAT_ARGB32); + disabled_texture.set_bitmap(disabled_bitmap, disabled_bitmap.cliprect(), TEXFORMAT_ARGB32); } } } @@ -514,9 +528,32 @@ menu_select_launch::menu_select_launch(mame_ui_manager &mui, render_container &c , m_cache(mui.get_session_data<menu_select_launch, cache_wrapper>(machine())) , m_is_swlist(is_swlist) , m_focus(focused_menu::MAIN) - , m_pressed(false) - , m_repeat(0) + , m_pointer_action(pointer_action::NONE) + , m_scroll_repeat(std::chrono::steady_clock::time_point::min()) + , m_base_pointer(0.0F, 0.0F) + , m_last_pointer(0.0F, 0.0F) + , m_clicked_line(0) + , m_primary_vbounds(0.0F, -1.0F) + , m_primary_items_top(-1.0F) + , m_primary_items_hbounds(0.0F, -1.0F) + , m_primary_lines(0) + , m_left_panel_width(-1.0F) + , m_left_items_hbounds(0.0F, -1.0F) + , m_left_items_top(1.0F) + , m_left_item_count(0) + , m_left_visible_lines(0) + , m_left_visible_top(0) + , m_right_panel_width(-1.0F) + , m_right_tabs_bottom(-1.0F) + , m_right_heading_top(-1.0F) + , m_right_content_vbounds(0.0F, -1.0F) + , m_right_content_hbounds(0.0F, -1.0F) , m_right_visible_lines(0) + , m_toolbar_button_vbounds(0.0F, -1.0F) + , m_toolbar_button_width(-1.0) + , m_toolbar_button_spacing(-1.0) + , m_toolbar_backtrack_left(-1.0) + , m_toolbar_main_left(-1.0) , m_panels_status(SHOW_PANELS) , m_right_panel(RP_FIRST) , m_has_icons(false) @@ -529,33 +566,109 @@ menu_select_launch::menu_select_launch(mame_ui_manager &mui, render_container &c } -bool menu_select_launch::next_image_view() +std::pair<bool, bool> menu_select_launch::next_right_panel_view() +{ + if (right_panel() == RP_IMAGES) + return next_image_view(); + else if (right_panel() == RP_INFOS) + return next_info_view(); + else + return std::make_pair(false, false); +} + + +std::pair<bool, bool> menu_select_launch::previous_right_panel_view() +{ + if (right_panel() == RP_IMAGES) + return previous_image_view(); + else if (right_panel() == RP_INFOS) + return previous_info_view(); + else + return std::make_pair(false, false); +} + + +std::pair<bool, bool> menu_select_launch::next_image_view() { if (LAST_VIEW > m_image_view) { ++m_image_view; set_switch_image(); - return true; + return std::make_pair(true, (LAST_VIEW > m_image_view)); } else { - return false; + return std::make_pair(false, false); } } -bool menu_select_launch::previous_image_view() +std::pair<bool, bool> menu_select_launch::previous_image_view() { if (FIRST_VIEW < m_image_view) { --m_image_view; set_switch_image(); - return true; + return std::make_pair(true, (FIRST_VIEW < m_image_view)); } else { - return false; + return std::make_pair(false, false); + } +} + + +std::pair<bool, bool> menu_select_launch::next_info_view() +{ + ui_software_info const *software; + ui_system_info const *system; + get_selection(software, system); + if (software && !software->startempty) + { + if ((ui_globals::cur_sw_dats_total - 1) > ui_globals::cur_sw_dats_view) + { + ++ui_globals::cur_sw_dats_view; + m_topline_datsview = 0; + return std::make_pair(true, (ui_globals::cur_sw_dats_total - 1) > ui_globals::cur_sw_dats_view); + } + } + else if (system || (software && software->driver)) + { + if ((ui_globals::curdats_total - 1) > ui_globals::curdats_view) + { + ++ui_globals::curdats_view; + m_topline_datsview = 0; + return std::make_pair(true, (ui_globals::curdats_total - 1) > ui_globals::curdats_view); + } } + return std::make_pair(false, false); +} + + +std::pair<bool, bool> menu_select_launch::previous_info_view() +{ + ui_software_info const *software; + ui_system_info const *system; + get_selection(software, system); + if (software && !software->startempty) + { + if (0 < ui_globals::cur_sw_dats_view) + { + --ui_globals::cur_sw_dats_view; + m_topline_datsview = 0; + return std::make_pair(true, 0 < ui_globals::cur_sw_dats_view); + } + } + else if (system || (software && software->driver)) + { + if (0 < ui_globals::curdats_view) + { + --ui_globals::curdats_view; + m_topline_datsview = 0; + return std::make_pair(true, 0 < ui_globals::curdats_view); + } + } + return std::make_pair(false, false); } @@ -651,13 +764,27 @@ void menu_select_launch::recompute_metrics(uint32_t width, uint32_t height, floa m_info_layout = std::nullopt; // calculate size of dividers between panes - m_divider_width = 2.0F * lr_border(); - m_divider_arrow_width = 0.4F * m_divider_width; - m_divider_arrow_height = 2.0F * tb_border(); + m_divider_width = 0.8F * line_height() * x_aspect(); + m_divider_arrow_width = 0.32F * line_height() * x_aspect(); + m_divider_arrow_height = 0.64F * line_height(); // calculate info text size m_info_line_height = ui().get_line_height(ui().options().infos_size()); + // invalidate panel metrics + m_primary_vbounds = std::make_pair(0.0F, -1.0F); + m_primary_items_hbounds = std::make_pair(0.0F, -1.0F); + m_left_panel_width = -1.0F; + m_left_items_hbounds = std::make_pair(0.0F, -1.0F); + m_right_panel_width = -1.0F; + m_right_heading_top = -1.0F; + m_right_content_vbounds = std::make_pair(0.0F, -1.0F); + m_right_content_hbounds = std::make_pair(0.0F, -1.0F); + m_toolbar_button_vbounds = std::make_pair(0.0F, -1.0F); + + // abandon pointer input + m_pointer_action = pointer_action::NONE; + // force right panel images to be redrawn m_cache.set_snapx_driver(nullptr); m_cache.set_snapx_software(nullptr); @@ -668,7 +795,7 @@ void menu_select_launch::recompute_metrics(uint32_t width, uint32_t height, floa // perform our special rendering //------------------------------------------------- -void menu_select_launch::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) +void menu_select_launch::custom_render(u32 flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) { std::string tempbuf[4]; @@ -682,7 +809,7 @@ void menu_select_launch::custom_render(void *selectedref, float top, float botto ui().colors().text_color(), ui().colors().background_color()); // draw toolbar - draw_toolbar(origx1, y1, origx2, origy1 - tb_border()); + draw_toolbar(flags, origx1, y1, origx2, origy1 - tb_border()); // determine the text to render below ui_software_info const *swinfo; @@ -772,14 +899,27 @@ void menu_select_launch::custom_render(void *selectedref, float top, float botto } else { - std::string copyright(emulator_info::get_copyright()); - size_t found1 = copyright.find_first_of('\n'); - size_t found2 = copyright.find_last_of('\n'); + std::string_view copyright(emulator_info::get_copyright()); + unsigned line(0); + + // first line is version string + tempbuf[line++] = string_format("%s %s", emulator_info::get_appname(), build_version); - tempbuf[0] = string_format(_("%1$s %2$s"), emulator_info::get_appname(), build_version); - tempbuf[1] = copyright.substr(0, found1); - tempbuf[2] = copyright.substr(found1 + 1, found2 - (found1 + 1)); - tempbuf[3] = copyright.substr(found2 + 1); + // output message + while (line < std::size(tempbuf)) + { + auto const found = copyright.find('\n'); + if (std::string::npos != found) + { + tempbuf[line++] = copyright.substr(0, found); + copyright.remove_prefix(found + 1); + } + else + { + tempbuf[line++] = copyright; + copyright = std::string_view(); + } + } } // draw the footer @@ -812,7 +952,7 @@ void menu_select_launch::rotate_focus(int dir) switch (get_focus()) { case focused_menu::MAIN: - if (selected_index() > m_available_items) + if (selected_index() >= m_available_items) { if ((0 > dir) || (m_panels_status == HIDE_BOTH)) select_prev(); @@ -915,204 +1055,211 @@ void menu_select_launch::inkey_dats() //------------------------------------------------- -// draw common arrows +// draw info arrow //------------------------------------------------- -void menu_select_launch::draw_common_arrow(float origx1, float origy1, float origx2, float origy2, int current, int dmin, int dmax, float title_size) +void menu_select_launch::draw_info_arrow(u32 flags, int line) { - // set left-right arrows dimension - float const ar_x0 = 0.5f * (origx2 + origx1) + 0.5f * title_size + gutter_width() - lr_arrow_width(); - float const ar_y0 = origy1 + 0.1f * line_height(); - float const ar_x1 = 0.5f * (origx2 + origx1) + 0.5f * title_size + gutter_width(); - float const ar_y1 = origy1 + 0.9f * line_height(); - - float const al_x0 = 0.5f * (origx2 + origx1) - 0.5f * title_size - gutter_width(); - float const al_y0 = origy1 + 0.1f * line_height(); - float const al_x1 = 0.5f * (origx2 + origx1) - 0.5f * title_size - gutter_width() + lr_arrow_width(); - float const al_y1 = origy1 + 0.9f * line_height(); + float const linetop(m_right_content_vbounds.first + (float(line) * m_info_line_height)); + float const linebottom(m_right_content_vbounds.first + (float(line + 1) * m_info_line_height)); + bool const hovered(pointer_in_rect(m_right_content_hbounds.first, linetop, m_right_content_hbounds.second, linebottom)); + bool const clicked((pointer_action::RIGHT_TRACK_LINE == m_pointer_action) && (line == m_clicked_line)); - rgb_t fgcolor_right, fgcolor_left; - fgcolor_right = fgcolor_left = ui().colors().text_color(); - - // set hover - if (mouse_in_rect(ar_x0, ar_y0, ar_x1, ar_y1) && current != dmax) + rgb_t bgcolor = ui().colors().text_bg_color(); + rgb_t fgcolor = ui().colors().text_color(); + if (clicked && hovered) { - ui().draw_textured_box(container(), ar_x0 + 0.01f, ar_y0, ar_x1 - 0.01f, ar_y1, ui().colors().mouseover_bg_color(), rgb_t(43, 43, 43), - hilight_main_texture(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXWRAP(1)); - set_hover(HOVER_UI_RIGHT); - fgcolor_right = ui().colors().mouseover_color(); + // draw selected highlight for tracked item + bgcolor = ui().colors().selected_bg_color(); + fgcolor = ui().colors().selected_color(); + highlight(m_right_content_hbounds.first, linetop, m_right_content_hbounds.second, linebottom, bgcolor); } - else if (mouse_in_rect(al_x0, al_y0, al_x1, al_y1) && current != dmin) + else if (clicked || (!m_ui_error && !(flags & PROCESS_NOINPUT) && hovered && pointer_idle())) { - ui().draw_textured_box(container(), al_x0 + 0.01f, al_y0, al_x1 - 0.01f, al_y1, ui().colors().mouseover_bg_color(), rgb_t(43, 43, 43), - hilight_main_texture(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXWRAP(1)); - set_hover(HOVER_UI_LEFT); - fgcolor_left = ui().colors().mouseover_color(); + // draw hover highlight when hovered over or dragged off + bgcolor = ui().colors().mouseover_bg_color(); + fgcolor = ui().colors().mouseover_color(); + highlight(m_right_content_hbounds.first, linetop, m_right_content_hbounds.second, linebottom, bgcolor); } - // apply arrow - if (dmax == dmin) - return; - else if (current == dmin) - draw_arrow(ar_x0, ar_y0, ar_x1, ar_y1, fgcolor_right, ROT90); - else if (current == dmax) - draw_arrow(al_x0, al_y0, al_x1, al_y1, fgcolor_left, ROT90 ^ ORIENTATION_FLIP_X); - else - { - draw_arrow(ar_x0, ar_y0, ar_x1, ar_y1, fgcolor_right, ROT90); - draw_arrow(al_x0, al_y0, al_x1, al_y1, fgcolor_left, ROT90 ^ ORIENTATION_FLIP_X); - } + draw_arrow( + 0.5F * (m_right_content_hbounds.first + m_right_content_hbounds.second - (x_aspect() * m_info_line_height)), + linetop + (0.25F * m_info_line_height), + 0.5F * (m_right_content_hbounds.first + m_right_content_hbounds.second + (x_aspect() * m_info_line_height)), + linetop + (0.75F * m_info_line_height), + fgcolor, + line ? (ROT0 ^ ORIENTATION_FLIP_Y) : ROT0); } //------------------------------------------------- -// draw info arrow +// draw vertical divider //------------------------------------------------- -void menu_select_launch::draw_info_arrow(int ub, float origx1, float origx2, float oy1, float line_height, float ud_arrow_width) +void menu_select_launch::draw_divider(u32 flags, float x1, bool right) { + // work out colours rgb_t fgcolor = ui().colors().text_color(); - - if (mouse_in_rect(origx1, oy1, origx2, oy1 + line_height)) + rgb_t bgcolor(0xef, 0x12, 0x47, 0x7b); // FIXME: magic numbers in colour? + bool const hovered(pointer_in_rect(x1, m_primary_vbounds.first, x1 + m_divider_width, m_primary_vbounds.second)); + bool const clicked((pointer_action::DIVIDER_TRACK == m_pointer_action) && ((right ? 1 : 0) == m_clicked_line)); + if (hovered && clicked) + { + fgcolor = ui().colors().selected_color(); + bgcolor = ui().colors().selected_bg_color(); + } + else if (clicked || (!m_ui_error && !(flags & PROCESS_NOINPUT) && hovered)) { - ui().draw_textured_box( - container(), - origx1 + 0.01f, oy1, origx2 - 0.01f, oy1 + line_height, - ui().colors().mouseover_bg_color(), rgb_t(43, 43, 43), - hilight_main_texture(), - PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXWRAP(1)); - set_hover(!ub ? HOVER_DAT_UP : HOVER_DAT_DOWN); fgcolor = ui().colors().mouseover_color(); } - u32 const orientation = !ub ? ROT0 : (ROT0 ^ ORIENTATION_FLIP_Y); - draw_arrow( - 0.5f * (origx1 + origx2) - 0.5f * ud_arrow_width, oy1 + 0.25f * line_height, - 0.5f * (origx1 + origx2) + 0.5f * ud_arrow_width, oy1 + 0.75f * line_height, - fgcolor, - orientation); + // draw the divider pane + ui().draw_outlined_box(container(), x1, m_primary_vbounds.first, x1 + m_divider_width, m_primary_vbounds.second, bgcolor); + + // draw the arrow + uint32_t orientation(ROT90); + if (right ? !show_right_panel() : show_left_panel()) + orientation ^= ORIENTATION_FLIP_X; + float const ar_x0 = x1 + (0.5F * (m_divider_width - m_divider_arrow_width)); + float const ar_y0 = 0.5F * (m_primary_vbounds.first + m_primary_vbounds.second - m_divider_arrow_height); + draw_arrow(ar_x0, ar_y0, ar_x0 + m_divider_arrow_width, ar_y0 + m_divider_arrow_height, fgcolor, orientation); } -bool menu_select_launch::draw_error_text() -{ - if (m_ui_error) - ui().draw_text_box(container(), m_error_text, text_layout::text_justify::CENTER, 0.5f, 0.5f, UI_RED_COLOR); - return m_ui_error; -} +//------------------------------------------------- +// draw left panel (filter list) +//------------------------------------------------- template <typename Filter> -float menu_select_launch::draw_left_panel( - typename Filter::type current, - std::map<typename Filter::type, typename Filter::ptr> const &filters, - float x1, float y1, float x2, float y2) +void menu_select_launch::draw_left_panel(u32 flags, typename Filter::type current, std::map<typename Filter::type, typename Filter::ptr> const &filters) { if (!show_left_panel()) - return draw_collapsed_left_panel(x1, y1, x2, y2); - - // calculate line height - float const sc(y2 - y1 - (2.0F * tb_border())); - float line_height_max(m_info_line_height); - if ((Filter::COUNT * line_height_max) > sc) { - float const lm(sc / Filter::COUNT); - line_height_max = line_height() * (lm / line_height()); + // left panel hidden, but no need to recompute metrics if target isn't resized + m_left_panel_width = 0.0F; + + draw_divider(flags, lr_border(), false); + return; } - // calculate horizontal offset for unadorned names - std::string tmp(convert_command_glyph("_# ")); - float const text_sign = ui().get_string_width(tmp, m_info_line_height); + // get the width of the selection indicator glyphs + float const checkmark_width = ui().get_string_width(convert_command_glyph("_# "), m_info_line_height); + + if (m_left_items_hbounds.first >= m_left_items_hbounds.second) + { + // calculate number of lines that will fit - centre vertically if we need scroll arrows + float const height(m_primary_vbounds.second - m_primary_vbounds.first); + int const lines((height - (tb_border() * 2.0F)) / m_info_line_height); + if (Filter::COUNT <= lines) + m_left_items_top = m_primary_vbounds.first + tb_border(); + else + m_left_items_top = m_primary_vbounds.first + ((height - (float(lines) * m_info_line_height)) * 0.5F); + float const pixelheight(target_size().second); + m_left_items_top = std::round(m_left_items_top * pixelheight) / pixelheight; + m_left_item_count = Filter::COUNT; + m_left_visible_lines = std::min<int>(Filter::COUNT, lines); + + // get the maximum filter name width, restricted to a quarter of the target width + float line_width(0.0F); + for (typename Filter::type x = Filter::FIRST; Filter::COUNT > x; ++x) + line_width = std::max(ui().get_string_width(Filter::display_name(x), m_info_line_height) + checkmark_width, line_width); + line_width = std::min(line_width + (lr_border() * 2.0F), 0.25F); + m_left_items_hbounds = std::make_pair(2.0F * lr_border(), (2.0F * lr_border()) + line_width); + + // make sure the scroll position makes sense + m_left_visible_top = (std::min)(m_left_visible_top, m_left_item_count - m_left_visible_lines); + } + m_left_panel_width = (m_left_items_hbounds.second - m_left_items_hbounds.first) + (lr_border() * 2.0F); - // get the maximum width of a filter name - float left_width(0.0F); - for (typename Filter::type x = Filter::FIRST; Filter::COUNT > x; ++x) - left_width = std::max(ui().get_string_width(Filter::display_name(x), m_info_line_height) + text_sign, left_width); - left_width = std::min(left_width, 0.25F); + // ensure the highlighted item is visible + if ((m_filter_highlight - Filter::FIRST) < (m_left_visible_top + 1)) + m_left_visible_top = (Filter::FIRST == m_filter_highlight) ? 0 : (m_filter_highlight - 1); + else if ((m_filter_highlight - Filter::FIRST) > (m_left_visible_top + m_left_visible_lines - 2)) + m_left_visible_top = (std::min)(m_filter_highlight - Filter::FIRST + 2 - m_left_visible_lines, m_left_item_count - m_left_visible_lines); // outline the box and inset by the border width - float const origy1(y1); - float const origy2(y2); - x2 = x1 + left_width + 2.0F * lr_border(); - ui().draw_outlined_box(container(), x1, y1, x2, y2, ui().colors().background_color()); - x1 += lr_border(); - x2 -= lr_border(); - y1 += tb_border(); - y2 -= tb_border(); + ui().draw_outlined_box( + container(), + lr_border(), m_primary_vbounds.first, lr_border() + m_left_panel_width, m_primary_vbounds.second, + ui().colors().background_color()); // now draw the rows + typename Filter::type filter(Filter::FIRST); + for (int i = 0; i < m_left_visible_top; ++i) + ++filter; auto const active_filter(filters.find(current)); - for (typename Filter::type filter = Filter::FIRST; Filter::COUNT > filter; ++filter) + std::string str; + bool const atbottom((m_left_visible_top + m_left_visible_lines) == m_left_item_count); + for (int line = 0; line < m_left_visible_lines; ++line, ++filter) { - std::string str; - if (filters.end() != active_filter) - { - str = active_filter->second->adorned_display_name(filter); - } - else - { - if (current == filter) - str = convert_command_glyph("_> "); - str.append(Filter::display_name(filter)); - } + float const line_top(m_left_items_top + (float(line) * m_info_line_height)); + bool const uparrow(!line && m_left_visible_top); + bool const downarrow(!atbottom && ((m_left_visible_lines - 1) == line)); - // handle mouse hover in passing + // work out the colours for this item rgb_t bgcolor = ui().colors().text_bg_color(); rgb_t fgcolor = ui().colors().text_color(); - if (mouse_in_rect(x1, y1, x2, y1 + line_height_max)) + bool const hovered(pointer_in_rect(m_left_items_hbounds.first, line_top, m_left_items_hbounds.second, line_top + m_info_line_height)); + bool const pointerline((pointer_action::LEFT_TRACK_LINE == m_pointer_action) && (line == m_clicked_line)); + if (pointerline && hovered) { - bgcolor = ui().colors().mouseover_bg_color(); - fgcolor = ui().colors().mouseover_color(); - set_hover(HOVER_FILTER_FIRST + filter); - highlight(x1, y1, x2, y1 + line_height_max, bgcolor); + // draw selected highlight for tracked item + bgcolor = ui().colors().selected_bg_color(); + fgcolor = ui().colors().selected_color(); + highlight(m_left_items_hbounds.first, line_top, m_left_items_hbounds.second, line_top + m_info_line_height, bgcolor); } - - // draw primary highlight if keyboard focus is here - if ((m_filter_highlight == filter) && (get_focus() == focused_menu::LEFT)) + else if ((m_filter_highlight == filter) && (get_focus() == focused_menu::LEFT)) { + // draw primary highlight if keyboard focus is here fgcolor = rgb_t(0xff, 0xff, 0xff, 0x00); bgcolor = rgb_t(0xff, 0xff, 0xff, 0xff); ui().draw_textured_box( container(), - x1, y1, x2, y1 + line_height_max, + m_left_items_hbounds.first, line_top, m_left_items_hbounds.second, line_top + m_info_line_height, bgcolor, rgb_t(255, 43, 43, 43), hilight_main_texture(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXWRAP(1)); } + else if (pointerline || (!m_ui_error && !(flags & PROCESS_NOINPUT) && hovered && pointer_idle())) + { + // draw hover highlight when hovered over or dragged off + bgcolor = ui().colors().mouseover_bg_color(); + fgcolor = ui().colors().mouseover_color(); + highlight(m_left_items_hbounds.first, line_top, m_left_items_hbounds.second, line_top + m_info_line_height, bgcolor); + } - // finally draw the text itself and move to the next line - float const x1t = x1 + ((str == Filter::display_name(filter)) ? text_sign : 0.0F); - ui().draw_text_full( - container(), str, - x1t, y1, x2 - x1t, - text_layout::text_justify::LEFT, text_layout::word_wrapping::TRUNCATE, - mame_ui_manager::NORMAL, fgcolor, bgcolor, - nullptr, nullptr, - m_info_line_height); - y1 += line_height_max; - } - - x1 = x2 + lr_border(); - x2 = x1 + m_divider_width; - y1 = origy1; - y2 = origy2; - - // set left-right arrows dimension - float const ar_x0 = 0.5F * (x2 + x1 - m_divider_arrow_width); - float const ar_y0 = 0.5F * (y2 + y1 - m_divider_arrow_height); - float const ar_x1 = ar_x0 + m_divider_arrow_width; - float const ar_y1 = ar_y0 + m_divider_arrow_height; - - ui().draw_outlined_box(container(), x1, y1, x2, y2, rgb_t(0xef, 0x12, 0x47, 0x7b)); - - rgb_t fgcolor = ui().colors().text_color(); - if (mouse_in_rect(x1, y1, x2, y2)) - { - fgcolor = ui().colors().mouseover_color(); - set_hover(HOVER_LPANEL_ARROW); + // finally draw the text itself + if (uparrow || downarrow) + { + draw_arrow( + 0.5F * (m_left_items_hbounds.first + m_left_items_hbounds.second + (x_aspect() * m_info_line_height)), + line_top + (0.25F * m_info_line_height), + 0.5F * (m_left_items_hbounds.first + m_left_items_hbounds.second - (x_aspect() * m_info_line_height)), + line_top + (0.75F * m_info_line_height), + fgcolor, + downarrow ? (ROT0 ^ ORIENTATION_FLIP_Y) : ROT0); + } + else + { + if (filters.end() != active_filter) + str = active_filter->second->adorned_display_name(filter); + else if (current == filter) + (str = convert_command_glyph("_> ")).append(Filter::display_name(filter)); + else + str = Filter::display_name(filter); + float const x1t = m_left_items_hbounds.first + lr_border() + ((str == Filter::display_name(filter)) ? checkmark_width : 0.0F); + ui().draw_text_full( + container(), str, + x1t, line_top, m_left_items_hbounds.second - x1t - lr_border() + (1.0F / float(target_size().second)), + text_layout::text_justify::LEFT, text_layout::word_wrapping::TRUNCATE, + mame_ui_manager::NORMAL, fgcolor, bgcolor, + nullptr, nullptr, + m_info_line_height); + } } - draw_arrow(ar_x0, ar_y0, ar_x1, ar_y1, fgcolor, ROT90 ^ ORIENTATION_FLIP_X); - return x2 + lr_border(); + // draw the divider + draw_divider(flags, lr_border() + m_left_panel_width, false); } @@ -1282,77 +1429,86 @@ bool menu_select_launch::select_part(mame_ui_manager &mui, render_container &con // draw toolbar //------------------------------------------------- -void menu_select_launch::draw_toolbar(float x1, float y1, float x2, float y2) +void menu_select_launch::draw_toolbar(u32 flags, float x1, float y1, float x2, float y2) { + // work out which buttons we're going to draw + bool const have_parent(m_is_swlist || !stack_has_special_main_menu()); + auto const *const toolbar_bitmaps(m_is_swlist ? SW_TOOLBAR_BITMAPS : SYS_TOOLBAR_BITMAPS); + unsigned const toolbar_count(m_is_swlist ? std::size(SW_TOOLBAR_BITMAPS) : std::size(SYS_TOOLBAR_BITMAPS)); + // draw a box ui().draw_outlined_box(container(), x1, y1, x2, y2, rgb_t(0xef, 0x12, 0x47, 0x7b)); - // take off the borders - float const aspect(machine().render().ui_aspect(&container())); - x1 += lr_border(); - x2 -= lr_border(); - y1 += tb_border(); - y2 -= tb_border(); + // cache metrics and bitmaps if necessary + if (m_toolbar_button_vbounds.first >= m_toolbar_button_vbounds.second) + { + m_toolbar_button_vbounds.first = y1 + tb_border(); + m_toolbar_button_vbounds.second = y2 - tb_border(); + float const button_height(m_toolbar_button_vbounds.second - m_toolbar_button_vbounds.first); + m_toolbar_button_width = button_height * float(x_aspect()); + m_toolbar_button_spacing = m_toolbar_button_width * 1.5F; + float const total_width((float(toolbar_count) + (float(toolbar_count - 1) * 0.5F)) * m_toolbar_button_width); + m_toolbar_backtrack_left = x2 - lr_border() - m_toolbar_button_width; + m_toolbar_main_left = (std::min)(m_toolbar_backtrack_left - (float(toolbar_count) * m_toolbar_button_spacing), (x1 + x2 - total_width) * 0.5F); + m_cache.cache_toolbar(machine(), m_toolbar_button_width, button_height); + } - // work out which buttons we're going to draw - constexpr unsigned SYS_TOOLBAR_BITMAPS[] = { TOOLBAR_BITMAP_FAVORITE, TOOLBAR_BITMAP_SAVE, TOOLBAR_BITMAP_AUDIT, TOOLBAR_BITMAP_INFO }; - constexpr unsigned SW_TOOLBAR_BITMAPS[] = { TOOLBAR_BITMAP_FAVORITE, TOOLBAR_BITMAP_INFO }; - bool const have_parent = m_is_swlist || !stack_has_special_main_menu(); - unsigned const *const toolbar_bitmaps = m_is_swlist ? SW_TOOLBAR_BITMAPS : SYS_TOOLBAR_BITMAPS; - unsigned const toolbar_count = m_is_swlist ? std::size(SW_TOOLBAR_BITMAPS) : std::size(SYS_TOOLBAR_BITMAPS); - - // calculate metrics - float const x_size = (y2 - y1) * aspect; - float const x_spacing = x_size * 1.5f; - float const backtrack_pos = x2 - x_size; - float const total_width = (float(toolbar_count) + (float(toolbar_count - 1) * 0.5f)) * x_size; - m_cache.cache_toolbar(machine(), x_size, y2 - y1); - - // add backtrack button - rgb_t color(0xffcccccc); - if (mouse_in_rect(backtrack_pos, y1, x2, y2)) - { - set_hover(HOVER_BACKTRACK); - color = rgb_t::white(); - float const ypos = y2 + line_height() + 2.0f * tb_border(); - ui().draw_text_box( - container(), - have_parent ? _("Return to Previous Menu") : _("Exit"), - text_layout::text_justify::RIGHT, 1.0f - lr_border(), ypos, - ui().colors().background_color()); + // tooltip needs to be above for pen/touch to avoid being obscured + float tooltip_pos; + switch (pointer_type()) + { + case ui_event::pointer::PEN: + case ui_event::pointer::TOUCH: + tooltip_pos = m_toolbar_button_vbounds.first - line_height() - (2.0F * tb_border()); + break; + default: + tooltip_pos = m_toolbar_button_vbounds.second + line_height() + tb_border(); + break; + } + + + { + // add backtrack button + bool const hovered(pointer_in_rect(m_toolbar_backtrack_left, m_toolbar_button_vbounds.first, m_toolbar_backtrack_left + m_toolbar_button_width, m_toolbar_button_vbounds.second)); + bool const tracked((pointer_action::TOOLBAR_TRACK == m_pointer_action) && (0 > m_clicked_line)); + rgb_t const color((hovered && tracked) ? rgb_t::white() : rgb_t(0xffcccccc)); + if (tracked || (hovered && !(flags & PROCESS_NOINPUT) && pointer_idle())) + { + ui().draw_text_box( + container(), + have_parent ? _("Return to Previous Menu") : _("Exit"), + text_layout::text_justify::RIGHT, 1.0F - lr_border(), tooltip_pos, + ui().colors().background_color()); + } + container().add_quad( + m_toolbar_backtrack_left, m_toolbar_button_vbounds.first, m_toolbar_backtrack_left + m_toolbar_button_width, m_toolbar_button_vbounds.second, + color, + m_cache.toolbar_textures()[2 * (have_parent ? TOOLBAR_BITMAP_PREVMENU : TOOLBAR_BITMAP_EXIT)].get(), + PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); } - container().add_quad( - backtrack_pos, y1, x2, y2, - color, - m_cache.toolbar_textures()[have_parent ? TOOLBAR_BITMAP_PREVMENU : TOOLBAR_BITMAP_EXIT].get(), - PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); // now add the other buttons - x1 = (std::min)(backtrack_pos - (float(toolbar_count) * x_spacing), x1 + ((x2 - x1 - total_width) * 0.5f)); - for (int z = 0; toolbar_count > z; ++z, x1 += x_spacing) + for (int z = 0; toolbar_count > z; ++z) { - auto const bitmap = toolbar_bitmaps[z]; - x2 = x1 + x_size; - color = rgb_t (0xffcccccc); - if (mouse_in_rect(x1, y1, x2, y2)) + auto const [bitmap, action, need_selection] = toolbar_bitmaps[z]; + float const button_left(m_toolbar_main_left + (float(z) * m_toolbar_button_spacing)); + float const button_right(button_left + m_toolbar_button_width); + bool const enabled(!need_selection || get_selection_ptr()); + bool const hovered(pointer_in_rect(button_left, m_toolbar_button_vbounds.first, button_right, m_toolbar_button_vbounds.second)); + bool const tracked((pointer_action::TOOLBAR_TRACK == m_pointer_action) && (z == m_clicked_line)); + rgb_t color((hovered && tracked && enabled) ? rgb_t::white() : rgb_t(0xffcccccc)); + if (tracked || (hovered && !(flags & PROCESS_NOINPUT) && pointer_idle())) { - bool const need_selection = (TOOLBAR_BITMAP_FAVORITE == bitmap) || (TOOLBAR_BITMAP_INFO == bitmap); - if (!need_selection || get_selection_ptr()) - { - set_hover(HOVER_B_FAV + bitmap); - color = rgb_t::white(); - } - float ypos = y2 + line_height() + 2.0f * tb_border(); ui().draw_text_box( container(), _(hover_msg[bitmap]), - text_layout::text_justify::CENTER, (x1 + x2) * 0.5f, ypos, + text_layout::text_justify::CENTER, (button_left + button_right) * 0.5F, tooltip_pos, ui().colors().background_color()); } container().add_quad( - x1, y1, x2, y2, + button_left, m_toolbar_button_vbounds.first, button_right, m_toolbar_button_vbounds.second, color, - m_cache.toolbar_textures()[bitmap].get(), + m_cache.toolbar_textures()[(2 * bitmap) + (enabled ? 0 : 1)].get(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); } } @@ -1377,13 +1533,6 @@ void menu_select_launch::draw_star(float x0, float y0) } -void menu_select_launch::set_pressed() -{ - (m_repeat == 0) ? m_repeat = osd_ticks() + osd_ticks_per_second() / 2 : m_repeat = osd_ticks() + osd_ticks_per_second() / 4; - m_pressed = true; -} - - //------------------------------------------------- // draw icons //------------------------------------------------- @@ -1404,10 +1553,9 @@ void menu_select_launch::draw_icon(int linenum, void *selectedref, float x0, flo // get title and search path for right panel //------------------------------------------------- -void menu_select_launch::get_title_search(std::string &snaptext, std::string &searchstr) +std::string menu_select_launch::get_arts_searchpath() { - // get arts title text - snaptext.assign(_("selmenu-artwork", std::get<1>(ARTS_INFO[m_image_view]))); + std::string searchstr; // get search path std::string addpath; @@ -1436,6 +1584,251 @@ void menu_select_launch::get_title_search(std::string &snaptext, std::string &se while (path_iter.next(c_path)) searchstr.append(";").append(curpath).append(PATH_SEPARATOR).append(c_path); } + + return searchstr; +} + + +//------------------------------------------------- +// handle UI input events for main menu +//------------------------------------------------- + +bool menu_select_launch::handle_events(u32 flags, event &ev) +{ + // loop while we have interesting events + bool stop(false), need_update(false), search_changed(false); + ui_event local_menu_event; + while (!stop && machine().ui_input().pop_event(&local_menu_event)) + { + switch (local_menu_event.event_type) + { + // deal with pointer-like input (mouse, pen, touch, etc.) + case ui_event::type::POINTER_UPDATE: + { + auto const [key, redraw] = handle_pointer_update(flags, local_menu_event); + need_update = need_update || redraw; + if (IPT_INVALID != key) + { + ev.iptkey = key; + stop = true; + } + } + break; + + // pointer left the normal way, possibly releasing buttons + case ui_event::type::POINTER_LEAVE: + { + auto const [key, redraw] = handle_pointer_leave(flags, local_menu_event); + need_update = need_update || redraw; + if (IPT_INVALID != key) + { + ev.iptkey = key; + stop = true; + } + } + break; + + // pointer left in some abnormal way - cancel any associated actions + case ui_event::type::POINTER_ABORT: + { + auto const [key, redraw] = handle_pointer_abort(flags, local_menu_event); + need_update = need_update || redraw; + if (IPT_INVALID != key) + { + ev.iptkey = key; + stop = true; + } + } + break; + + // caught scroll event + case ui_event::type::MOUSE_WHEEL: + if ((&machine().render().ui_target() == local_menu_event.target) && pointer_idle() && !m_ui_error) + { + float x, y; + bool const hit(local_menu_event.target->map_point_container(local_menu_event.mouse_x, local_menu_event.mouse_y, container(), x, y)); + if (!hit) + break; + + if ((x >= m_primary_items_hbounds.first) && (x < m_primary_items_hbounds.second) && (y >= m_primary_items_top) && (y < (m_primary_items_top + (float(m_primary_lines) * line_height())))) + { + if (local_menu_event.zdelta > 0) + { + if ((selected_index() >= m_available_items) || is_first_selected()) + break; + stop = true; + ev.iptkey = IPT_CUSTOM; // stop processing events so info can be rebuilt + set_selected_index(selected_index() - local_menu_event.num_lines); + if (selected_index() < top_line + (top_line != 0)) + top_line -= local_menu_event.num_lines; + } + else + { + if (selected_index() >= (m_available_items - 1)) + break; + stop = true; + ev.iptkey = IPT_CUSTOM; // stop processing events so info can be rebuilt + set_selected_index(std::min(selected_index() + local_menu_event.num_lines, m_available_items - 1)); + if (selected_index() >= top_line + m_visible_items + (top_line != 0)) + top_line += local_menu_event.num_lines; + } + } + else if ((x >= m_right_content_hbounds.first) && (x < m_right_content_hbounds.second) && (y >= m_right_content_vbounds.first) && (y < m_right_content_vbounds.second)) + { + if (show_right_panel() && (RP_INFOS == m_right_panel)) + { + if (local_menu_event.zdelta > 0) + m_topline_datsview -= local_menu_event.num_lines; + else + m_topline_datsview += local_menu_event.num_lines; + } + } + } + break; + + // text input goes to the search field unless there's an error message displayed + case ui_event::type::IME_CHAR: + if (!pointer_idle()) + break; + + if (exclusive_input_pressed(ev.iptkey, IPT_UI_FOCUS_NEXT, 0) || exclusive_input_pressed(ev.iptkey, IPT_UI_FOCUS_PREV, 0)) + { + stop = true; + } + else if (m_ui_error) + { + ev.iptkey = IPT_CUSTOM; + stop = true; + } + else if (accept_search()) + { + if (input_character(m_search, local_menu_event.ch, uchar_is_printable)) + search_changed = true; + } + break; + + // ignore everything else + default: + break; + } + + // need to update search before processing certain kinds of events, but others don't matter + if (search_changed) + { + switch (machine().ui_input().peek_event_type()) + { + case ui_event::type::MOUSE_WHEEL: + case ui_event::type::POINTER_UPDATE: + case ui_event::type::POINTER_LEAVE: + case ui_event::type::POINTER_ABORT: + stop = true; + break; + case ui_event::type::NONE: + case ui_event::type::WINDOW_FOCUS: + case ui_event::type::WINDOW_DEFOCUS: + case ui_event::type::IME_CHAR: + break; + } + } + } + + // deal with repeating main scroll arrows + if ((pointer_action::MAIN_TRACK_LINE == m_pointer_action) && (is_main_up_arrow(m_clicked_line) || (is_main_down_arrow(m_clicked_line)))) + { + if (check_scroll_repeat(m_primary_items_top, m_primary_items_hbounds, line_height())) + { + if (!m_clicked_line) + { + // scroll up + --top_line; + if (main_at_top()) + m_pointer_action = pointer_action::NONE; + } + else + { + // scroll down + ++top_line; + if (main_at_bottom()) + m_pointer_action = pointer_action::NONE; + } + if (main_force_visible_selection()) + { + if (IPT_INVALID == ev.iptkey) + ev.iptkey = IPT_CUSTOM; // stop processing events so the info pane can be rebuilt + } + need_update = true; + } + } + + // deal with repeating info view arrows + if (pointer_action::RIGHT_TRACK_ARROW == m_pointer_action) + { + float const left(m_clicked_line ? (m_right_content_hbounds.second - lr_border() - lr_arrow_width()) : (m_right_content_hbounds.first + lr_border())); + float const right(m_clicked_line ? (m_right_content_hbounds.second - lr_border()) : (m_right_content_hbounds.first + lr_border() + lr_arrow_width())); + if (pointer_in_rect(left, right_arrows_top(), right, right_arrows_bottom())) + { + if (std::chrono::steady_clock::now() >= m_scroll_repeat) + { + m_scroll_repeat += std::chrono::milliseconds(200); + if (!(m_clicked_line ? next_right_panel_view() : previous_right_panel_view()).second) + m_pointer_action = pointer_action::NONE; + need_update = true; + } + } + } + + // deal with repeating filter scroll arrows + if ((pointer_action::LEFT_TRACK_LINE == m_pointer_action) && (is_left_up_arrow(m_clicked_line) || (is_left_down_arrow(m_clicked_line)))) + { + if (check_scroll_repeat(m_left_items_top, m_left_items_hbounds, m_info_line_height)) + { + if (!m_clicked_line) + { + // scroll up + --m_left_visible_top; + m_filter_highlight = std::min(m_left_visible_top + m_left_visible_lines - 2, m_filter_highlight); + if (left_at_top()) + m_pointer_action = pointer_action::NONE; + } + else + { + // scroll down + ++m_left_visible_top; + m_filter_highlight = std::max(m_left_visible_top + 1, m_filter_highlight); + if (left_at_bottom()) + m_pointer_action = pointer_action::NONE; + } + need_update = true; + } + } + + // deal with repeating filter scroll arrows + if (pointer_action::RIGHT_TRACK_LINE == m_pointer_action) + { + if (check_scroll_repeat(m_right_content_vbounds.first, m_right_content_hbounds, m_info_line_height)) + { + if (!m_clicked_line) + { + // scroll up + --m_topline_datsview; + if (info_at_top()) + m_pointer_action = pointer_action::NONE; + } + else + { + // scroll down + ++m_topline_datsview; + if (info_at_bottom()) + m_pointer_action = pointer_action::NONE; + } + need_update = true; + } + } + + if (search_changed) + reset(reset_options::SELECT_FIRST); + + return need_update; } @@ -1443,11 +1836,11 @@ void menu_select_launch::get_title_search(std::string &snaptext, std::string &se // handle keys for main menu //------------------------------------------------- -void menu_select_launch::handle_keys(u32 flags, int &iptkey) +bool menu_select_launch::handle_keys(u32 flags, int &iptkey) { // bail if no items if (item_count() == 0) - return; + return false; // if we hit select, return true or pop the stack, depending on the item if (exclusive_input_pressed(iptkey, IPT_UI_SELECT, 0)) @@ -1458,9 +1851,9 @@ void menu_select_launch::handle_keys(u32 flags, int &iptkey) } else if (m_focus == focused_menu::LEFT) { - filter_selected(); + filter_selected(m_filter_highlight); } - return; + return false; } if (exclusive_input_pressed(iptkey, IPT_UI_BACK, 0)) @@ -1468,13 +1861,13 @@ void menu_select_launch::handle_keys(u32 flags, int &iptkey) if (m_ui_error) { // dismiss error - return; + return false; } else if (!is_special_main_menu() && m_search.empty()) { // pop the stack if this isn't the root session menu stack_pop(); - return; + return false; } } @@ -1492,11 +1885,12 @@ void menu_select_launch::handle_keys(u32 flags, int &iptkey) stack_pop(); machine().schedule_exit(); } - return; + return false; } // validate the current selection validate_selection(1); + bool updated(false); // accept left/right keys as-is with repeat if (exclusive_input_pressed(iptkey, IPT_UI_LEFT, (flags & PROCESS_LR_REPEAT) ? 6 : 0)) @@ -1504,12 +1898,28 @@ void menu_select_launch::handle_keys(u32 flags, int &iptkey) if (m_focus == focused_menu::RIGHTTOP) { // Swap the right panel and swallow it - m_right_panel = RP_IMAGES; iptkey = IPT_INVALID; + if (right_panel() != RP_IMAGES) + { + m_right_panel = RP_IMAGES; + updated = true; + } } - else + else if (show_right_panel()) { - return; + // Swap the right panel page and swallow it + if (right_panel() == RP_IMAGES) + { + iptkey = IPT_INVALID; + if (previous_image_view().first) + updated = true; + } + else if (right_panel() == RP_INFOS) + { + iptkey = IPT_INVALID; + if (previous_info_view().first) + updated = true; + } } } @@ -1519,71 +1929,120 @@ void menu_select_launch::handle_keys(u32 flags, int &iptkey) if (m_focus == focused_menu::RIGHTTOP) { // Swap the right panel and swallow it - m_right_panel = RP_INFOS; iptkey = IPT_INVALID; + if (right_panel() != RP_INFOS) + { + m_right_panel = RP_INFOS; + updated = true; + } } - else + else if (show_right_panel()) { - return; + // Swap the right panel page and swallow it + if (right_panel() == RP_IMAGES) + { + iptkey = IPT_INVALID; + if (next_image_view().first) + updated = true; + } + else if (right_panel() == RP_INFOS) + { + iptkey = IPT_INVALID; + if (next_info_view().first) + updated = true; + } } } // up backs up by one item if (exclusive_input_pressed(iptkey, IPT_UI_UP, 6)) { - if (show_left_panel() && m_focus == focused_menu::LEFT) + if (m_focus == focused_menu::LEFT) { - return; + // swallow it + iptkey = IPT_INVALID; + if (m_filter_highlight) + { + --m_filter_highlight; + updated = true; + } } - else if (show_right_panel() && ((m_focus == focused_menu::RIGHTTOP) || (m_focus == focused_menu::RIGHTBOTTOM))) + else if ((m_focus == focused_menu::RIGHTTOP) || (m_focus == focused_menu::RIGHTBOTTOM)) { - m_topline_datsview--; - return; + // swallow it + iptkey = IPT_INVALID; + if (m_topline_datsview) + { + m_topline_datsview--; + updated = true; + } } else if (selected_index() == m_available_items + 1 || is_first_selected() || m_ui_error) { - return; + return updated; + } + else + { + set_selected_index(selected_index() - 1); + if (selected_index() == top_line && top_line != 0) + top_line--; } - - set_selected_index(selected_index() - 1); - - if (selected_index() == top_line && top_line != 0) - top_line--; } // down advances by one item if (exclusive_input_pressed(iptkey, IPT_UI_DOWN, 6)) { - if (show_left_panel() && m_focus == focused_menu::LEFT) + if (m_focus == focused_menu::LEFT) { - return; + // swallow it + iptkey = IPT_INVALID; + if ((m_left_item_count - 1) > m_filter_highlight) + { + ++m_filter_highlight; + updated = true; + } } - else if (show_right_panel() && ((m_focus == focused_menu::RIGHTTOP) || (m_focus == focused_menu::RIGHTBOTTOM))) + else if ((m_focus == focused_menu::RIGHTTOP) || (m_focus == focused_menu::RIGHTBOTTOM)) { + // swallow it + iptkey = IPT_INVALID; + updated = true; m_topline_datsview++; - return; } else if (is_last_selected() || selected_index() == m_available_items - 1 || m_ui_error) { - return; + return updated; + } + else + { + set_selected_index(selected_index() + 1); + if (selected_index() == top_line + m_visible_items + (top_line != 0)) + top_line++; } - - set_selected_index(selected_index() + 1); - if (selected_index() == top_line + m_visible_items + (top_line != 0)) - top_line++; } // page up backs up by m_visible_items if (exclusive_input_pressed(iptkey, IPT_UI_PAGE_UP, 6)) { - // Infos - if (show_right_panel() && ((m_focus == focused_menu::RIGHTTOP) || (m_focus == focused_menu::RIGHTBOTTOM))) + if (m_focus == focused_menu::LEFT) + { + // Filters - swallow it + iptkey = IPT_INVALID; + if (!left_at_top()) + { + updated = true; + m_left_visible_top -= std::min(std::max(m_left_visible_lines - 3, 1), m_left_visible_top); + m_filter_highlight = std::min(m_left_visible_top + m_left_visible_lines - 2, m_filter_highlight); + } + } + else if ((m_focus == focused_menu::RIGHTTOP) || (m_focus == focused_menu::RIGHTBOTTOM)) { + // Infos - swallow it + iptkey = IPT_INVALID; + updated = true; m_topline_datsview -= m_right_visible_lines - 3; - return; } - - if (selected_index() < m_available_items && !m_ui_error) + else if (selected_index() < m_available_items && !m_ui_error) { set_selected_index(std::max(selected_index() - m_visible_items, 0)); @@ -1594,14 +2053,25 @@ void menu_select_launch::handle_keys(u32 flags, int &iptkey) // page down advances by m_visible_items if (exclusive_input_pressed(iptkey, IPT_UI_PAGE_DOWN, 6)) { - // Infos - if (show_right_panel() && ((m_focus == focused_menu::RIGHTTOP) || (m_focus == focused_menu::RIGHTBOTTOM))) + if (m_focus == focused_menu::LEFT) + { + // Filters - swallow it + iptkey = IPT_INVALID; + if (!left_at_bottom()) + { + updated = true; + m_left_visible_top += std::min(std::max(m_left_visible_lines - 3, 1), m_left_item_count - m_left_visible_lines - m_left_visible_top); + m_filter_highlight = std::max(m_left_visible_top + 1, m_filter_highlight); + } + } + else if ((m_focus == focused_menu::RIGHTTOP) || (m_focus == focused_menu::RIGHTBOTTOM)) { + // Infos - swallow it + iptkey = IPT_INVALID; + updated = true; m_topline_datsview += m_right_visible_lines - 3; - return; } - - if (selected_index() < m_available_items && !m_ui_error) + else if (selected_index() < m_available_items && !m_ui_error) { set_selected_index(std::min(selected_index() + m_visible_lines - 2 + (selected_index() == 0), m_available_items - 1)); @@ -1612,49 +2082,77 @@ void menu_select_launch::handle_keys(u32 flags, int &iptkey) // home goes to the start if (exclusive_input_pressed(iptkey, IPT_UI_HOME, 0)) { - if (show_left_panel() && m_focus == focused_menu::LEFT) + if (m_focus == focused_menu::LEFT) { - return; + // Filters - swallow it + iptkey = IPT_INVALID; + if (m_filter_highlight) + { + updated = true; + m_left_visible_top = 0; + m_filter_highlight = 0; + } } - else if (show_right_panel() && ((m_focus == focused_menu::RIGHTTOP) || (m_focus == focused_menu::RIGHTBOTTOM))) + else if ((m_focus == focused_menu::RIGHTTOP) || (m_focus == focused_menu::RIGHTBOTTOM)) { + // Infos - swallow it + iptkey = IPT_INVALID; + if (m_topline_datsview) + updated = true; m_topline_datsview = 0; - return; } - - if (selected_index() < m_available_items && !m_ui_error) + else if (selected_index() < m_available_items && !m_ui_error) + { select_first_item(); + } } // end goes to the last if (exclusive_input_pressed(iptkey, IPT_UI_END, 0)) { - if (show_left_panel() && m_focus == focused_menu::LEFT) + if (m_focus == focused_menu::LEFT) { - return; + // Filters - swallow it + iptkey = IPT_INVALID; + if ((m_left_item_count - 1) != m_filter_highlight) + { + updated = true; + if (!left_at_bottom()) + m_left_visible_top = m_left_item_count - m_left_visible_lines; + m_filter_highlight = m_left_item_count - 1; + } } - else if (show_right_panel() && ((m_focus == focused_menu::RIGHTTOP) || (m_focus == focused_menu::RIGHTBOTTOM))) + else if ((m_focus == focused_menu::RIGHTTOP) || (m_focus == focused_menu::RIGHTBOTTOM)) { + // Infos - swallow it + iptkey = IPT_INVALID; + updated = true; m_topline_datsview = m_total_lines; - return; } - - if (selected_index() < m_available_items && !m_ui_error) + else if (selected_index() < m_available_items && !m_ui_error) + { set_selected_index(top_line = m_available_items - 1); + } } // focus next rotates throw targets forward if (exclusive_input_pressed(iptkey, IPT_UI_FOCUS_NEXT, 12)) { if (!m_ui_error) + { rotate_focus(1); + updated = true; + } } // focus next rotates throw targets forward if (exclusive_input_pressed(iptkey, IPT_UI_FOCUS_PREV, 12)) { if (!m_ui_error) + { rotate_focus(-1); + updated = true; + } } // handle a toggle cheats request @@ -1691,238 +2189,942 @@ void menu_select_launch::handle_keys(u32 flags, int &iptkey) break; } } + return updated; } //------------------------------------------------- -// handle input events for main menu +// handle pointer input for main menu //------------------------------------------------- -void menu_select_launch::handle_events(u32 flags, event &ev) +std::tuple<int, bool, bool> menu_select_launch::custom_pointer_updated(bool changed, ui_event const &uievt) { - if (m_pressed) + if (ui_event::type::POINTER_ABORT == uievt.event_type) + return std::make_tuple(IPT_INVALID, false, false); + + // if nothing's happening, check for clicks + if (pointer_idle()) { - bool const pressed = mouse_pressed(); - s32 target_x, target_y; - bool button; - render_target *const mouse_target = machine().ui_input().find_mouse(&target_x, &target_y, &button); - if (mouse_target && button && (hover() == HOVER_ARROW_DOWN || hover() == HOVER_ARROW_UP)) - { - if (pressed) - machine().ui_input().push_mouse_down_event(mouse_target, target_x, target_y); - } + if ((uievt.pointer_pressed & 0x01) && !(uievt.pointer_buttons & ~u32(0x01))) + return handle_primary_down(changed, uievt); + else if ((uievt.pointer_pressed & 0x02) && !(uievt.pointer_buttons & ~u32(0x02))) + return handle_right_down(changed, uievt); + else if ((uievt.pointer_pressed & 0x04) && !(uievt.pointer_buttons & ~u32(0x04))) + return handle_middle_down(changed, uievt); else + return std::make_tuple(IPT_INVALID, false, false); + } + + // handle in-progress actions + switch (m_pointer_action) + { + case pointer_action::NONE: + break; + case pointer_action::MAIN_TRACK_LINE: + return update_main_track_line(changed, uievt); + case pointer_action::MAIN_TRACK_RBUTTON: + return update_main_track_rbutton(changed, uievt); + case pointer_action::MAIN_DRAG: + return update_main_drag(changed, uievt); + case pointer_action::LEFT_TRACK_LINE: + return update_left_track_line(changed, uievt); + case pointer_action::LEFT_DRAG: + return update_left_drag(changed, uievt); + case pointer_action::RIGHT_TRACK_TAB: + return update_right_track_tab(changed, uievt); + case pointer_action::RIGHT_TRACK_ARROW: + return update_right_track_arrow(changed, uievt); + case pointer_action::RIGHT_TRACK_LINE: + return update_right_track_line(changed, uievt); + case pointer_action::RIGHT_SWITCH: + return update_right_switch(changed, uievt); + case pointer_action::RIGHT_DRAG: + return update_right_drag(changed, uievt); + case pointer_action::TOOLBAR_TRACK: + return update_toolbar_track(changed, uievt); + case pointer_action::DIVIDER_TRACK: + return update_divider_track(changed, uievt); + } + return std::make_tuple(IPT_INVALID, false, false); +} + + +//------------------------------------------------- +// handle primary click +//------------------------------------------------- + +std::tuple<int, bool, bool> menu_select_launch::handle_primary_down(bool changed, ui_event const &uievt) +{ + if (m_ui_error) + { + m_ui_error = false; + m_error_text.clear(); + return std::make_tuple(IPT_INVALID, true, true); + } + + auto const [x, y] = pointer_location(); + + // check main item list + if ((x >= m_primary_items_hbounds.first) && (x < m_primary_items_hbounds.second) && (y > m_primary_items_top)) + { + int const line((y - m_primary_items_top) / line_height()); + if (line < (m_primary_lines + m_skip_main_items)) { - reset_pressed(); + int key(IPT_INVALID); + m_pointer_action = pointer_action::MAIN_TRACK_LINE; + m_base_pointer = m_last_pointer = std::make_pair(x, y); + m_clicked_line = line; + if (is_main_up_arrow(line)) + { + // top line is a scroll arrow + --top_line; + if (main_force_visible_selection()) + key = IPT_CUSTOM; // stop processing events so the info pane can be rebuilt + if (!main_at_top()) + m_scroll_repeat = std::chrono::steady_clock::now() + std::chrono::milliseconds(300); + else + m_pointer_action = pointer_action::NONE; + } + else if (is_main_down_arrow(line)) + { + // bottom line is a scroll arrow + ++top_line; + if (main_force_visible_selection()) + key = IPT_CUSTOM; // stop processing events so the info pane can be rebuilt + if (!main_at_bottom()) + m_scroll_repeat = std::chrono::steady_clock::now() + std::chrono::milliseconds(300); + else + m_pointer_action = pointer_action::NONE; + } + return std::make_tuple(key, true, true); } } - // loop while we have interesting events - bool stop(false), search_changed(false); - ui_event local_menu_event; - while (!stop && machine().ui_input().pop_event(&local_menu_event)) + // check filter list + if (show_left_panel() && (x >= m_left_items_hbounds.first) && (x < m_left_items_hbounds.second) && (y >= m_left_items_top)) { - switch (local_menu_event.event_type) + int const line((y - m_left_items_top) / m_info_line_height); + if (line < m_left_visible_lines) { - // if we are hovering over a valid item, select it with a single click - case ui_event::type::MOUSE_DOWN: - if (m_ui_error) + m_pointer_action = pointer_action::LEFT_TRACK_LINE; + m_base_pointer = m_last_pointer = std::make_pair(x, y); + m_clicked_line = line; + if (is_left_up_arrow(line)) { - ev.iptkey = IPT_OTHER; - stop = true; + // top line is a scroll arrow + --m_left_visible_top; + m_filter_highlight = std::min(m_left_visible_top + m_left_visible_lines - 2, m_filter_highlight); + if (!left_at_top()) + m_scroll_repeat = std::chrono::steady_clock::now() + std::chrono::milliseconds(300); + else + m_pointer_action = pointer_action::NONE; + } + else if (is_left_down_arrow(line)) + { + // bottom line is a scroll arrow + ++m_left_visible_top; + m_filter_highlight = std::max(m_left_visible_top + 1, m_filter_highlight); + if (!left_at_bottom()) + m_scroll_repeat = std::chrono::steady_clock::now() + std::chrono::milliseconds(300); + else + m_pointer_action = pointer_action::NONE; } else { - if (hover() >= 0 && hover() < item_count()) - { - if (hover() >= m_available_items - 1 && selected_index() < m_available_items) - m_prev_selected = get_selection_ref(); - set_selected_index(hover()); - m_focus = focused_menu::MAIN; - } - else if (hover() == HOVER_ARROW_UP) - { - set_selected_index(std::max(selected_index() - m_visible_items, 0)); - top_line -= m_visible_items - (top_line + m_visible_lines == m_available_items); - set_pressed(); - } - else if (hover() == HOVER_ARROW_DOWN) - { - set_selected_index(std::min(selected_index() + m_visible_lines - 2 + (selected_index() == 0), m_available_items - 1)); - top_line += m_visible_lines - 2; - set_pressed(); - } - else if (hover() == HOVER_UI_RIGHT) - ev.iptkey = IPT_UI_RIGHT; - else if (hover() == HOVER_UI_LEFT) - ev.iptkey = IPT_UI_LEFT; - else if (hover() == HOVER_DAT_DOWN) - m_topline_datsview += m_right_visible_lines - 3; - else if (hover() == HOVER_DAT_UP) - m_topline_datsview -= m_right_visible_lines - 3; - else if (hover() == HOVER_LPANEL_ARROW) + // ignore multi-click actions - someone hammered on the scroll arrow and hit the end + if (1 < uievt.pointer_clicks) + m_pointer_action = pointer_action::NONE; + } + return std::make_tuple(IPT_INVALID, true, true); + } + } + + // check right panel content + if (show_right_panel()) + { + // check right tabs + if ((x >= right_panel_left()) && (x < right_panel_right()) && (y >= m_primary_vbounds.first) && (y < m_right_tabs_bottom)) + { + int const tab((x - right_panel_left()) / right_tab_width()); + if (tab != m_right_panel) + { + m_pointer_action = pointer_action::RIGHT_TRACK_TAB; + m_clicked_line = tab; + } + return std::make_tuple(IPT_INVALID, true, pointer_action::NONE != m_pointer_action); + } + + if ((x >= m_right_content_hbounds.first) && (x < m_right_content_hbounds.second)) + { + // check right panel heading arrows + if ((ui_event::pointer::TOUCH != uievt.pointer_type) && (y >= right_arrows_top()) && (y < right_arrows_bottom())) + { + if ((x >= (m_right_content_hbounds.first + lr_border())) && (x < (m_right_content_hbounds.first + lr_border() + lr_arrow_width()))) { - if (get_focus() == focused_menu::LEFT) + auto const [updated, notend] = previous_right_panel_view(); + if (notend) { - set_focus(focused_menu::MAIN); - select_prev(); + m_pointer_action = pointer_action::RIGHT_TRACK_ARROW; + m_scroll_repeat = std::chrono::steady_clock::now() + std::chrono::milliseconds(600); + m_base_pointer = m_last_pointer = std::make_pair(x, y); + m_clicked_line = 0; } - - m_panels_status ^= HIDE_LEFT_PANEL; + return std::make_tuple(IPT_INVALID, true, updated); } - else if (hover() == HOVER_RPANEL_ARROW) + else if ((x >= (m_right_content_hbounds.second - lr_border() - lr_arrow_width())) && (x < (m_right_content_hbounds.second - lr_border()))) { - if ((get_focus() == focused_menu::RIGHTTOP) || (get_focus() == focused_menu::RIGHTBOTTOM)) + auto const [updated, notend] = next_right_panel_view(); + if (notend) { - set_focus(focused_menu::MAIN); - select_prev(); + m_pointer_action = pointer_action::RIGHT_TRACK_ARROW; + m_scroll_repeat = std::chrono::steady_clock::now() + std::chrono::milliseconds(600); + m_base_pointer = m_last_pointer = std::make_pair(x, y); + m_clicked_line = 1; } + return std::make_tuple(IPT_INVALID, true, updated); + } + } - m_panels_status ^= HIDE_RIGHT_PANEL; + // check right panel heading touch swipe + if ((ui_event::pointer::TOUCH == uievt.pointer_type) && (y >= m_right_heading_top) && (y < (m_right_heading_top + line_height()))) + { + m_pointer_action = pointer_action::RIGHT_SWITCH; + m_base_pointer = m_last_pointer = std::make_pair(x, y); + m_clicked_line = 0; + if (right_panel() == RP_IMAGES) + { + m_clicked_line = m_image_view; } - else if (hover() == HOVER_B_FAV) + else if (right_panel() == RP_INFOS) { - ev.iptkey = IPT_UI_FAVORITES; - stop = true; + ui_software_info const *software; + ui_system_info const *system; + get_selection(software, system); + if (software && !software->startempty) + m_clicked_line = ui_globals::cur_sw_dats_view; + else if (system || (software && software->driver)) + m_clicked_line = ui_globals::curdats_view; } - else if (hover() == HOVER_B_EXPORT) + return std::make_tuple(IPT_INVALID, true, true); + } + + // check info scroll + if ((RP_INFOS == m_right_panel) && (y >= m_right_content_vbounds.first) && (y < (m_right_content_vbounds.first + (float(m_right_visible_lines) * m_info_line_height)))) + { + int const line((y - m_right_content_vbounds.first) / m_info_line_height); + if (line < m_right_visible_lines) { - inkey_export(); - stop = true; + bool redraw(false); + m_base_pointer = m_last_pointer = std::make_pair(x, y); + m_clicked_line = line; + if (!line && !info_at_top()) + { + redraw = true; + --m_topline_datsview; + if (!info_at_top()) + { + m_pointer_action = pointer_action::RIGHT_TRACK_LINE; + m_scroll_repeat = std::chrono::steady_clock::now() + std::chrono::milliseconds(300); + } + } + else if ((line == (m_right_visible_lines - 1)) && !info_at_bottom()) + { + redraw = true; + ++m_topline_datsview; + if (!info_at_bottom()) + { + m_pointer_action = pointer_action::RIGHT_TRACK_LINE; + m_scroll_repeat = std::chrono::steady_clock::now() + std::chrono::milliseconds(300); + } + } + else if (ui_event::pointer::TOUCH == uievt.pointer_type) + { + m_pointer_action = pointer_action::RIGHT_DRAG; + m_clicked_line = m_topline_datsview; + } + return std::make_tuple(IPT_INVALID, true, redraw); } - else if (hover() == HOVER_B_AUDIT) + } + } + } + + // check toolbar + if ((y >= m_toolbar_button_vbounds.first) && (y < m_toolbar_button_vbounds.second)) + { + if ((x >= m_toolbar_backtrack_left) && (x < (m_toolbar_backtrack_left + m_toolbar_button_width))) + { + m_pointer_action = pointer_action::TOOLBAR_TRACK; + m_clicked_line = -1; + return std::make_tuple(IPT_INVALID, true, true); + } + else + { + unsigned const toolbar_count(m_is_swlist ? std::size(SW_TOOLBAR_BITMAPS) : std::size(SYS_TOOLBAR_BITMAPS)); + float const button(std::floor((x - m_toolbar_main_left) / m_toolbar_button_spacing)); + int const n(button); + if ((n >= 0) && (n < toolbar_count) && (x < (m_toolbar_main_left + (button * m_toolbar_button_spacing) + m_toolbar_button_width))) + { + m_pointer_action = pointer_action::TOOLBAR_TRACK; + m_clicked_line = n; + return std::make_tuple(IPT_INVALID, true, true); + } + } + } + + // check dividers + if ((y >= m_primary_vbounds.first) && (y < m_primary_vbounds.second)) + { + if ((x >= left_divider_left()) && (x < left_divider_right())) + { + m_pointer_action = pointer_action::DIVIDER_TRACK; + m_clicked_line = 0; + return std::make_tuple(IPT_INVALID, true, true); + } + else if ((x >= right_divider_left()) && (x < right_divider_right())) + { + m_pointer_action = pointer_action::DIVIDER_TRACK; + m_clicked_line = 1; + return std::make_tuple(IPT_INVALID, true, true); + } + } + + return std::make_tuple(IPT_INVALID, false, false); +} + + +//------------------------------------------------- +// handle right click +//------------------------------------------------- + +std::tuple<int, bool, bool> menu_select_launch::handle_right_down(bool changed, ui_event const &uievt) +{ + if (m_ui_error) + return std::make_tuple(IPT_INVALID, false, false); + + // check main item list + auto const [x, y] = pointer_location(); + if ((x >= m_primary_items_hbounds.first) && (x < m_primary_items_hbounds.second) && (y > m_primary_items_top)) + { + int const line((y - m_primary_items_top) / line_height()); + if ((line < m_primary_lines) && !is_main_up_arrow(line) && (!is_main_down_arrow(line))) + { + m_pointer_action = pointer_action::MAIN_TRACK_RBUTTON; + m_base_pointer = m_last_pointer = std::make_pair(x, y); + m_clicked_line = line; + return std::make_tuple(IPT_INVALID, true, true); + } + } + + return std::make_tuple(IPT_INVALID, false, false); +} + + +//------------------------------------------------- +// handle middle click +//------------------------------------------------- + +std::tuple<int, bool, bool> menu_select_launch::handle_middle_down(bool changed, ui_event const &uievt) +{ + if (m_ui_error) + return std::make_tuple(IPT_INVALID, false, false); + + auto const [x, y] = pointer_location(); + if ((y >= m_primary_vbounds.first) && (y < m_primary_vbounds.second)) + { + if ((x >= left_divider_right()) && (x < right_divider_left())) + { + // main list + if (m_skip_main_items && (y >= (m_primary_items_top + ((float(m_primary_lines) + ((item(m_available_items).type() == menu_item_type::SEPARATOR) ? 0.5F : 0.0F)) * line_height())))) + { + if (selected_index() < m_available_items) { - ev.iptkey = IPT_UI_AUDIT; - stop = true; + m_prev_selected = get_selection_ref(); + set_selected_index(m_available_items + 1); } - else if (hover() == HOVER_B_DATS) + } + else + { + if ((get_focus() != focused_menu::MAIN) || (selected_index() > m_available_items)) + select_prev(); + } + set_focus(focused_menu::MAIN); + } + else if ((x >= left_panel_left()) && (x < left_panel_right())) + { + // left panel + assert(show_left_panel()); + if ((get_focus() == focused_menu::MAIN) && (selected_index() <= m_available_items)) + m_prev_selected = get_selection_ref(); + set_focus(focused_menu::LEFT); + return std::make_tuple(IPT_INVALID, true, true); + } + else if ((x >= right_panel_left()) && (x < right_panel_right())) + { + // right panel + assert(show_right_panel()); + if ((get_focus() == focused_menu::MAIN) && (selected_index() <= m_available_items)) + m_prev_selected = get_selection_ref(); + set_focus((y < m_right_tabs_bottom) ? focused_menu::RIGHTTOP : focused_menu::RIGHTBOTTOM); + return std::make_tuple(IPT_INVALID, true, true); + } + } + + return std::make_tuple(IPT_INVALID, false, false); +} + + +//------------------------------------------------- +// track click on main item +//------------------------------------------------- + +std::tuple<int, bool, bool> menu_select_launch::update_main_track_line(bool changed, ui_event const &uievt) +{ + auto const [x, y] = pointer_location(); + float const itemtop(m_primary_items_top + (float(m_clicked_line) * line_height())); + float const itembottom(m_primary_items_top + (float(m_clicked_line + 1) * line_height())); + int key(IPT_INVALID); + bool redraw(false); + + if (is_main_up_arrow(m_clicked_line) || is_main_down_arrow(m_clicked_line)) + { + // top or bottom line is a scroll arrow + bool const reentered(reentered_rect(m_last_pointer.first, m_last_pointer.second, x, y, m_primary_items_hbounds.first, itemtop, m_primary_items_hbounds.second, itembottom)); + if (reentered) + { + auto const now(std::chrono::steady_clock::now()); + if (now >= m_scroll_repeat) + { + m_scroll_repeat = std::chrono::steady_clock::now() + std::chrono::milliseconds(100); + if (!m_clicked_line) { - inkey_dats(); - stop = true; + --top_line; + if (main_at_top()) + m_pointer_action = pointer_action::NONE; } - else if (hover() == HOVER_BACKTRACK) + else { - ev.iptkey = IPT_UI_BACK; - stack_pop(); - if (is_special_main_menu()) - machine().schedule_exit(); - stop = true; + ++top_line; + if (main_at_bottom()) + m_pointer_action = pointer_action::NONE; } - else if (hover() >= HOVER_RP_FIRST && hover() <= HOVER_RP_LAST) + if (main_force_visible_selection()) + key = IPT_CUSTOM; // stop processing events so the info pane can be rebuilt + redraw = true; + } + } + } + else + { + // check for conversion to touch scroll + if (ui_event::pointer::TOUCH == uievt.pointer_type) + { + auto const [h, v] = check_drag_conversion(x, y, m_base_pointer.first, m_base_pointer.second, line_height()); + if (h || (v && (m_clicked_line >= m_primary_lines))) + { + m_pointer_action = pointer_action::NONE; + return std::make_tuple(IPT_INVALID, false, true); + } + else if (v) + { + m_pointer_action = pointer_action::MAIN_DRAG; + m_last_pointer = m_base_pointer; + m_clicked_line = top_line; + return update_main_drag(changed, uievt); + } + } + + // check to see if they released over the item + if (uievt.pointer_released & 0x01) + { + m_pointer_action = pointer_action::NONE; + if ((0 < uievt.pointer_clicks) || ((x >= m_primary_items_hbounds.first) && (x < m_primary_items_hbounds.second) && (y >= itemtop) && (y < itembottom))) + { + if (m_clicked_line < m_visible_lines) { - m_right_panel = (HOVER_RP_FIRST - hover()) * (-1); - stop = true; + // systems or software items are always selectable + if (2 == uievt.pointer_clicks) + key = IPT_UI_SELECT; + else if (selected_index() != (m_clicked_line + top_line)) + key = IPT_CUSTOM; // stop processing events so the info pane can be rebuilt + set_selected_index(m_clicked_line + top_line); + set_focus(focused_menu::MAIN); } - else if (hover() >= HOVER_FILTER_FIRST && hover() <= HOVER_FILTER_LAST) + else if ((m_clicked_line >= m_primary_lines) && (m_clicked_line < (m_primary_lines + m_skip_main_items))) { - m_filter_highlight = hover() - HOVER_FILTER_FIRST; - filter_selected(); - stop = true; + // need to ensure this is a selectable item + int const itemnum(m_available_items + m_clicked_line - m_primary_lines); + if (is_selectable(item(itemnum))) + { + if (selected_index() < m_available_items) + m_prev_selected = get_selection_ref(); + set_selected_index(itemnum); + set_focus(focused_menu::MAIN); + if (2 == uievt.pointer_clicks) + key = IPT_UI_SELECT; + } } } - break; + return std::make_tuple(key, false, true); + } + } - // if we are hovering over a valid item, fake a UI_SELECT with a double-click - case ui_event::type::MOUSE_DOUBLE_CLICK: - if (hover() >= 0 && hover() < item_count()) - { - set_selected_index(hover()); - ev.iptkey = IPT_UI_SELECT; - } - stop = true; - break; + // stop tracking if the primary button is released or another button is pressed + if ((uievt.pointer_released & 0x01) || (uievt.pointer_pressed & ~u32(0x01))) + m_pointer_action = pointer_action::NONE; + if (pointer_action::NONE != m_pointer_action) + m_last_pointer = std::make_pair(x, y); + return std::make_tuple(key, pointer_action::NONE != m_pointer_action, redraw); +} - // caught scroll event - case ui_event::type::MOUSE_WHEEL: - if (hover() >= 0 && hover() < item_count() - m_skip_main_items) + +//------------------------------------------------- +// track right click +//------------------------------------------------- + +std::tuple<int, bool, bool> menu_select_launch::update_main_track_rbutton(bool changed, ui_event const &uievt) +{ + // see if it was released inside the line + if (uievt.pointer_released & 0x02) + { + m_pointer_action = pointer_action::NONE; + auto const [x, y] = pointer_location(); + float const linetop(m_primary_items_top + (float(m_clicked_line) * line_height())); + float const linebottom(m_primary_items_top + (float(m_clicked_line + 1) * line_height())); + if ((x >= m_primary_items_hbounds.first) && (x < m_primary_items_hbounds.second) && (y >= linetop) && (y < linebottom)) + { + show_config_menu(m_clicked_line + top_line); + return std::make_tuple(IPT_CUSTOM, false, false); // return IPT_CUSTOM to stop processing events + } + } + + // stop tracking if another button is pressed + if (uievt.pointer_pressed & ~u32(0x02)) + m_pointer_action = pointer_action::NONE; + return std::make_tuple(IPT_INVALID, pointer_action::NONE != m_pointer_action, pointer_action::DIVIDER_TRACK != m_pointer_action); +} + + +//------------------------------------------------- +// track main touch drag scroll +//------------------------------------------------- + +std::tuple<int, bool, bool> menu_select_launch::update_main_drag(bool changed, ui_event const &uievt) +{ + auto const newtop(drag_scroll( + pointer_location().second, m_base_pointer.second, m_last_pointer.second, -line_height(), + m_clicked_line, 0, m_available_items - m_primary_lines)); + bool const moved(newtop != top_line); + int key(IPT_INVALID); + if (moved) + { + // scroll and move the selection if necessary to keep it in the visible range + top_line = newtop; + if (main_force_visible_selection()) + key = IPT_CUSTOM; // stop processing events so the info pane can be rebuilt + } + + // stop tracking if the primary button is released or another button is pressed + if ((uievt.pointer_released & 0x01) || (uievt.pointer_pressed & ~u32(0x01))) + m_pointer_action = pointer_action::NONE; + return std::make_tuple(key, pointer_action::NONE != m_pointer_action, moved); +} + + +//------------------------------------------------- +// track click on left panel item +//------------------------------------------------- + +std::tuple<int, bool, bool> menu_select_launch::update_left_track_line(bool changed, ui_event const &uievt) +{ + auto const [x, y] = pointer_location(); + float const itemtop(m_left_items_top + (float(m_clicked_line) * m_info_line_height)); + float const itembottom(m_left_items_top + (float(m_clicked_line + 1) * m_info_line_height)); + bool redraw(false); + + if (is_left_up_arrow(m_clicked_line) || is_left_down_arrow(m_clicked_line)) + { + // top or bottom line is a scroll arrow + bool const reentered(reentered_rect(m_last_pointer.first, m_last_pointer.second, x, y, m_left_items_hbounds.first, itemtop, m_left_items_hbounds.second, itembottom)); + if (reentered) + { + auto const now(std::chrono::steady_clock::now()); + if (now >= m_scroll_repeat) { - if (local_menu_event.zdelta > 0) + m_scroll_repeat = now + std::chrono::milliseconds(100); + if (!m_clicked_line) { - if (selected_index() >= m_available_items || is_first_selected() || m_ui_error) - break; - set_selected_index(selected_index() - local_menu_event.num_lines); - if (selected_index() < top_line + (top_line != 0)) - top_line -= local_menu_event.num_lines; + --m_left_visible_top; + m_filter_highlight = std::min(m_left_visible_top + m_left_visible_lines - 2, m_filter_highlight); + if (left_at_top()) + m_pointer_action = pointer_action::NONE; } else { - if (selected_index() >= m_available_items - 1 || m_ui_error) - break; - set_selected_index(std::min(selected_index() + local_menu_event.num_lines, m_available_items - 1)); - if (selected_index() >= top_line + m_visible_items + (top_line != 0)) - top_line += local_menu_event.num_lines; + ++m_left_visible_top; + m_filter_highlight = std::max(m_left_visible_top + 1, m_filter_highlight); + if (left_at_bottom()) + m_pointer_action = pointer_action::NONE; } + redraw = true; } - else if (hover() == HOVER_INFO_TEXT) + } + } + else + { + // check for conversion to touch scroll + if (ui_event::pointer::TOUCH == uievt.pointer_type) + { + auto const [h, v] = check_drag_conversion(x, y, m_base_pointer.first, m_base_pointer.second, m_info_line_height); + if (h) { - if (local_menu_event.zdelta > 0) - m_topline_datsview -= local_menu_event.num_lines; - else - m_topline_datsview += local_menu_event.num_lines; + m_pointer_action = pointer_action::NONE; + return std::make_tuple(IPT_INVALID, false, true); } - break; - - // translate CHAR events into specials - case ui_event::type::IME_CHAR: - if (exclusive_input_pressed(ev.iptkey, IPT_UI_FOCUS_NEXT, 0) || exclusive_input_pressed(ev.iptkey, IPT_UI_FOCUS_PREV, 0)) + else if (v) { - stop = true; + m_pointer_action = pointer_action::LEFT_DRAG; + m_last_pointer = m_base_pointer; + m_clicked_line = m_left_visible_top; + return update_left_drag(changed, uievt); } - else if (m_ui_error) + } + + // this is a filter - check to see if they released over the item + if ((uievt.pointer_released & 0x01) && (x >= m_left_items_hbounds.first) && (x < m_left_items_hbounds.second) && (y >= itemtop) && (y < itembottom)) + { + m_pointer_action = pointer_action::NONE; + filter_selected(m_left_visible_top + m_clicked_line); + return std::make_tuple(IPT_CUSTOM, false, true); // return IPT_CUSTOM to stop processing events + } + } + + // stop tracking if the primary button is released or another button is pressed + if ((uievt.pointer_released & 0x01) || (uievt.pointer_pressed & ~u32(0x01))) + m_pointer_action = pointer_action::NONE; + if (pointer_action::NONE != m_pointer_action) + m_last_pointer = std::make_pair(x, y); + return std::make_tuple(IPT_INVALID, pointer_action::NONE != m_pointer_action, redraw); +} + + +//------------------------------------------------- +// track left panel touch drag scroll +//------------------------------------------------- + +std::tuple<int, bool, bool> menu_select_launch::update_left_drag(bool changed, ui_event const &uievt) +{ + auto const newtop(drag_scroll( + pointer_location().second, m_base_pointer.second, m_last_pointer.second, -m_info_line_height, + m_clicked_line, 0, m_left_item_count - m_left_visible_lines)); + bool const moved(newtop != m_left_visible_top); + if (moved) + { + // scroll and move the selection if necessary to keep it in the visible range + m_left_visible_top = newtop; + int const first(left_at_top() ? 0 : (newtop + 1)); + int const last(newtop + m_left_visible_lines - (left_at_bottom() ? 1 : 2)); + m_filter_highlight = std::clamp(m_filter_highlight, first, last); + } + + // stop tracking if the primary button is released or another button is pressed + if ((uievt.pointer_released & 0x01) || (uievt.pointer_pressed & ~u32(0x01))) + m_pointer_action = pointer_action::NONE; + return std::make_tuple(IPT_INVALID, pointer_action::NONE != m_pointer_action, moved); +} + + +//------------------------------------------------- +// track click on right panel tab +//------------------------------------------------- + +std::tuple<int, bool, bool> menu_select_launch::update_right_track_tab(bool changed, ui_event const &uievt) +{ + // see if it was released inside the divider + if (uievt.pointer_released & 0x01) + { + m_pointer_action = pointer_action::NONE; + auto const [x, y] = pointer_location(); + float const left(right_panel_left() + (float(m_clicked_line) * right_tab_width())); + float const right(right_panel_left() + (float(m_clicked_line + 1) * right_tab_width())); + if ((x >= left) && (x < right) && (y >= m_primary_vbounds.first) && (y < m_right_tabs_bottom)) + { + m_right_panel = m_clicked_line; + return std::make_tuple(IPT_CUSTOM, false, true); // return IPT_CUSTOM to stop processing events + } + } + + // stop tracking if another button is pressed + if (uievt.pointer_pressed & ~u32(0x01)) + m_pointer_action = pointer_action::NONE; + return std::make_tuple(IPT_INVALID, pointer_action::NONE != m_pointer_action, pointer_action::DIVIDER_TRACK != m_pointer_action); +} + + +//------------------------------------------------- +// track right panel heading left/right arrows +//------------------------------------------------- + +std::tuple<int, bool, bool> menu_select_launch::update_right_track_arrow(bool changed, ui_event const &uievt) +{ + auto const [x, y] = pointer_location(); + float const left(m_clicked_line ? (m_right_content_hbounds.second - lr_border() - lr_arrow_width()) : (m_right_content_hbounds.first + lr_border())); + float const right(m_clicked_line ? (m_right_content_hbounds.second - lr_border()) : (m_right_content_hbounds.first + lr_border() + lr_arrow_width())); + + // check for reentry + bool redraw(false); + bool const reentered(reentered_rect(m_last_pointer.first, m_last_pointer.second, x, y, left, right_arrows_top(), right, right_arrows_bottom())); + if (reentered) + { + auto const now(std::chrono::steady_clock::now()); + if (now >= m_scroll_repeat) + { + m_scroll_repeat = std::chrono::steady_clock::now() + std::chrono::milliseconds(200); + bool notend; + std::tie(redraw, notend) = m_clicked_line ? next_right_panel_view() : previous_right_panel_view(); + if (!notend) + m_pointer_action = pointer_action::NONE; + } + } + + // stop tracking if the primary button is released or another button is pressed + if ((uievt.pointer_released & 0x01) || (uievt.pointer_pressed & ~u32(0x01))) + m_pointer_action = pointer_action::NONE; + if (pointer_action::NONE != m_pointer_action) + m_last_pointer = std::make_pair(x, y); + return std::make_tuple(IPT_INVALID, pointer_action::NONE != m_pointer_action, redraw); +} + + +//------------------------------------------------- +// track right scroll arrows +//------------------------------------------------- + +std::tuple<int, bool, bool> menu_select_launch::update_right_track_line(bool changed, ui_event const &uievt) +{ + auto const [x, y] = pointer_location(); + float const itemtop(m_right_content_vbounds.first + (float(m_clicked_line) * m_info_line_height)); + float const itembottom(m_right_content_vbounds.first + (float(m_clicked_line + 1) * m_info_line_height)); + bool redraw(false); + + bool const reentered(reentered_rect(m_last_pointer.first, m_last_pointer.second, x, y, m_right_content_hbounds.first, itemtop, m_right_content_hbounds.second, itembottom)); + if (reentered) + { + auto const now(std::chrono::steady_clock::now()); + if (now >= m_scroll_repeat) + { + m_scroll_repeat = std::chrono::steady_clock::now() + std::chrono::milliseconds(100); + if (!m_clicked_line) { - ev.iptkey = IPT_SPECIAL; - stop = true; + --m_topline_datsview; + if (info_at_top()) + m_pointer_action = pointer_action::NONE; } - else if (accept_search()) + else { - if (input_character(m_search, local_menu_event.ch, uchar_is_printable)) - search_changed = true; + ++m_topline_datsview; + if (info_at_bottom()) + m_pointer_action = pointer_action::NONE; } - break; + redraw = true; + } + } + + // stop tracking if the primary button is released or another button is pressed + if ((uievt.pointer_released & 0x01) || (uievt.pointer_pressed & ~u32(0x01))) + m_pointer_action = pointer_action::NONE; + if (pointer_action::NONE != m_pointer_action) + m_last_pointer = std::make_pair(x, y); + return std::make_tuple(IPT_INVALID, pointer_action::NONE != m_pointer_action, redraw); +} + + +//------------------------------------------------- +// track panel heading touch drag switch +//------------------------------------------------- - case ui_event::type::MOUSE_RDOWN: - if (hover() >= 0 && hover() < item_count() - m_skip_main_items) +std::tuple<int, bool, bool> menu_select_launch::update_right_switch(bool changed, ui_event const &uievt) +{ + // get new page + ui_software_info const *software; + ui_system_info const *system; + get_selection(software, system); + int const min((RP_IMAGES == m_right_panel) ? FIRST_VIEW : 0); + int const max( + (RP_IMAGES == m_right_panel) ? LAST_VIEW : + (software && !software->startempty) ? (ui_globals::cur_sw_dats_total - 1) : + (system || (software && software->driver)) ? (ui_globals::curdats_total - 1) : + 0); + auto const newpage(drag_scroll( + pointer_location().first, m_base_pointer.first, m_last_pointer.first, 0.125F * (m_right_content_hbounds.first - m_right_content_hbounds.second), + m_clicked_line, min, max)); + + // switch page + u8 dummy(newpage); + u8 ¤t( + (RP_IMAGES == m_right_panel) ? m_image_view : + (software && !software->startempty) ? ui_globals::cur_sw_dats_view : + (system || (software && software->driver)) ? ui_globals::curdats_view : + dummy); + bool const redraw(newpage != current); + if (redraw) + { + current = newpage; + if (RP_IMAGES == m_right_panel) + set_switch_image(); + else + m_topline_datsview = 0; + } + + // stop tracking if the primary button is released or another button is pressed + if ((uievt.pointer_released & 0x01) || (uievt.pointer_pressed & ~u32(0x01))) + m_pointer_action = pointer_action::NONE; + return std::make_tuple(IPT_INVALID, pointer_action::NONE != m_pointer_action, redraw); +} + + +//------------------------------------------------- +// track right panel touch drag scroll +//------------------------------------------------- + +std::tuple<int, bool, bool> menu_select_launch::update_right_drag(bool changed, ui_event const &uievt) +{ + auto const newtop(drag_scroll( + pointer_location().second, m_base_pointer.second, m_last_pointer.second, -m_info_line_height, + m_clicked_line, 0, m_total_lines - m_right_visible_lines)); + bool const moved(newtop != m_topline_datsview); + m_topline_datsview = newtop; + + // stop tracking if the primary button is released or another button is pressed + if ((uievt.pointer_released & 0x01) || (uievt.pointer_pressed & ~u32(0x01))) + m_pointer_action = pointer_action::NONE; + return std::make_tuple(IPT_INVALID, pointer_action::NONE != m_pointer_action, moved); +} + + +//------------------------------------------------- +// track click on toolbar button +//------------------------------------------------- + +std::tuple<int, bool, bool> menu_select_launch::update_toolbar_track(bool changed, ui_event const &uievt) +{ + // see if it was released inside the button + if (uievt.pointer_released & 0x01) + { + m_pointer_action = pointer_action::NONE; + auto const [x, y] = pointer_location(); + float const left((0 > m_clicked_line) ? m_toolbar_backtrack_left : (m_toolbar_main_left + (float(m_clicked_line) * m_toolbar_button_spacing))); + if ((x >= left) && (x < (left + m_toolbar_button_width)) && (y >= m_toolbar_button_vbounds.first) && (y < m_toolbar_button_vbounds.second)) + { + if (0 > m_clicked_line) { - set_selected_index(hover()); - m_prev_selected = get_selection_ref(); - m_focus = focused_menu::MAIN; - ev.iptkey = IPT_CUSTOM; - ev.mouse.x0 = local_menu_event.mouse_x; - ev.mouse.y0 = local_menu_event.mouse_y; - stop = true; + // backtrack button + stack_pop(); + if (is_special_main_menu()) + machine().schedule_exit(); + return std::make_tuple(IPT_UI_BACK, false, true); + } + else + { + // main buttons + auto const *const toolbar_bitmaps(m_is_swlist ? SW_TOOLBAR_BITMAPS : SYS_TOOLBAR_BITMAPS); + auto const [bitmap, action, need_selection] = toolbar_bitmaps[m_clicked_line]; + switch (action) + { + case IPT_UI_EXPORT: + inkey_export(); + break; + case IPT_UI_DATS: + inkey_dats(); + break; + default: + return std::make_tuple(action, false, true); + } + return std::make_tuple(IPT_CUSTOM, false, true); // return IPT_CUSTOM to stop processing events } - break; - - // ignore everything else - default: - break; } + } - // need to update search before processing certain kinds of events, but others don't matter - if (search_changed) + // stop tracking if another button is pressed + if (uievt.pointer_pressed & ~u32(0x01)) + m_pointer_action = pointer_action::NONE; + return std::make_tuple(IPT_INVALID, pointer_action::NONE != m_pointer_action, pointer_action::TOOLBAR_TRACK != m_pointer_action); +} + + +//------------------------------------------------- +// track click on divider +//------------------------------------------------- + +std::tuple<int, bool, bool> menu_select_launch::update_divider_track(bool changed, ui_event const &uievt) +{ + // see if it was released inside the divider + if (uievt.pointer_released & 0x01) + { + m_pointer_action = pointer_action::NONE; + auto const [x, y] = pointer_location(); + float const left(m_clicked_line ? right_divider_left() : left_divider_left()); + float const right(m_clicked_line ? right_divider_right() : left_divider_right()); + if ((x >= left) && (x < right) && (y >= m_primary_vbounds.first) && (y < m_primary_vbounds.second)) { - switch (machine().ui_input().peek_event_type()) + if (m_clicked_line) { - case ui_event::type::MOUSE_DOWN: - case ui_event::type::MOUSE_RDOWN: - case ui_event::type::MOUSE_DOUBLE_CLICK: - case ui_event::type::MOUSE_WHEEL: - stop = true; - break; - case ui_event::type::NONE: - case ui_event::type::WINDOW_FOCUS: - case ui_event::type::WINDOW_DEFOCUS: - case ui_event::type::MOUSE_MOVE: - case ui_event::type::MOUSE_LEAVE: - case ui_event::type::MOUSE_UP: - case ui_event::type::MOUSE_RUP: - case ui_event::type::IME_CHAR: - break; + if ((get_focus() == focused_menu::RIGHTTOP) || (get_focus() == focused_menu::RIGHTBOTTOM)) + { + set_focus(focused_menu::MAIN); + select_prev(); + } + m_panels_status ^= HIDE_RIGHT_PANEL; } + else + { + if (get_focus() == focused_menu::LEFT) + { + set_focus(focused_menu::MAIN); + select_prev(); + } + m_panels_status ^= HIDE_LEFT_PANEL; + } + return std::make_tuple(IPT_CUSTOM, false, true); // return IPT_CUSTOM to stop processing events } } - if (search_changed) - reset(reset_options::SELECT_FIRST); + + // stop tracking if another button is pressed + if (uievt.pointer_pressed & ~u32(0x01)) + m_pointer_action = pointer_action::NONE; + return std::make_tuple(IPT_INVALID, pointer_action::NONE != m_pointer_action, pointer_action::DIVIDER_TRACK != m_pointer_action); +} + + +//------------------------------------------------- +// move selection to visible range +//------------------------------------------------- + +bool menu_select_launch::main_force_visible_selection() +{ + int const first(main_at_top() ? 0 : (top_line + 1)); + int const last(top_line + m_primary_lines - (main_at_bottom() ? 1 : 2)); + if (selected_index() < m_available_items) + { + int const restricted(std::clamp(selected_index(), first, last)); + if (selected_index() != restricted) + { + set_selected_index(restricted); + return true; + } + } + else + { + int selection(0); + while ((m_available_items > selection) && (item(selection).ref() != m_prev_selected)) + ++selection; + auto const ref(item(std::clamp(selection, first, last)).ref()); + if (ref != m_prev_selected) + { + m_prev_selected = ref; + return true; + } + } + return false; } @@ -1932,57 +3134,22 @@ void menu_select_launch::handle_events(u32 flags, event &ev) void menu_select_launch::draw(u32 flags) { - bool noinput = (flags & PROCESS_NOINPUT); - float const icon_offset = m_has_icons ? (1.5f * ud_arrow_width()) : 0.0f; - float right_panel_size = show_right_panel() ? 0.3f : m_divider_width; - float visible_width = 1.0f - 4.0f * lr_border(); - float primary_left = (1.0f - visible_width) * 0.5f; - float primary_width = visible_width; - draw_background(); + // recompute height of primary menu area if necessary + if (m_primary_vbounds.first >= m_primary_vbounds.second) + { + float const pixelheight(target_size().second); + float const lines(std::floor((1.0F - (4.0F * tb_border()) - get_customtop() - get_custombottom()) / line_height())); + float const itemsheight(line_height() * lines); + float const space(1.0F - itemsheight - get_customtop() - get_custombottom()); + m_primary_items_top = std::round((get_customtop() + (0.5F * space)) * pixelheight) / pixelheight; + m_primary_vbounds = std::make_pair(m_primary_items_top - tb_border(), m_primary_items_top + itemsheight + tb_border()); + m_primary_lines = int(lines) - m_skip_main_items; + } - clear_hover(); + // ensure the selection is visible m_available_items = item_count() - m_skip_main_items; - float extra_height = m_skip_main_items * line_height(); - float visible_extra_menu_height = get_customtop() + get_custombottom() + extra_height; - - // locate mouse - if (noinput) - ignore_mouse(); - else - map_mouse(); - - // account for extra space at the top and bottom - float visible_main_menu_height = 1.0f - 2.0f * tb_border() - visible_extra_menu_height; - m_visible_lines = int(std::trunc(visible_main_menu_height / line_height())); - visible_main_menu_height = float(m_visible_lines) * line_height(); - - // compute top/left of inner menu area by centering - float visible_left = primary_left; - float visible_top = (1.0f - (visible_main_menu_height + visible_extra_menu_height)) * 0.5f; - - // if the menu is at the bottom of the extra, adjust - visible_top += get_customtop(); - - // compute left box size - float x1 = visible_left - lr_border(); - float y1 = visible_top - tb_border(); - float x2 = x1 + 2.0f * lr_border(); - float y2 = visible_top + visible_main_menu_height + tb_border() + extra_height; - - // add left box - visible_left = draw_left_panel(x1, y1, x2, y2); - visible_width -= right_panel_size + visible_left - 2.0f * lr_border(); - - // compute and add main box - x1 = visible_left - lr_border(); - x2 = visible_left + visible_width + lr_border(); - float line = visible_top + (float(m_visible_lines) * line_height()); - ui().draw_outlined_box(container(), x1, y1, x2, y2, ui().colors().background_color()); - - // make sure the selection - if (m_available_items < m_visible_lines) - m_visible_lines = m_available_items; + m_visible_lines = (std::min)(m_primary_lines, m_available_items); int selection; if (selected_index() < m_available_items) { @@ -2007,35 +3174,41 @@ void menu_select_launch::draw(u32 flags) else if (selection >= (top_line + m_visible_lines - 2)) top_line = selection - m_visible_lines + ((selection == (m_available_items - 1)) ? 1: 2); } - - // determine effective positions taking into account the hilighting arrows - float effective_width = visible_width - 2.0f * gutter_width(); - float effective_left = visible_left + gutter_width(); - if ((m_focus == focused_menu::MAIN) && (selected_index() < m_available_items)) m_prev_selected = nullptr; - int const n_loop = (std::min)(m_visible_lines, m_available_items); - float const pixelheight = machine().render().ui_target().height(); - for (int linenum = 0; linenum < n_loop; linenum++) + // draw background, left and right panels, and outline of main box + draw_background(); + draw_left_panel(flags); + draw_right_panel(flags); + ui().draw_outlined_box( + container(), + left_divider_right(), m_primary_vbounds.first, right_divider_left(), m_primary_vbounds.second, + ui().colors().background_color()); + + // calculate horizontal geometry of main item list + m_primary_items_hbounds = std::make_pair(left_divider_right() + lr_border(), right_divider_left() - lr_border()); + float const item_text_left(m_primary_items_hbounds.first + gutter_width()); + float const item_text_width(m_primary_items_hbounds.second - m_primary_items_hbounds.first - (2.0F * gutter_width())); + float const icon_offset(m_has_icons ? (1.5F * ud_arrow_width()) : 0.0F); + + // draw main scrolling items + for (int linenum = 0; linenum < m_visible_lines; linenum++) { - float line_y = (std::round(visible_top * pixelheight) / pixelheight) + (float(linenum) * line_height()); - int itemnum = top_line + linenum; - const menu_item &pitem = item(itemnum); - const std::string_view itemtext = pitem.text(); + int const itemnum(top_line + linenum); + menu_item const &pitem(item(itemnum)); + std::string_view const itemtext(pitem.text()); + float const linetop(m_primary_items_top + (float(linenum) * line_height())); + float const linebottom(linetop + line_height()); + + // work out colours rgb_t fgcolor = ui().colors().text_color(); rgb_t bgcolor = ui().colors().text_bg_color(); rgb_t fgcolor3 = ui().colors().clone_color(); - float line_x0 = x1 + 0.5f * UI_LINE_WIDTH; - float line_y0 = line_y; - float line_x1 = x2 - 0.5f * UI_LINE_WIDTH; - float line_y1 = line_y + line_height(); - - // set the hover if this is our item - if (mouse_in_rect(line_x0, line_y0, line_x1, line_y1) && is_selectable(pitem)) - set_hover(itemnum); - - if (is_selected(itemnum) && m_focus == focused_menu::MAIN) + bool const hovered(is_selectable(pitem) && pointer_in_rect(m_primary_items_hbounds.first, linetop, m_primary_items_hbounds.second, linebottom)); + bool const pointerline((pointer_action::MAIN_TRACK_LINE == m_pointer_action) && (linenum == m_clicked_line)); + bool const rclickline((pointer_action::MAIN_TRACK_RBUTTON == m_pointer_action) && (linenum == m_clicked_line)); + if (!rclickline && is_selected(itemnum) && (get_focus() == focused_menu::MAIN)) { // if we're selected, draw with a different background fgcolor = rgb_t(0xff, 0xff, 0x00); @@ -2043,173 +3216,177 @@ void menu_select_launch::draw(u32 flags) fgcolor3 = rgb_t(0xcc, 0xcc, 0x00); ui().draw_textured_box( container(), - line_x0 + 0.01f, line_y0, line_x1 - 0.01f, line_y1, + m_primary_items_hbounds.first, linetop, m_primary_items_hbounds.second, linebottom, bgcolor, rgb_t(43, 43, 43), hilight_main_texture(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXWRAP(1)); } - else if (itemnum == hover()) + else if ((pointerline || rclickline) && hovered) + { + // draw selected highlight for tracked item + fgcolor = fgcolor3 = ui().colors().selected_color(); + bgcolor = ui().colors().selected_bg_color(); + highlight(m_primary_items_hbounds.first, linetop, m_primary_items_hbounds.second, linebottom, bgcolor); + } + else if (pointerline || rclickline || (!m_ui_error && !(flags & PROCESS_NOINPUT) && hovered && pointer_idle())) { - // else if the mouse is over this item, draw with a different background - fgcolor = fgcolor3 = ui().options().mouseover_color(); + // draw hover highlight when hovered over or dragged off + fgcolor = fgcolor3 = ui().colors().mouseover_color(); bgcolor = ui().colors().mouseover_bg_color(); - highlight(line_x0, line_y0, line_x1, line_y1, bgcolor); + highlight(m_primary_items_hbounds.first, linetop, m_primary_items_hbounds.second, linebottom, bgcolor); } else if (pitem.ref() == m_prev_selected) { - fgcolor = fgcolor3 = ui().options().mouseover_color(); + fgcolor = fgcolor3 = ui().colors().mouseover_color(); bgcolor = ui().colors().mouseover_bg_color(); - ui().draw_textured_box(container(), line_x0 + 0.01f, line_y0, line_x1 - 0.01f, line_y1, bgcolor, rgb_t(43, 43, 43), + ui().draw_textured_box( + container(), + m_primary_items_hbounds.first, linetop, m_primary_items_hbounds.second, linebottom, + bgcolor, rgb_t(43, 43, 43), hilight_main_texture(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXWRAP(1)); } - if (linenum == 0 && top_line != 0) + if ((!linenum && top_line) || ((linenum == (m_visible_lines - 1)) && (itemnum != m_available_items - 1))) { - // if we're on the top line, display the up arrow - draw_arrow(0.5f * (x1 + x2) - 0.5f * ud_arrow_width(), line_y + 0.25f * line_height(), - 0.5f * (x1 + x2) + 0.5f * ud_arrow_width(), line_y + 0.75f * line_height(), fgcolor, ROT0); - - if (hover() == itemnum) - set_hover(HOVER_ARROW_UP); - } - else if (linenum == m_visible_lines - 1 && itemnum != m_available_items - 1) - { - // if we're on the bottom line, display the down arrow - draw_arrow(0.5f * (x1 + x2) - 0.5f * ud_arrow_width(), line_y + 0.25f * line_height(), - 0.5f * (x1 + x2) + 0.5f * ud_arrow_width(), line_y + 0.75f * line_height(), fgcolor, ROT0 ^ ORIENTATION_FLIP_Y); - - if (hover() == itemnum) - set_hover(HOVER_ARROW_DOWN); + // if we're on the top or bottom line, display the up or down arrow + draw_arrow( + 0.5F * (m_primary_items_hbounds.first + m_primary_items_hbounds.second - ud_arrow_width()), linetop + (0.25F * line_height()), + 0.5F * (m_primary_items_hbounds.first + m_primary_items_hbounds.second + ud_arrow_width()), linetop + (0.75F * line_height()), + fgcolor, + linenum ? (ROT0 ^ ORIENTATION_FLIP_Y) : ROT0); } else if (pitem.type() == menu_item_type::SEPARATOR) { // if we're just a divider, draw a line - container().add_line(visible_left, line_y + 0.5f * line_height(), visible_left + visible_width, line_y + 0.5f * line_height(), + container().add_line( + m_primary_items_hbounds.first, linetop + (0.5F * line_height()), + m_primary_items_hbounds.second, linetop + (0.5F * line_height()), UI_LINE_WIDTH, ui().colors().text_color(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); } - else if (pitem.subtext().empty()) - { - // draw the item centered - int const item_invert = pitem.flags() & FLAG_INVERT; - if (m_has_icons) - draw_icon(linenum, item(itemnum).ref(), effective_left, line_y); - ui().draw_text_full( - container(), - itemtext, - effective_left + icon_offset, line_y, effective_width - icon_offset, - text_layout::text_justify::LEFT, text_layout::word_wrapping::TRUNCATE, - mame_ui_manager::NORMAL, item_invert ? fgcolor3 : fgcolor, bgcolor, - nullptr, nullptr, - line_height()); - } else { - int const item_invert = pitem.flags() & FLAG_INVERT; - std::string_view const subitem_text = pitem.subtext(); - float item_width, subitem_width; - - // compute right space for subitem - subitem_width = get_string_width(subitem_text); - subitem_width += gutter_width(); - - // draw the item left-justified + bool const item_invert(pitem.flags() & FLAG_INVERT); if (m_has_icons) - draw_icon(linenum, item(itemnum).ref(), effective_left, line_y); - ui().draw_text_full( - container(), - itemtext, - effective_left + icon_offset, line_y, effective_width - icon_offset - subitem_width, - text_layout::text_justify::LEFT, text_layout::word_wrapping::TRUNCATE, - mame_ui_manager::NORMAL, item_invert ? fgcolor3 : fgcolor, bgcolor, - &item_width, nullptr, - line_height()); - - // draw the subitem right-justified - ui().draw_text_full( - container(), - subitem_text, - effective_left + icon_offset + item_width, line_y, effective_width - icon_offset - item_width, - text_layout::text_justify::RIGHT, text_layout::word_wrapping::NEVER, - mame_ui_manager::NORMAL, item_invert ? fgcolor3 : fgcolor, bgcolor, - nullptr, nullptr, - line_height()); + draw_icon(linenum, pitem.ref(), item_text_left, linetop); + if (pitem.subtext().empty()) + { + // draw the item left-aligned + ui().draw_text_full( + container(), + itemtext, + item_text_left + icon_offset, linetop, item_text_width - icon_offset, + text_layout::text_justify::LEFT, text_layout::word_wrapping::TRUNCATE, + mame_ui_manager::NORMAL, item_invert ? fgcolor3 : fgcolor, bgcolor, + nullptr, nullptr, + line_height()); + } + else + { + // compute right space for subitem + std::string_view const subitem_text(pitem.subtext()); + float const subitem_width(get_string_width(subitem_text) + gutter_width()); + + // draw the item left-aligned + float item_width; + ui().draw_text_full( + container(), + itemtext, + item_text_left + icon_offset, linetop, item_text_width - icon_offset - subitem_width, + text_layout::text_justify::LEFT, text_layout::word_wrapping::TRUNCATE, + mame_ui_manager::NORMAL, item_invert ? fgcolor3 : fgcolor, bgcolor, + &item_width, nullptr, + line_height()); + + // draw the subitem right-aligned + ui().draw_text_full( + container(), + subitem_text, + item_text_left + icon_offset + item_width, linetop, item_text_width - icon_offset - item_width, + text_layout::text_justify::RIGHT, text_layout::word_wrapping::NEVER, + mame_ui_manager::NORMAL, item_invert ? fgcolor3 : fgcolor, bgcolor, + nullptr, nullptr, + line_height()); + } } } - for (size_t count = m_available_items; count < item_count(); count++) + // draw extra fixed items + for (size_t linenum = 0; linenum < m_skip_main_items; linenum++) { - const menu_item &pitem = item(count); - const std::string_view itemtext = pitem.text(); - float line_x0 = x1 + 0.5f * UI_LINE_WIDTH; - float line_y0 = line; - float line_x1 = x2 - 0.5f * UI_LINE_WIDTH; - float line_y1 = line + line_height(); + int const itemnum(m_available_items + linenum); + menu_item const &pitem(item(itemnum)); + std::string_view const itemtext(pitem.text()); + float const linetop(m_primary_items_top + (float(m_primary_lines + linenum) * line_height())); + float const linebottom(linetop + line_height()); + + // work out colours rgb_t fgcolor = ui().colors().text_color(); rgb_t bgcolor = ui().colors().text_bg_color(); - - if (mouse_in_rect(line_x0, line_y0, line_x1, line_y1) && is_selectable(pitem)) - set_hover(count); - - // if we're selected, draw with a different background - if (is_selected(count) && m_focus == focused_menu::MAIN) + bool const hovered(is_selectable(pitem) && pointer_in_rect(m_primary_items_hbounds.first, linetop, m_primary_items_hbounds.second, linebottom)); + bool const pointerline((pointer_action::MAIN_TRACK_LINE == m_pointer_action) && ((m_primary_lines + linenum) == m_clicked_line)); + if (is_selected(itemnum) && (get_focus() == focused_menu::MAIN)) { + // if we're selected, draw with a different background fgcolor = rgb_t(0xff, 0xff, 0x00); bgcolor = rgb_t(0xff, 0xff, 0xff); - ui().draw_textured_box(container(), line_x0 + 0.01f, line_y0, line_x1 - 0.01f, line_y1, bgcolor, rgb_t(43, 43, 43), + ui().draw_textured_box( + container(), + m_primary_items_hbounds.first, linetop, m_primary_items_hbounds.second, linebottom, + bgcolor, rgb_t(43, 43, 43), hilight_main_texture(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXWRAP(1)); } - // else if the mouse is over this item, draw with a different background - else if (count == hover()) + else if (pointerline && hovered) { - fgcolor = ui().options().mouseover_color(); + // draw selected highlight for tracked item + fgcolor = ui().colors().selected_color(); + bgcolor = ui().colors().selected_bg_color(); + highlight(m_primary_items_hbounds.first, linetop, m_primary_items_hbounds.second, linebottom, bgcolor); + } + else if (pointerline || (!m_ui_error && !(flags & PROCESS_NOINPUT) && hovered && pointer_idle())) + { + // draw hover highlight when hovered over or dragged off + fgcolor = ui().colors().mouseover_color(); bgcolor = ui().colors().mouseover_bg_color(); - highlight(line_x0, line_y0, line_x1, line_y1, bgcolor); + highlight(m_primary_items_hbounds.first, linetop, m_primary_items_hbounds.second, linebottom, bgcolor); } if (pitem.type() == menu_item_type::SEPARATOR) { + // if we're just a divider, draw a line container().add_line( - visible_left, line + 0.5f * line_height(), - visible_left + visible_width, line + 0.5f * line_height(), - UI_LINE_WIDTH, - ui().colors().text_color(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + m_primary_items_hbounds.first, linetop + (0.5F * line_height()), + m_primary_items_hbounds.second, linetop + (0.5F * line_height()), + UI_LINE_WIDTH, ui().colors().text_color(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); } else { + // draw the item centred ui().draw_text_full( container(), itemtext, - effective_left, line, effective_width, + item_text_left, linetop, item_text_width, text_layout::text_justify::CENTER, text_layout::word_wrapping::TRUNCATE, mame_ui_manager::NORMAL, fgcolor, bgcolor, nullptr, nullptr, line_height()); } - line += line_height(); } - x1 = x2; - x2 += right_panel_size; - - draw_right_panel(x1, y1, x2, y2); - - x1 = primary_left - lr_border(); - x2 = primary_left + primary_width + lr_border(); - // if there is something special to add, do it by calling the virtual method - custom_render(get_selection_ref(), get_customtop(), get_custombottom(), x1, y1, x2, y2); + custom_render( + flags, + get_selection_ref(), + get_customtop(), get_custombottom(), + lr_border(), m_primary_vbounds.first, 1.0F - lr_border(), m_primary_vbounds.second); - // return the number of visible lines, minus 1 for top arrow and 1 for bottom arrow - m_visible_items = m_visible_lines - (top_line != 0) - (top_line + m_visible_lines != m_available_items); - - // noinput - if (noinput) + // show error text if necessary + if (m_ui_error) { - int alpha = (1.0f - machine().options().pause_brightness()) * 255.0f; - if (alpha > 255) - alpha = 255; - if (alpha >= 0) - container().add_rect(0.0f, 0.0f, 1.0f, 1.0f, rgb_t(alpha, 0x00, 0x00, 0x00), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + container().add_rect(0.0F, 0.0F, 1.0F, 1.0F, rgb_t(114, 0, 0, 0), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + ui().draw_text_box(container(), m_error_text, text_layout::text_justify::CENTER, 0.5f, 0.5f, UI_RED_COLOR); } + + // return the number of visible lines, minus 1 for top arrow and 1 for bottom arrow + m_visible_items = m_visible_lines - (top_line != 0) - (top_line + m_visible_lines != m_available_items); } @@ -2217,116 +3394,197 @@ void menu_select_launch::draw(u32 flags) // draw right panel //------------------------------------------------- -void menu_select_launch::draw_right_panel(float origx1, float origy1, float origx2, float origy2) +void menu_select_launch::draw_right_panel(u32 flags) { - float const x2(!show_right_panel() ? origx2 : (origx1 + m_divider_width)); - - // set left-right arrows dimension - float const ar_x0(0.5F * (x2 + origx1 - m_divider_arrow_width)); - float const ar_y0(0.5F * (origy2 + origy1 - m_divider_arrow_height)); - float const ar_x1(ar_x0 + m_divider_arrow_width); - float const ar_y1(ar_y0 + m_divider_arrow_height); + if (show_right_panel()) + { + m_right_panel_width = 0.3F - m_divider_width; - ui().draw_outlined_box(container(), origx1, origy1, origx2, origy2, rgb_t(0xef, 0x12, 0x47, 0x7b)); + ui().draw_outlined_box( + container(), right_panel_left(), m_primary_vbounds.first, right_panel_right(), m_primary_vbounds.second, + ui().colors().background_color()); + draw_right_box_tabs(flags); - rgb_t fgcolor(ui().colors().text_color()); - if (mouse_in_rect(origx1, origy1, x2, origy2)) - { - fgcolor = ui().options().mouseover_color(); - set_hover(HOVER_RPANEL_ARROW); - } + if (0.0F >= m_right_heading_top) + { + float const pixelheight(target_size().second); + m_right_heading_top = std::round((m_right_tabs_bottom + UI_LINE_WIDTH + tb_border()) * pixelheight) / pixelheight; + m_right_content_vbounds = std::make_pair( + std::round((m_right_heading_top + line_height() + tb_border()) * pixelheight) / pixelheight, + m_primary_vbounds.second - tb_border()); + m_right_content_hbounds = std::make_pair(right_panel_left() + lr_border(), right_panel_right() - lr_border()); + } - if (!show_right_panel()) - { - draw_arrow(ar_x0, ar_y0, ar_x1, ar_y1, fgcolor, ROT90 ^ ORIENTATION_FLIP_X); + if (m_right_panel == RP_IMAGES) + arts_render(flags); + else + infos_render(flags); } else { - draw_arrow(ar_x0, ar_y0, ar_x1, ar_y1, fgcolor, ROT90); - origy1 = draw_right_box_title(x2, origy1, origx2, origy2); - - if (m_right_panel == RP_IMAGES) - arts_render(x2, origy1, origx2, origy2); - else - infos_render(x2, origy1, origx2, origy2); + m_right_panel_width = 0.0F; } + + draw_divider(flags, 1.0F - lr_border() - m_right_panel_width - m_divider_width, true); } //------------------------------------------------- -// draw right box title +// draw right box tabs //------------------------------------------------- -float menu_select_launch::draw_right_box_title(float x1, float y1, float x2, float y2) +void menu_select_launch::draw_right_box_tabs(u32 flags) { - float const midl = (x2 - x1) * 0.5f; + m_right_tabs_bottom = m_primary_vbounds.first + line_height(); - // add outlined box for options - ui().draw_outlined_box(container(), x1, y1, x2, y2, ui().colors().background_color()); + float const x1(1.0F - lr_border() - m_right_panel_width); + float const x2(1.0F - lr_border()); + float const tabwidth = right_tab_width(); - // add separator line - container().add_line(x1 + midl, y1, x1 + midl, y1 + line_height(), UI_LINE_WIDTH, ui().colors().border_color(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); - - std::string buffer[RP_LAST + 1] = { _(RIGHT_PANEL_NAMES[0].second), _(RIGHT_PANEL_NAMES[1].second) }; + std::string const buffer[RP_LAST + 1] = { _(RIGHT_PANEL_NAMES[0].second), _(RIGHT_PANEL_NAMES[1].second) }; // check size float text_size = 1.0f; for (auto & elem : buffer) { auto textlen = get_string_width(elem) + 0.01f; - float tmp_size = (textlen > midl) ? (midl / textlen) : 1.0f; + float tmp_size = (textlen > tabwidth) ? (tabwidth / textlen) : 1.0f; text_size = std::min(text_size, tmp_size); } for (int cells = RP_FIRST; cells <= RP_LAST; ++cells) { - rgb_t bgcolor = ui().colors().text_bg_color(); - rgb_t fgcolor = ui().colors().text_color(); - - if (mouse_in_rect(x1, y1, x1 + midl, y1 + line_height())) - { - if (m_right_panel != cells) - { - bgcolor = ui().colors().mouseover_bg_color(); - fgcolor = ui().options().mouseover_color(); - set_hover(HOVER_RP_FIRST + cells); - } - } - - if (m_right_panel != cells) - { - container().add_line(x1, y1 + line_height(), x1 + midl, y1 + line_height(), UI_LINE_WIDTH, - ui().colors().border_color(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); - if (fgcolor != ui().colors().mouseover_color()) - fgcolor = ui().colors().clone_color(); - } + float const tableft(x1 + (float(cells - RP_FIRST) * tabwidth)); - if (m_focus == focused_menu::RIGHTTOP && m_right_panel == cells) + rgb_t fgcolor = ui().colors().text_color(); + rgb_t bgcolor = ui().colors().text_bg_color(); + if ((focused_menu::RIGHTTOP == m_focus) && (cells == m_right_panel)) { + // draw primary highlight if keyboard focus is here fgcolor = rgb_t(0xff, 0xff, 0x00); bgcolor = rgb_t(0xff, 0xff, 0xff); ui().draw_textured_box( container(), - x1 + UI_LINE_WIDTH, y1 + UI_LINE_WIDTH, x1 + midl - UI_LINE_WIDTH, y1 + line_height(), + tableft + UI_LINE_WIDTH, m_primary_vbounds.first + UI_LINE_WIDTH, tableft + tabwidth - UI_LINE_WIDTH, m_right_tabs_bottom, bgcolor, rgb_t(43, 43, 43), hilight_main_texture(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXWRAP(1)); } - else if (bgcolor == ui().colors().mouseover_bg_color()) + else if (cells != m_right_panel) { - container().add_rect(x1 + UI_LINE_WIDTH, y1 + UI_LINE_WIDTH, x1 + midl - UI_LINE_WIDTH, y1 + line_height(), - bgcolor, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXWRAP(1)); + bool const hovered(pointer_in_rect(tableft, m_primary_vbounds.first, tableft + tabwidth, m_right_tabs_bottom)); + bool const pointertab((pointer_action::RIGHT_TRACK_TAB == m_pointer_action) && (cells == m_clicked_line)); + if (pointertab && hovered) + { + // draw selected highlight for tracked item + fgcolor = ui().colors().selected_color(); + bgcolor = ui().colors().selected_bg_color(); + highlight(tableft + UI_LINE_WIDTH, m_primary_vbounds.first + UI_LINE_WIDTH, tableft + tabwidth - UI_LINE_WIDTH, m_right_tabs_bottom, bgcolor); + } + else if (pointertab || (!m_ui_error && !(flags & PROCESS_NOINPUT) && hovered && pointer_idle())) + { + // draw hover highlight when hovered over or dragged off + fgcolor = ui().colors().mouseover_color(); + bgcolor = ui().colors().mouseover_bg_color(); + highlight(tableft + UI_LINE_WIDTH, m_primary_vbounds.first + UI_LINE_WIDTH, tableft + tabwidth - UI_LINE_WIDTH, m_right_tabs_bottom, bgcolor); + } + else + { + // dim unselected tab title + fgcolor = ui().colors().clone_color(); + } } ui().draw_text_full( container(), buffer[cells], - x1 + UI_LINE_WIDTH, y1, midl - UI_LINE_WIDTH, + tableft + UI_LINE_WIDTH, m_primary_vbounds.first, tabwidth - UI_LINE_WIDTH, text_layout::text_justify::CENTER, text_layout::word_wrapping::NEVER, mame_ui_manager::NORMAL, fgcolor, bgcolor, nullptr, nullptr, line_height() * text_size); - x1 += midl; + + // add lines when appropriate + if (RP_FIRST < cells) + { + container().add_line( + tableft, m_primary_vbounds.first, tableft, m_right_tabs_bottom, + UI_LINE_WIDTH, ui().colors().border_color(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + if (m_right_panel == cells) + { + container().add_line( + x1, m_primary_vbounds.first + line_height(), tableft, m_right_tabs_bottom, + UI_LINE_WIDTH, ui().colors().border_color(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + } + } } + if (RP_LAST != m_right_panel) + { + container().add_line( + x1 + (float(m_right_panel + 1) * tabwidth), m_right_tabs_bottom, x2, m_right_tabs_bottom, + UI_LINE_WIDTH, ui().colors().border_color(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + } +} + + +//------------------------------------------------- +// draw right box heading +//------------------------------------------------- + +void menu_select_launch::draw_right_box_heading(u32 flags, bool larrow, bool rarrow, std::string_view text) +{ + float const text_left(m_right_content_hbounds.first + (2.0F * lr_border()) + lr_arrow_width()); + float const text_width(m_right_content_hbounds.second - m_right_content_hbounds.first - (4.0F * lr_border()) - (2.0F * lr_arrow_width())); - return (y1 + line_height() + UI_LINE_WIDTH); + rgb_t fgcolor(ui().colors().text_color()); + rgb_t bgcolor(ui().colors().text_bg_color()); + if (pointer_action::RIGHT_SWITCH == m_pointer_action) + { + // touch swipe to switch + fgcolor = ui().colors().selected_color(); + bgcolor = ui().colors().selected_bg_color(); + highlight(m_right_content_hbounds.first, m_right_heading_top, m_right_content_hbounds.second, m_right_heading_top + line_height(), bgcolor); + } + else if (focused_menu::RIGHTBOTTOM == m_focus) + { + // keyboard focus + fgcolor = rgb_t(0xff, 0xff, 0x00); + bgcolor = rgb_t(0xff, 0xff, 0xff); + ui().draw_textured_box( + container(), + m_right_content_hbounds.first, m_right_heading_top, m_right_content_hbounds.second, m_right_heading_top + line_height(), + bgcolor, rgb_t(43, 43, 43), + hilight_main_texture(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXWRAP(1)); + } + + ui().draw_text_full(container(), + text, text_left, m_right_heading_top, text_width, + text_layout::text_justify::CENTER, text_layout::word_wrapping::TRUNCATE, mame_ui_manager::NORMAL, fgcolor, bgcolor, + nullptr, nullptr, + line_height()); + + if (larrow) + { + // not using the selected colour here because the background isn't changed + float const left(m_right_content_hbounds.first + lr_border()); + float const right(m_right_content_hbounds.first + lr_border() + lr_arrow_width()); + bool const hovered(pointer_in_rect(left, right_arrows_top(), right, right_arrows_bottom())); + bool const tracked((pointer_action::RIGHT_TRACK_ARROW == m_pointer_action) && !m_clicked_line); + rgb_t fg(fgcolor); + if ((focused_menu::RIGHTBOTTOM != m_focus) && (tracked || (!m_ui_error && !(flags & PROCESS_NOINPUT) && pointer_idle() && hovered))) + fg = ui().colors().mouseover_color(); + draw_arrow(left, right_arrows_top(), right, right_arrows_bottom(), fg, ROT90 ^ ORIENTATION_FLIP_X); + } + + if (rarrow) + { + // not using the selected colour here because the background isn't changed + float const left(m_right_content_hbounds.second - lr_border() - lr_arrow_width()); + float const right(m_right_content_hbounds.second - lr_border()); + bool const hovered(pointer_in_rect(left, right_arrows_top(), right, right_arrows_bottom())); + bool const tracked((pointer_action::RIGHT_TRACK_ARROW == m_pointer_action) && m_clicked_line); + rgb_t fg(fgcolor); + if ((focused_menu::RIGHTBOTTOM != m_focus) && (tracked || (!m_ui_error && !(flags & PROCESS_NOINPUT) && pointer_idle() && hovered))) + fg = ui().colors().mouseover_color(); + draw_arrow(left, right_arrows_top(), right, right_arrows_bottom(), fg, ROT90); + } } @@ -2334,8 +3592,11 @@ float menu_select_launch::draw_right_box_title(float x1, float y1, float x2, flo // perform our special rendering //------------------------------------------------- -void menu_select_launch::arts_render(float origx1, float origy1, float origx2, float origy2) +void menu_select_launch::arts_render(u32 flags) { + // draw the heading + draw_right_box_heading(flags, FIRST_VIEW < m_image_view, LAST_VIEW > m_image_view, _("selmenu-artwork", std::get<1>(ARTS_INFO[m_image_view]))); + ui_software_info const *software; ui_system_info const *system; get_selection(software, system); @@ -2344,13 +3605,10 @@ void menu_select_launch::arts_render(float origx1, float origy1, float origx2, f { m_cache.set_snapx_driver(nullptr); - // arts title and searchpath - std::string const searchstr = arts_render_common(origx1, origy1, origx2, origy2); - // loads the image if necessary if (!m_cache.snapx_software_is(software) || !snapx_valid() || m_switch_image) { - emu_file snapfile(searchstr.c_str(), OPEN_FLAG_READ); + emu_file snapfile(get_arts_searchpath(), OPEN_FLAG_READ); bitmap_argb32 tmp_bitmap; if (software->startempty == 1) @@ -2370,84 +3628,31 @@ void menu_select_launch::arts_render(float origx1, float origy1, float origx2, f m_cache.set_snapx_software(software); m_switch_image = false; - arts_render_images(std::move(tmp_bitmap), origx1, origy1, origx2, origy2); + arts_render_images(std::move(tmp_bitmap)); } // if the image is available, loaded and valid, display it - draw_snapx(origx1, origy1, origx2, origy2); + draw_snapx(); } else if (system) { m_cache.set_snapx_software(nullptr); - std::string const searchstr = arts_render_common(origx1, origy1, origx2, origy2); - // loads the image if necessary if (!m_cache.snapx_driver_is(system->driver) || !snapx_valid() || m_switch_image) { - emu_file snapfile(searchstr, OPEN_FLAG_READ); + emu_file snapfile(get_arts_searchpath(), OPEN_FLAG_READ); bitmap_argb32 tmp_bitmap; load_driver_image(tmp_bitmap, snapfile, *system->driver); m_cache.set_snapx_driver(system->driver); m_switch_image = false; - arts_render_images(std::move(tmp_bitmap), origx1, origy1, origx2, origy2); + arts_render_images(std::move(tmp_bitmap)); } // if the image is available, loaded and valid, display it - draw_snapx(origx1, origy1, origx2, origy2); - } -} - - -//------------------------------------------------- -// common function for images render -//------------------------------------------------- - -std::string menu_select_launch::arts_render_common(float origx1, float origy1, float origx2, float origy2) -{ - std::string snaptext, searchstr; - get_title_search(snaptext, searchstr); - - // apply title to right panel - float title_size = 0.0f; - for (int x = FIRST_VIEW; x < LAST_VIEW; x++) - { - float const text_length = get_text_width( - _("selmenu-artwork", std::get<1>(ARTS_INFO[m_image_view])), - origx1, origy1, origx2 - origx1, - text_layout::text_justify::CENTER, text_layout::word_wrapping::TRUNCATE); - title_size = (std::max)(text_length + 0.01f, title_size); + draw_snapx(); } - - rgb_t const fgcolor = (m_focus == focused_menu::RIGHTBOTTOM) ? rgb_t(0xff, 0xff, 0x00) : ui().colors().text_color(); - rgb_t const bgcolor = (m_focus == focused_menu::RIGHTBOTTOM) ? rgb_t(0xff, 0xff, 0xff) : ui().colors().text_bg_color(); - float const middle = origx2 - origx1; - - // check size - float const sc = title_size + 2.0f * gutter_width(); - float const tmp_size = (sc > middle) ? ((middle - 2.0f * gutter_width()) / sc) : 1.0f; - title_size *= tmp_size; - - if (bgcolor != ui().colors().text_bg_color()) - { - ui().draw_textured_box( - container(), - origx1 + ((middle - title_size) * 0.5f), origy1 + tb_border(), - origx1 + ((middle + title_size) * 0.5f), origy1 + tb_border() + line_height(), - bgcolor, rgb_t(43, 43, 43), - hilight_main_texture(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXWRAP(1)); - } - - ui().draw_text_full(container(), - snaptext, origx1, origy1 + tb_border(), origx2 - origx1, - text_layout::text_justify::CENTER, text_layout::word_wrapping::TRUNCATE, mame_ui_manager::NORMAL, fgcolor, bgcolor, - nullptr, nullptr, - line_height() * tmp_size); - - draw_common_arrow(origx1, origy1 + tb_border(), origx2, origy2, m_image_view, FIRST_VIEW, LAST_VIEW, title_size); - - return searchstr; } @@ -2455,12 +3660,11 @@ std::string menu_select_launch::arts_render_common(float origx1, float origy1, f // perform rendering of image //------------------------------------------------- -void menu_select_launch::arts_render_images(bitmap_argb32 &&tmp_bitmap, float origx1, float origy1, float origx2, float origy2) +void menu_select_launch::arts_render_images(bitmap_argb32 &&tmp_bitmap) { - bool no_available = false; - // if it fails, use the default image - if (!tmp_bitmap.valid()) + bool no_available(!tmp_bitmap.valid()); + if (no_available) { tmp_bitmap.allocate(256, 256); const bitmap_argb32 &src(m_cache.no_avail_bitmap()); @@ -2469,76 +3673,74 @@ void menu_select_launch::arts_render_images(bitmap_argb32 &&tmp_bitmap, float or for (int y = 0; y < 256; y++) tmp_bitmap.pix(y, x) = src.pix(y, x); } - no_available = true; } bitmap_argb32 &snapx_bitmap(m_cache.snapx_bitmap()); - if (tmp_bitmap.valid()) + if (!tmp_bitmap.valid()) { - float panel_width = origx2 - origx1 - 0.02f; - float panel_height = origy2 - origy1 - 0.02f - (3.0f * tb_border()) - (2.0f * line_height()); - int screen_width = machine().render().ui_target().width(); - int screen_height = machine().render().ui_target().height(); + snapx_bitmap.reset(); + return; + } + + float const panel_width(m_right_content_hbounds.second - m_right_content_hbounds.first); + float const panel_height(m_right_content_vbounds.second - m_right_content_vbounds.first); - if (machine().render().ui_target().orientation() & ORIENTATION_SWAP_XY) - std::swap(screen_height, screen_width); + auto [screen_width, screen_height] = target_size(); + if (machine().render().ui_target().orientation() & ORIENTATION_SWAP_XY) + { + using std::swap; + swap(screen_height, screen_width); + } - int panel_width_pixel = panel_width * screen_width; - int panel_height_pixel = panel_height * screen_height; + int const panel_width_pixel(panel_width * screen_width); + int const panel_height_pixel(panel_height * screen_height); - // Calculate resize ratios for resizing - auto ratioW = (float)panel_width_pixel / tmp_bitmap.width(); - auto ratioH = (float)panel_height_pixel / tmp_bitmap.height(); - auto ratioI = (float)tmp_bitmap.height() / tmp_bitmap.width(); - auto dest_xPixel = tmp_bitmap.width(); - auto dest_yPixel = tmp_bitmap.height(); + // Calculate resize ratios for resizing + auto const ratioW(float(panel_width_pixel) / float(tmp_bitmap.width())); + auto const ratioH(float(panel_height_pixel) / float(tmp_bitmap.height())); + auto const ratioI(float(tmp_bitmap.height()) / float(tmp_bitmap.width())); + auto dest_xPixel(tmp_bitmap.width()); + auto dest_yPixel(tmp_bitmap.height()); + if (ui().options().forced_4x3_snapshot() && (ratioI < 0.75F) && (m_image_view == SNAPSHOT_VIEW)) + { // force 4:3 ratio min - if (ui().options().forced_4x3_snapshot() && ratioI < 0.75f && m_image_view == SNAPSHOT_VIEW) - { - // smaller ratio will ensure that the image fits in the view - dest_yPixel = tmp_bitmap.width() * 0.75f; - ratioH = (float)panel_height_pixel / dest_yPixel; - float ratio = std::min(ratioW, ratioH); - dest_xPixel = tmp_bitmap.width() * ratio; - dest_yPixel *= ratio; - } + dest_yPixel = tmp_bitmap.width() * 0.75F; + float const ratio = std::min(ratioW, float(panel_height_pixel) / float(dest_yPixel)); + dest_xPixel = tmp_bitmap.width() * ratio; + dest_yPixel *= ratio; + } + else if ((ratioW < 1.0F) || (ratioH < 1.0F) || (ui().options().enlarge_snaps() && !no_available)) + { // resize the bitmap if necessary - else if (ratioW < 1 || ratioH < 1 || (ui().options().enlarge_snaps() && !no_available)) - { - // smaller ratio will ensure that the image fits in the view - float ratio = std::min(ratioW, ratioH); - dest_xPixel = tmp_bitmap.width() * ratio; - dest_yPixel = tmp_bitmap.height() * ratio; - } - - bitmap_argb32 dest_bitmap; - - // resample if necessary - if (dest_xPixel != tmp_bitmap.width() || dest_yPixel != tmp_bitmap.height()) - { - dest_bitmap.allocate(dest_xPixel, dest_yPixel); - render_color color = { 1.0f, 1.0f, 1.0f, 1.0f }; - render_resample_argb_bitmap_hq(dest_bitmap, tmp_bitmap, color, true); - } - else - dest_bitmap = std::move(tmp_bitmap); - - snapx_bitmap.allocate(panel_width_pixel, panel_height_pixel); - int x1 = (0.5f * panel_width_pixel) - (0.5f * dest_xPixel); - int y1 = (0.5f * panel_height_pixel) - (0.5f * dest_yPixel); + float const ratio(std::min(ratioW, ratioH)); + dest_xPixel = tmp_bitmap.width() * ratio; + dest_yPixel = tmp_bitmap.height() * ratio; + } - for (int x = 0; x < dest_xPixel; x++) - for (int y = 0; y < dest_yPixel; y++) - snapx_bitmap.pix(y + y1, x + x1) = dest_bitmap.pix(y, x); - // apply bitmap - m_cache.snapx_texture()->set_bitmap(snapx_bitmap, snapx_bitmap.cliprect(), TEXFORMAT_ARGB32); + // resample if necessary + bitmap_argb32 dest_bitmap; + if ((dest_xPixel != tmp_bitmap.width()) || (dest_yPixel != tmp_bitmap.height())) + { + dest_bitmap.allocate(dest_xPixel, dest_yPixel); + render_resample_argb_bitmap_hq(dest_bitmap, tmp_bitmap, render_color{ 1.0F, 1.0F, 1.0F, 1.0F }, true); } else { - snapx_bitmap.reset(); + dest_bitmap = std::move(tmp_bitmap); } + + snapx_bitmap.allocate(panel_width_pixel, panel_height_pixel); + int x1(0.5F * (float(panel_width_pixel) - float(dest_xPixel))); + int y1(0.5F * (float(panel_height_pixel) - float(dest_yPixel))); + + for (int x = 0; x < dest_xPixel; x++) + for (int y = 0; y < dest_yPixel; y++) + snapx_bitmap.pix(y + y1, x + x1) = dest_bitmap.pix(y, x); + + // apply bitmap + m_cache.snapx_texture()->set_bitmap(snapx_bitmap, snapx_bitmap.cliprect(), TEXFORMAT_ARGB32); } @@ -2546,18 +3748,14 @@ void menu_select_launch::arts_render_images(bitmap_argb32 &&tmp_bitmap, float or // draw snapshot //------------------------------------------------- -void menu_select_launch::draw_snapx(float origx1, float origy1, float origx2, float origy2) +void menu_select_launch::draw_snapx() { // if the image is available, loaded and valid, display it if (snapx_valid()) { - float const x1 = origx1 + 0.01f; - float const x2 = origx2 - 0.01f; - float const y1 = origy1 + (2.0f * tb_border()) + line_height(); - float const y2 = origy2 - tb_border() - line_height(); - - // apply texture - container().add_quad(x1, y1, x2, y2, rgb_t::white(), m_cache.snapx_texture(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + container().add_quad( + m_right_content_hbounds.first, m_right_content_vbounds.first, m_right_content_hbounds.second, m_right_content_vbounds.second, + rgb_t::white(), m_cache.snapx_texture(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); } } @@ -2721,37 +3919,10 @@ bool menu_select_launch::has_multiple_bios(game_driver const &driver, s_bios &bi //------------------------------------------------- -// draw collapsed left panel -//------------------------------------------------- - -float menu_select_launch::draw_collapsed_left_panel(float x1, float y1, float x2, float y2) -{ - // set left-right arrows dimension - float const ar_x0 = 0.5F * (x2 + x1 - m_divider_arrow_width); - float const ar_y0 = 0.5F * (y2 + y1 - m_divider_arrow_height); - float const ar_x1 = ar_x0 + m_divider_arrow_width; - float const ar_y1 = ar_y0 + m_divider_arrow_height; - - ui().draw_outlined_box(container(), x1, y1, x2, y2, rgb_t(0xef, 0x12, 0x47, 0x7b)); // FIXME: magic numbers in colour? - - rgb_t fgcolor = ui().colors().text_color(); - if (mouse_in_rect(x1, y1, x2, y2)) - { - fgcolor = ui().options().mouseover_color(); - set_hover(HOVER_LPANEL_ARROW); - } - - draw_arrow(ar_x0, ar_y0, ar_x1, ar_y1, fgcolor, ROT90); - - return x2 + lr_border(); -} - - -//------------------------------------------------- // draw infos //------------------------------------------------- -void menu_select_launch::infos_render(float origx1, float origy1, float origx2, float origy2) +void menu_select_launch::infos_render(u32 flags) { std::string_view first; ui_software_info const *software; @@ -2837,70 +4008,23 @@ void menu_select_launch::infos_render(float origx1, float origy1, float origx2, return; } - origy1 += tb_border(); - float const aspect(machine().render().ui_aspect(&container())); - float const gutter_width = 0.4f * line_height() * aspect * 1.3f; - float oy1 = origy1 + line_height(); - + // draw the heading std::string_view const snaptext(m_info_view ? std::string_view(m_items_list[m_info_view - 1]) : first); + draw_right_box_heading(flags, 0 < m_info_view, (total - 1) > m_info_view, snaptext); - // get width of widest title - float title_size(0.0f); - for (std::size_t x = 0; total > x; ++x) - { - std::string_view const name(x ? std::string_view(m_items_list[x - 1]) : first); - float txt_length = get_string_width(name); - txt_length += 0.01f; - title_size = (std::max)(txt_length, title_size); - } - - rgb_t fgcolor = ui().colors().text_color(); - rgb_t bgcolor = ui().colors().text_bg_color(); - if (get_focus() == focused_menu::RIGHTBOTTOM) - { - fgcolor = rgb_t(0xff, 0xff, 0xff, 0x00); - bgcolor = rgb_t(0xff, 0xff, 0xff, 0xff); - } - - float middle = origx2 - origx1; - - // check size - float sc = title_size + 2.0f * gutter_width; - float tmp_size = (sc > middle) ? ((middle - 2.0f * gutter_width) / sc) : 1.0f; - title_size *= tmp_size; - - if (bgcolor != ui().colors().text_bg_color()) - { - ui().draw_textured_box( - container(), - origx1 + ((middle - title_size) * 0.5f), origy1, origx1 + ((middle + title_size) * 0.5f), - origy1 + line_height(), bgcolor, rgb_t(255, 43, 43, 43), - hilight_main_texture(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXWRAP(1)); - } - - ui().draw_text_full( - container(), - snaptext, - origx1, origy1, origx2 - origx1, - text_layout::text_justify::CENTER, text_layout::word_wrapping::NEVER, - mame_ui_manager::NORMAL, fgcolor, bgcolor, nullptr, nullptr, - line_height() * tmp_size); - - sc = origx2 - origx1 - (2.0f * gutter_width); + float const sc(m_right_panel_width - (2.0F * gutter_width())); if (!m_info_layout || (m_info_layout->width() != sc)) { m_info_layout.emplace( *ui().get_font(), - machine().render().ui_aspect(&container()) * m_info_line_height, m_info_line_height, + x_aspect() * m_info_line_height, m_info_line_height, sc, text_layout::text_justify::LEFT, text_layout::word_wrapping::WORD); menu_dats_view::add_info_text(*m_info_layout, m_info_buffer, ui().colors().text_color()); m_total_lines = m_info_layout->lines(); } - draw_common_arrow(origx1, origy1, origx2, origy2, m_info_view, 0, total - 1, title_size); - - m_right_visible_lines = floor((origy2 - oy1) / m_info_line_height); + m_right_visible_lines = floor((m_right_content_vbounds.second - m_right_content_vbounds.first) / m_info_line_height); if (m_total_lines < m_right_visible_lines) m_right_visible_lines = m_total_lines; if (m_topline_datsview < 0) @@ -2909,22 +4033,19 @@ void menu_select_launch::infos_render(float origx1, float origy1, float origx2, m_topline_datsview = m_total_lines - m_right_visible_lines; // get the number of visible lines, minus 1 for top arrow and 1 for bottom arrow - bool const up_arrow = m_topline_datsview > 0; - bool const down_arrow = (m_topline_datsview + m_right_visible_lines) < m_total_lines; - int const r_visible_lines = m_right_visible_lines - (up_arrow ? 1 : 0) - (down_arrow ? 1 : 0); - - if (mouse_in_rect(origx1 + gutter_width, oy1, origx2 - gutter_width, origy2)) - set_hover(HOVER_INFO_TEXT); + bool const up_arrow(!info_at_top()); + bool const down_arrow(!info_at_bottom()); + int const r_visible_lines(m_right_visible_lines - (up_arrow ? 1 : 0) - (down_arrow ? 1 : 0)); if (up_arrow) - draw_info_arrow(0, origx1, origx2, oy1, m_info_line_height, m_info_line_height * aspect); + draw_info_arrow(flags, 0); if (down_arrow) - draw_info_arrow(1, origx1, origx2, oy1 + (float(m_right_visible_lines - 1) * m_info_line_height), m_info_line_height, m_info_line_height * aspect); + draw_info_arrow(flags, m_right_visible_lines - 1); m_info_layout->emit( container(), m_topline_datsview ? (m_topline_datsview + 1) : 0, r_visible_lines, - origx1 + gutter_width, oy1 + (m_topline_datsview ? m_info_line_height : 0.0f)); + right_panel_left() + gutter_width(), m_right_content_vbounds.first + (m_topline_datsview ? m_info_line_height : 0.0f)); } diff --git a/src/frontend/mame/ui/selmenu.h b/src/frontend/mame/ui/selmenu.h index 872901ff9b6..b6793994a1f 100644 --- a/src/frontend/mame/ui/selmenu.h +++ b/src/frontend/mame/ui/selmenu.h @@ -13,14 +13,19 @@ #pragma once #include "ui/menu.h" +#include "ui/utils.h" #include "audit.h" #include "lrucache.h" +#include <chrono> #include <map> #include <memory> #include <optional> +#include <string_view> +#include <tuple> +#include <utility> #include <vector> @@ -30,6 +35,14 @@ struct ui_software_info; namespace ui { +enum +{ + RP_FIRST = 0, + RP_IMAGES = RP_FIRST, + RP_INFOS, + RP_LAST = RP_INFOS +}; + class machine_static_info; class menu_select_launch : public menu @@ -113,9 +126,6 @@ protected: menu_select_launch(mame_ui_manager &mui, render_container &container, bool is_swlist); focused_menu get_focus() const { return m_focus; } - void set_focus(focused_menu focus) { m_focus = focus; } - bool next_image_view(); - bool previous_image_view(); bool dismiss_error(); void set_error(reset_options ropt, std::string &&message); @@ -127,7 +137,7 @@ protected: void launch_system(game_driver const &driver, ui_software_info const &swinfo, std::string const &part) { launch_system(ui(), driver, &swinfo, &part, nullptr); } virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; - virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; + virtual void custom_render(u32 flags, void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; virtual void menu_activated() override; virtual void menu_deactivated() override; @@ -137,15 +147,10 @@ protected: // draw arrow void draw_common_arrow(float origx1, float origy1, float origx2, float origy2, int current, int dmin, int dmax, float title); - void draw_info_arrow(int ub, float origx1, float origx2, float oy1, float line_height, float ud_arrow_width); - - bool draw_error_text(); + void draw_info_arrow(u32 flags, int line); template <typename Filter> - float draw_left_panel( - typename Filter::type current, - std::map<typename Filter::type, typename Filter::ptr> const &filters, - float x1, float y1, float x2, float y2); + void draw_left_panel(u32 flags, typename Filter::type current, std::map<typename Filter::type, typename Filter::ptr> const &filters); // icon helpers void check_for_icons(char const *listname); @@ -164,8 +169,11 @@ protected: return (uintptr_t(selected_ref) > m_skip_main_items) ? selected_ref : m_prev_selected; } + bool show_left_panel() const { return !(m_panels_status & HIDE_LEFT_PANEL); } + bool show_right_panel() const { return !(m_panels_status & HIDE_RIGHT_PANEL); } u8 right_panel() const { return m_right_panel; } u8 right_image() const { return m_image_view; } + char const *right_panel_config_string() const; char const *right_image_config_string() const; void set_right_panel(u8 index); @@ -189,11 +197,28 @@ protected: std::string m_search; private: + enum class pointer_action + { + NONE, + MAIN_TRACK_LINE, + MAIN_TRACK_RBUTTON, + MAIN_DRAG, + LEFT_TRACK_LINE, + LEFT_DRAG, + RIGHT_TRACK_TAB, + RIGHT_TRACK_ARROW, + RIGHT_TRACK_LINE, + RIGHT_SWITCH, + RIGHT_DRAG, + TOOLBAR_TRACK, + DIVIDER_TRACK + }; + using bitmap_vector = std::vector<bitmap_argb32>; using texture_ptr_vector = std::vector<texture_ptr>; using s_parts = std::unordered_map<std::string, std::string>; - using s_bios = std::vector<std::pair<std::string, int>>; + using s_bios = std::vector<std::pair<std::string, int> >; class software_parts; class bios_selection; @@ -242,22 +267,48 @@ private: using flags_cache = util::lru_cache_map<game_driver const *, system_flags>; - void reset_pressed() { m_pressed = false; m_repeat = 0; } - bool mouse_pressed() const { return (osd_ticks() >= m_repeat); } - void set_pressed(); + // various helpers for common calculations + bool main_at_top() const noexcept { return !top_line; } + bool main_at_bottom() const noexcept { return (top_line + m_primary_lines) >= m_available_items; } + bool is_main_up_arrow(int index) const noexcept { return !index && !main_at_top(); } + bool is_main_down_arrow(int index) const noexcept { return ((m_primary_lines - 1) == index) && !main_at_bottom(); } + bool left_at_top() const noexcept { return !m_left_visible_top; } + bool left_at_bottom() const noexcept { return (m_left_visible_top + m_left_visible_lines) >= m_left_item_count; } + bool is_left_up_arrow(int index) const noexcept { return !index && !left_at_top(); } + bool is_left_down_arrow(int index) const noexcept { return ((m_left_visible_lines - 1) == index) && !left_at_bottom(); } + bool info_at_top() const noexcept { return !m_topline_datsview; } + bool info_at_bottom() const noexcept { return (m_topline_datsview + m_right_visible_lines) >= m_total_lines; } + + // getting precalculated geometry + float left_panel_left() const noexcept { return lr_border(); } + float left_panel_right() const noexcept { return lr_border() + m_left_panel_width; } + float right_panel_left() const noexcept { return 1.0F - lr_border() - m_right_panel_width; } + float right_panel_right() const noexcept { return 1.0F - lr_border(); } + float right_tab_width() const noexcept { return m_right_panel_width / float(RP_LAST - RP_FIRST + 1); } + float right_arrows_top() const noexcept { return m_right_heading_top + (0.1F * line_height()); } + float right_arrows_bottom() const noexcept { return m_right_heading_top + (0.9F * line_height()); } + float left_divider_left() const noexcept { return lr_border() + m_left_panel_width; } + float left_divider_right() const noexcept { return lr_border() + m_left_panel_width + m_divider_width; } + float right_divider_left() const noexcept { return 1.0F - lr_border() - m_right_panel_width - m_divider_width; } + float right_divider_right() const noexcept { return 1.0F - lr_border() - m_right_panel_width; } bool snapx_valid() const { return m_cache.snapx_bitmap().valid(); } + void draw_divider(u32 flags, float x1, bool right); + // draw left panel - virtual float draw_left_panel(float x1, float y1, float x2, float y2) = 0; - float draw_collapsed_left_panel(float x1, float y1, float x2, float y2); + virtual void draw_left_panel(u32 flags) = 0; // draw infos - void infos_render(float x1, float y1, float x2, float y2); + void infos_render(u32 flags); void general_info(ui_system_info const *system, game_driver const &driver, std::string &buffer); // get selected software and/or driver virtual void get_selection(ui_software_info const *&software, ui_system_info const *&system) const = 0; + + // show configuration menu + virtual void show_config_menu(int index) = 0; + virtual bool accept_search() const { return true; } void select_prev() { @@ -277,38 +328,65 @@ private: } } } + void set_focus(focused_menu focus) { m_focus = focus; } void rotate_focus(int dir); - - void draw_toolbar(float x1, float y1, float x2, float y2); + std::pair<bool, bool> next_right_panel_view(); + std::pair<bool, bool> previous_right_panel_view(); + std::pair<bool, bool> next_image_view(); + std::pair<bool, bool> previous_image_view(); + std::pair<bool, bool> next_info_view(); + std::pair<bool, bool> previous_info_view(); + + void draw_toolbar(u32 flags, float x1, float y1, float x2, float y2); void draw_star(float x0, float y0); void draw_icon(int linenum, void *selectedref, float x1, float y1); virtual render_texture *get_icon_texture(int linenum, void *selectedref) = 0; - void get_title_search(std::string &title, std::string &search); + std::string get_arts_searchpath(); // event handling - virtual void handle_keys(u32 flags, int &iptkey) override; - virtual void handle_events(u32 flags, event &ev) override; + virtual bool handle_events(u32 flags, event &ev) override; + virtual bool handle_keys(u32 flags, int &iptkey) override; + virtual std::tuple<int, bool, bool> custom_pointer_updated(bool changed, ui_event const &uievt) override; + + // pointer handling helpers + std::tuple<int, bool, bool> handle_primary_down(bool changed, ui_event const &uievt); + std::tuple<int, bool, bool> handle_right_down(bool changed, ui_event const &uievt); + std::tuple<int, bool, bool> handle_middle_down(bool changed, ui_event const &uievt); + std::tuple<int, bool, bool> update_main_track_line(bool changed, ui_event const &uievt); + std::tuple<int, bool, bool> update_main_track_rbutton(bool changed, ui_event const &uievt); + std::tuple<int, bool, bool> update_main_drag(bool changed, ui_event const &uievt); + std::tuple<int, bool, bool> update_left_track_line(bool changed, ui_event const &uievt); + std::tuple<int, bool, bool> update_left_drag(bool changed, ui_event const &uievt); + std::tuple<int, bool, bool> update_right_track_tab(bool changed, ui_event const &uievt); + std::tuple<int, bool, bool> update_right_track_arrow(bool changed, ui_event const &uievt); + std::tuple<int, bool, bool> update_right_track_line(bool changed, ui_event const &uievt); + std::tuple<int, bool, bool> update_right_switch(bool changed, ui_event const &uievt); + std::tuple<int, bool, bool> update_right_drag(bool changed, ui_event const &uievt); + std::tuple<int, bool, bool> update_toolbar_track(bool changed, ui_event const &uievt); + std::tuple<int, bool, bool> update_divider_track(bool changed, ui_event const &uievt); + + bool main_force_visible_selection(); // draw game list virtual void draw(u32 flags) override; // draw right panel - void draw_right_panel(float origx1, float origy1, float origx2, float origy2); - float draw_right_box_title(float x1, float y1, float x2, float y2); + void draw_right_panel(u32 flags); + void draw_right_box_tabs(u32 flags); + void draw_right_box_heading(u32 flags, bool larrow, bool rarrow, std::string_view text); // images render - void arts_render(float origx1, float origy1, float origx2, float origy2); - std::string arts_render_common(float origx1, float origy1, float origx2, float origy2); - void arts_render_images(bitmap_argb32 &&bitmap, float origx1, float origy1, float origx2, float origy2); - void draw_snapx(float origx1, float origy1, float origx2, float origy2); + void arts_render(u32 flags); + void arts_render_images(bitmap_argb32 &&bitmap); + void draw_snapx(); // text for main top/bottom panels virtual void make_topbox_text(std::string &line0, std::string &line1, std::string &line2) const = 0; virtual std::string make_software_description(ui_software_info const &software, ui_system_info const *system) const = 0; // filter navigation - virtual void filter_selected() = 0; + virtual void filter_selected(int index) = 0; static void make_audit_fail_text(std::ostream &str, media_auditor const &auditor, media_auditor::summary summary); static void launch_system(mame_ui_manager &mui, game_driver const &driver, ui_software_info const *swinfo, std::string const *part, int const *bios); @@ -316,8 +394,20 @@ private: static bool has_multiple_bios(ui_software_info const &swinfo, s_bios &biosname); static bool has_multiple_bios(game_driver const &driver, s_bios &biosname); - bool show_left_panel() const; - bool show_right_panel() const; + bool check_scroll_repeat(float top, std::pair<float, float> hbounds, float height) + { + float const linetop(top + (float(m_clicked_line) * height)); + float const linebottom(top + (float(m_clicked_line + 1) * height)); + if (pointer_in_rect(hbounds.first, linetop, hbounds.second, linebottom)) + { + if (std::chrono::steady_clock::now() >= m_scroll_repeat) + { + m_scroll_repeat += std::chrono::milliseconds(100); + return true; + } + } + return false; + } bool m_ui_error; std::string m_error_text; @@ -339,11 +429,38 @@ private: cache &m_cache; bool m_is_swlist; focused_menu m_focus; - bool m_pressed; // mouse button held down - osd_ticks_t m_repeat; + pointer_action m_pointer_action; + std::chrono::steady_clock::time_point m_scroll_repeat; + std::pair<float, float> m_base_pointer; + std::pair<float, float> m_last_pointer; + int m_clicked_line; + + std::pair<float, float> m_primary_vbounds; + float m_primary_items_top; + std::pair<float, float> m_primary_items_hbounds; + int m_primary_lines; + + float m_left_panel_width; + std::pair<float, float> m_left_items_hbounds; + float m_left_items_top; + int m_left_item_count; + int m_left_visible_lines; + int m_left_visible_top; + + float m_right_panel_width; + float m_right_tabs_bottom; + float m_right_heading_top; + std::pair<float, float> m_right_content_vbounds; + std::pair<float, float> m_right_content_hbounds; int m_right_visible_lines; // right box lines + std::pair<float, float> m_toolbar_button_vbounds; + float m_toolbar_button_width; + float m_toolbar_button_spacing; + float m_toolbar_backtrack_left; + float m_toolbar_main_left; + u8 m_panels_status; u8 m_right_panel; bool m_has_icons; diff --git a/src/frontend/mame/ui/selsoft.cpp b/src/frontend/mame/ui/selsoft.cpp index bc8d3267585..5a0a4f1eb87 100644 --- a/src/frontend/mame/ui/selsoft.cpp +++ b/src/frontend/mame/ui/selsoft.cpp @@ -14,6 +14,7 @@ #include "ui/ui.h" #include "ui/icorender.h" #include "ui/inifile.h" +#include "ui/miscmenu.h" #include "ui/selector.h" #include "drivenum.h" @@ -444,68 +445,6 @@ bool menu_select_software::handle(event const *ev) changed = inkey_select(ev); break; - case IPT_UI_LEFT: - if (right_panel() == RP_IMAGES) - { - // Images - changed = previous_image_view(); - } - else if (right_panel() == RP_INFOS && ui_globals::cur_sw_dats_view > 0) - { - // Infos - ui_globals::cur_sw_dats_view--; - m_topline_datsview = 0; - changed = true; - } - break; - - case IPT_UI_RIGHT: - if (right_panel() == RP_IMAGES) - { - // Images - changed = next_image_view(); - } - else if (right_panel() == RP_INFOS && ui_globals::cur_sw_dats_view < (ui_globals::cur_sw_dats_total - 1)) - { - // Infos - ui_globals::cur_sw_dats_view++; - m_topline_datsview = 0; - changed = true; - } - break; - - case IPT_UI_UP: - if ((get_focus() == focused_menu::LEFT) && (software_filter::FIRST < m_filter_highlight)) - { - --m_filter_highlight; - changed = true; - } - break; - - case IPT_UI_DOWN: - if ((get_focus() == focused_menu::LEFT) && (software_filter::LAST > m_filter_highlight)) - { - ++m_filter_highlight; - changed = true; - } - break; - - case IPT_UI_HOME: - if (get_focus() == focused_menu::LEFT) - { - m_filter_highlight = software_filter::FIRST; - changed = true; - } - break; - - case IPT_UI_END: - if (get_focus() == focused_menu::LEFT) - { - m_filter_highlight = software_filter::LAST; - changed = true; - } - break; - case IPT_UI_DATS: inkey_dats(); break; @@ -538,8 +477,6 @@ bool menu_select_software::handle(event const *ev) } } - // if we're in an error state, overlay an error message - draw_error_text(); return changed; } @@ -552,7 +489,7 @@ void menu_select_software::recompute_metrics(uint32_t width, uint32_t height, fl menu_select_launch::recompute_metrics(width, height, aspect); // configure the custom rendering - set_custom_space(4.0F * line_height() + 5.0F * tb_border(), 4.0F * line_height() + 4.0F * tb_border()); + set_custom_space(4.0F * line_height() + 5.0F * tb_border(), 4.0F * line_height() + 3.0F * tb_border()); } //------------------------------------------------- @@ -704,9 +641,9 @@ bool menu_select_software::inkey_select(const event *menu_event) // draw left box //------------------------------------------------- -float menu_select_software::draw_left_panel(float x1, float y1, float x2, float y2) +void menu_select_software::draw_left_panel(u32 flags) { - return menu_select_launch::draw_left_panel<software_filter>(m_data->filter_type(), m_data->filters(), x1, y1, x2, y2); + return menu_select_launch::draw_left_panel<software_filter>(flags, m_data->filter_type(), m_data->filters()); } @@ -771,6 +708,12 @@ void menu_select_software::get_selection(ui_software_info const *&software, ui_s } +void menu_select_software::show_config_menu(int index) +{ + menu::stack_push<menu_machine_configure>(ui(), container(), m_system, nullptr); +} + + void menu_select_software::make_topbox_text(std::string &line0, std::string &line1, std::string &line2) const { // determine the text for the header @@ -794,29 +737,28 @@ std::string menu_select_software::make_software_description(ui_software_info con } -void menu_select_software::filter_selected() +void menu_select_software::filter_selected(int index) { - if ((software_filter::FIRST <= m_filter_highlight) && (software_filter::LAST >= m_filter_highlight)) - { - m_data->get_filter(software_filter::type(m_filter_highlight)).show_ui( - ui(), - container(), - [this] (software_filter &filter) + assert((software_filter::FIRST <= index) && (software_filter::LAST >= index)); + + m_data->get_filter(software_filter::type(index)).show_ui( + ui(), + container(), + [this] (software_filter &filter) + { + software_filter::type const new_type(filter.get_type()); + if (software_filter::CUSTOM == new_type) { - software_filter::type const new_type(filter.get_type()); - if (software_filter::CUSTOM == new_type) + emu_file file(ui().options().ui_path(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); + if (!file.open(util::string_format("custom_%s_filter.ini", m_system.driver->name))) { - emu_file file(ui().options().ui_path(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); - if (!file.open(util::string_format("custom_%s_filter.ini", m_system.driver->name))) - { - filter.save_ini(file, 0); - file.close(); - } + filter.save_ini(file, 0); + file.close(); } - m_data->set_filter_type(new_type); - reset(reset_options::REMEMBER_REF); - }); - } + } + m_data->set_filter_type(new_type); + reset(reset_options::REMEMBER_REF); + }); } } // namespace ui diff --git a/src/frontend/mame/ui/selsoft.h b/src/frontend/mame/ui/selsoft.h index 3b3074ddcdf..e250a608c08 100644 --- a/src/frontend/mame/ui/selsoft.h +++ b/src/frontend/mame/ui/selsoft.h @@ -48,18 +48,19 @@ private: virtual bool handle(event const *ev) override; // drawing - virtual float draw_left_panel(float x1, float y1, float x2, float y2) override; + virtual void draw_left_panel(u32 flags) override; virtual render_texture *get_icon_texture(int linenum, void *selectedref) override; // get selected software and/or driver virtual void get_selection(ui_software_info const *&software, ui_system_info const *&system) const override; + virtual void show_config_menu(int index) override; // text for main top/bottom panels virtual void make_topbox_text(std::string &line0, std::string &line1, std::string &line2) const override; virtual std::string make_software_description(ui_software_info const &software, ui_system_info const *system) const override; // filter navigation - virtual void filter_selected() override; + virtual void filter_selected(int index) override; // toolbar virtual void inkey_export() override { throw false; } diff --git a/src/frontend/mame/ui/simpleselgame.cpp b/src/frontend/mame/ui/simpleselgame.cpp index 569aeed400d..354a8ad2c87 100644 --- a/src/frontend/mame/ui/simpleselgame.cpp +++ b/src/frontend/mame/ui/simpleselgame.cpp @@ -27,6 +27,7 @@ #include "uiinput.h" #include <cctype> +#include <string_view> namespace ui { @@ -143,16 +144,6 @@ bool simple_menu_select_game::handle(event const *ev) inkey_special(*ev); break; } - - // if we're in an error state, overlay an error message - if (m_error) - { - ui().draw_text_box( - container(), - _("The selected game is missing one or more required ROM or CHD images. " - "Please select a different game.\n\nPress any key to continue."), - text_layout::text_justify::CENTER, 0.5f, 0.5f, UI_RED_COLOR); - } return changed; } @@ -300,7 +291,7 @@ void simple_menu_select_game::recompute_metrics(uint32_t width, uint32_t height, menu::recompute_metrics(width, height, aspect); // configure the custom rendering - set_custom_space(line_height() + 3.0f * tb_border(), 4.0f * line_height() + 3.0f * tb_border()); + set_custom_space(line_height() + 3.0f * tb_border(), 5.0f * line_height() + 3.0f * tb_border()); } @@ -308,11 +299,11 @@ void simple_menu_select_game::recompute_metrics(uint32_t width, uint32_t height, // custom_render - perform our special rendering //------------------------------------------------- -void simple_menu_select_game::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) +void simple_menu_select_game::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) { - // if no matches, display the error message if (m_nomatch) { + // if no matches, display the error message ui().draw_text_box( container(), string_format( @@ -326,104 +317,133 @@ void simple_menu_select_game::custom_render(void *selectedref, float top, float UI_RED_COLOR); return; } - - const game_driver *driver; - std::string tempbuf[5]; - - // display the current typeahead - if (!m_search.empty()) - tempbuf[0] = string_format(_("Type name or select: %1$s_"), m_search); else - tempbuf[0] = _("Type name or select: (random)"); - - // draw the top box - draw_text_box( - tempbuf, tempbuf + 1, - origx1, origx2, origy1 - top, origy1 - tb_border(), - text_layout::text_justify::CENTER, text_layout::word_wrapping::TRUNCATE, false, - ui().colors().text_color(), ui().colors().background_color()); - - // determine the text to render below - driver = ((uintptr_t)selectedref > 1) ? (const game_driver *)selectedref : nullptr; - if (driver) { - // first line is game name - tempbuf[0] = string_format(_("%1$-.100s"), driver->type.fullname()); - - // next line is year, manufacturer - tempbuf[1] = string_format(_("%1$s, %2$-.100s"), driver->year, driver->manufacturer); - - // next line source path - tempbuf[2] = string_format(_("Source file: %1$s"), info_xml_creator::format_sourcefile(driver->type.source())); + std::string tempbuf[5]; - // update cached values if selection changed - if (driver != m_cached_driver) + // display the current typeahead + if (!m_search.empty()) + tempbuf[0] = string_format(_("Type name or select: %1$s_"), m_search); + else + tempbuf[0] = _("Type name or select: (random)"); + + // draw the top box + draw_text_box( + tempbuf, tempbuf + 1, + origx1, origx2, origy1 - top, origy1 - tb_border(), + text_layout::text_justify::CENTER, text_layout::word_wrapping::TRUNCATE, false, + ui().colors().text_color(), ui().colors().background_color()); + + // determine the text to render below + game_driver const *const driver = (uintptr_t(selectedref) > 1) ? (const game_driver *)selectedref : nullptr; + if (driver) { - emu_options clean_options; - machine_static_info const info(ui().options(), machine_config(*driver, clean_options)); - m_cached_driver = driver; - m_cached_flags = info.machine_flags(); - m_cached_unemulated = info.unemulated_features(); - m_cached_imperfect = info.imperfect_features(); - m_cached_color = info.status_color(); - } + // first line is game name + tempbuf[0] = string_format(_("%1$-.100s"), driver->type.fullname()); - // next line is overall driver status - if (m_cached_flags & machine_flags::NOT_WORKING) - tempbuf[3] = _("Overall: NOT WORKING"); - else if ((m_cached_unemulated | m_cached_imperfect) & device_t::feature::PROTECTION) - tempbuf[3] = _("Overall: Unemulated Protection"); - else - tempbuf[3] = _("Overall: Working"); + // next line is year, manufacturer + tempbuf[1] = string_format(_("%1$s, %2$-.100s"), driver->year, driver->manufacturer); - // next line is graphics, sound status - if (m_cached_unemulated & device_t::feature::GRAPHICS) - tempbuf[4] = _("Graphics: Unimplemented, "); - else if ((m_cached_unemulated | m_cached_imperfect) & (device_t::feature::GRAPHICS | device_t::feature::PALETTE)) - tempbuf[4] = _("Graphics: Imperfect, "); - else - tempbuf[4] = _("Graphics: OK, "); - - if (m_cached_flags & machine_flags::NO_SOUND_HW) - tempbuf[4].append(_("Sound: None")); - else if (m_cached_unemulated & device_t::feature::SOUND) - tempbuf[4].append(_("Sound: Unimplemented")); - else if (m_cached_imperfect & device_t::feature::SOUND) - tempbuf[4].append(_("Sound: Imperfect")); - else - tempbuf[4].append(_("Sound: OK")); - } - else - { - const char *s = emulator_info::get_copyright(); - unsigned line = 0; + // next line source path + tempbuf[2] = string_format(_("Source file: %1$s"), info_xml_creator::format_sourcefile(driver->type.source())); + + // update cached values if selection changed + if (driver != m_cached_driver) + { + emu_options clean_options; + machine_static_info const info(ui().options(), machine_config(*driver, clean_options)); + m_cached_driver = driver; + m_cached_flags = info.machine_flags(); + m_cached_unemulated = info.unemulated_features(); + m_cached_imperfect = info.imperfect_features(); + m_cached_color = info.status_color(); + } - // first line is version string - tempbuf[line++] = string_format("%s %s", emulator_info::get_appname(), build_version); + // next line is overall driver status + if (m_cached_flags & machine_flags::NOT_WORKING) + tempbuf[3] = _("Overall: NOT WORKING"); + else if ((m_cached_unemulated | m_cached_imperfect) & device_t::feature::PROTECTION) + tempbuf[3] = _("Overall: Unemulated Protection"); + else + tempbuf[3] = _("Overall: Working"); - // output message - while (line < std::size(tempbuf)) + // next line is graphics, sound status + if (m_cached_unemulated & device_t::feature::GRAPHICS) + tempbuf[4] = _("Graphics: Unimplemented, "); + else if ((m_cached_unemulated | m_cached_imperfect) & (device_t::feature::GRAPHICS | device_t::feature::PALETTE)) + tempbuf[4] = _("Graphics: Imperfect, "); + else + tempbuf[4] = _("Graphics: OK, "); + + if (m_cached_flags & machine_flags::NO_SOUND_HW) + tempbuf[4].append(_("Sound: None")); + else if (m_cached_unemulated & device_t::feature::SOUND) + tempbuf[4].append(_("Sound: Unimplemented")); + else if (m_cached_imperfect & device_t::feature::SOUND) + tempbuf[4].append(_("Sound: Imperfect")); + else + tempbuf[4].append(_("Sound: OK")); + } + else { - if (!(*s == 0 || *s == '\n')) - tempbuf[line].push_back(*s); + std::string_view s = emulator_info::get_copyright(); + unsigned line = 0; + + // first line is version string + tempbuf[line++] = string_format("%s %s", emulator_info::get_appname(), build_version); - if (*s == '\n') + // output message + while (line < std::size(tempbuf)) { - line++; - s++; - } else if (*s != 0) - s++; - else - line++; + auto const found = s.find('\n'); + if (std::string::npos != found) + { + tempbuf[line++] = s.substr(0, found); + s.remove_prefix(found + 1); + } + else + { + tempbuf[line++] = s; + s = std::string_view(); + } + } } + + // draw the bottom box + draw_text_box( + std::begin(tempbuf), std::end(tempbuf), + origx1, origx2, origy2 + tb_border(), origy2 + bottom, + text_layout::text_justify::CENTER, text_layout::word_wrapping::TRUNCATE, true, + ui().colors().text_color(), driver ? m_cached_color : ui().colors().background_color()); + } + + // if we're in an error state, overlay an error message + if (m_error) + { + ui().draw_text_box( + container(), + _("The selected system is missing one or more required ROMs/disk images. " + "Please select a different system.\n\nPress any key to continue."), + text_layout::text_justify::CENTER, 0.5f, 0.5f, UI_RED_COLOR); } +} + - // draw the bottom box - draw_text_box( - tempbuf, tempbuf + 4, - origx1, origx2, origy2 + tb_border(), origy2 + bottom, - text_layout::text_justify::CENTER, text_layout::word_wrapping::TRUNCATE, true, - ui().colors().text_color(), driver ? m_cached_color : ui().colors().background_color()); +//------------------------------------------------- +// custom_pointer_updated - override pointer +// handling +//------------------------------------------------- + +std::tuple<int, bool, bool> simple_menu_select_game::custom_pointer_updated(bool changed, ui_event const &uievt) +{ + // only override mouse handling when error message is visible + if (!m_error || !uievt.pointer_buttons) + return std::make_tuple(IPT_INVALID, false, false); + + // primary click dismisses the message + if ((uievt.pointer_pressed & 0x01) && !(uievt.pointer_buttons & ~u32(0x01))) + m_error = false; + return std::make_tuple(IPT_INVALID, true, !m_error); } diff --git a/src/frontend/mame/ui/simpleselgame.h b/src/frontend/mame/ui/simpleselgame.h index f010bf14e8e..fcfe94e6109 100644 --- a/src/frontend/mame/ui/simpleselgame.h +++ b/src/frontend/mame/ui/simpleselgame.h @@ -15,8 +15,10 @@ #include "menu.h" + class driver_enumerator; + namespace ui { class simple_menu_select_game : public menu @@ -30,8 +32,9 @@ public: protected: virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; - virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; virtual bool custom_ui_back() override { return !m_search.empty(); } + virtual std::tuple<int, bool, bool> custom_pointer_updated(bool changed, ui_event const &uievt) override; private: enum { VISIBLE_GAMES_IN_LIST = 15 }; diff --git a/src/frontend/mame/ui/sliders.cpp b/src/frontend/mame/ui/sliders.cpp index b708b6af56a..1cbec4e3528 100644 --- a/src/frontend/mame/ui/sliders.cpp +++ b/src/frontend/mame/ui/sliders.cpp @@ -255,11 +255,10 @@ void menu_sliders::recompute_metrics(uint32_t width, uint32_t height, float aspe //------------------------------------------------- -// menu_sliders_custom_render - perform our special -// rendering +// custom_render - perform our special rendering //------------------------------------------------- -void menu_sliders::custom_render(void *selectedref, float top, float bottom, float x1, float y1, float x2, float y2) +void menu_sliders::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float x1, float y1, float x2, float y2) { const slider_state *curslider = (const slider_state *)selectedref; if (curslider != nullptr) diff --git a/src/frontend/mame/ui/sliders.h b/src/frontend/mame/ui/sliders.h index f9c0c69a176..b3c8ae3aca7 100644 --- a/src/frontend/mame/ui/sliders.h +++ b/src/frontend/mame/ui/sliders.h @@ -26,7 +26,7 @@ public: protected: virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; - virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; virtual void menu_activated() override; virtual void menu_deactivated() override; diff --git a/src/frontend/mame/ui/slotopt.cpp b/src/frontend/mame/ui/slotopt.cpp index bd92a2f9c9b..10fbeed03c6 100644 --- a/src/frontend/mame/ui/slotopt.cpp +++ b/src/frontend/mame/ui/slotopt.cpp @@ -222,7 +222,7 @@ void menu_slot_devices::recompute_metrics(uint32_t width, uint32_t height, float // custom_render - draw extra menu content //------------------------------------------------- -void menu_slot_devices::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) +void menu_slot_devices::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) { if (selectedref && (ITEMREF_RESET != selectedref)) { diff --git a/src/frontend/mame/ui/slotopt.h b/src/frontend/mame/ui/slotopt.h index af272dde8ed..91c78de7d1e 100644 --- a/src/frontend/mame/ui/slotopt.h +++ b/src/frontend/mame/ui/slotopt.h @@ -27,7 +27,7 @@ public: protected: virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; - virtual void custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) override; + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) override; private: enum class step_t diff --git a/src/frontend/mame/ui/state.cpp b/src/frontend/mame/ui/state.cpp index 0c39c89d9e6..efbc9e67e98 100644 --- a/src/frontend/mame/ui/state.cpp +++ b/src/frontend/mame/ui/state.cpp @@ -13,6 +13,7 @@ #include "emuopts.h" #include "inputdev.h" +#include "uiinput.h" #include "path.h" @@ -376,10 +377,11 @@ void menu_load_save_state_base::slot_selected(std::string &&name) // handle_keys - override key handling //------------------------------------------------- -void menu_load_save_state_base::handle_keys(uint32_t flags, int &iptkey) +bool menu_load_save_state_base::handle_keys(uint32_t flags, int &iptkey) { if (m_confirm_delete) { + bool updated(false); if (exclusive_input_pressed(iptkey, IPT_UI_SELECT, 0)) { // try to remove the file @@ -415,21 +417,40 @@ void menu_load_save_state_base::handle_keys(uint32_t flags, int &iptkey) m_confirm_prompt.clear(); m_confirm_delete = nullptr; m_keys_released = false; + updated = true; } iptkey = IPT_INVALID; + return updated; } else if (INPUT_CODE_INVALID != m_slot_selected) { iptkey = IPT_INVALID; + return false; } else { - menu::handle_keys(flags, iptkey); + return autopause_menu<>::handle_keys(flags, iptkey); } } //------------------------------------------------- +// custom_pointer_updated - override pointer +// handling +//------------------------------------------------- + +std::tuple<int, bool, bool> menu_load_save_state_base::custom_pointer_updated(bool changed, ui_event const &uievt) +{ + // suppress clicks on the menu while the delete prompt is visible + if (m_confirm_delete && uievt.pointer_buttons) + return std::make_tuple(IPT_INVALID, true, false); + else + return autopause_menu<>::custom_pointer_updated(changed, uievt); + +} + + +//------------------------------------------------- // recompute_metrics - recompute metrics //------------------------------------------------- @@ -446,7 +467,7 @@ void menu_load_save_state_base::recompute_metrics(uint32_t width, uint32_t heigh // custom_render - perform our special rendering //------------------------------------------------- -void menu_load_save_state_base::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) +void menu_load_save_state_base::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) { std::string_view text[2]; unsigned count(0U); diff --git a/src/frontend/mame/ui/state.h b/src/frontend/mame/ui/state.h index 8d02ab08881..1bbdd1396ae 100644 --- a/src/frontend/mame/ui/state.h +++ b/src/frontend/mame/ui/state.h @@ -17,6 +17,7 @@ #include "iptseqpoll.h" #include <chrono> +#include <tuple> #include <unordered_map> @@ -37,8 +38,9 @@ protected: bool one_shot); virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; - virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; - virtual void handle_keys(uint32_t flags, int &iptkey) override; + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; + virtual bool handle_keys(uint32_t flags, int &iptkey) override; + virtual std::tuple<int, bool, bool> custom_pointer_updated(bool changed, ui_event const &uievt) override; virtual void populate() override; virtual bool handle(event const *ev) override; diff --git a/src/frontend/mame/ui/submenu.cpp b/src/frontend/mame/ui/submenu.cpp index 188f6622c71..cce1f28eeb2 100644 --- a/src/frontend/mame/ui/submenu.cpp +++ b/src/frontend/mame/ui/submenu.cpp @@ -465,7 +465,7 @@ void submenu::recompute_metrics(uint32_t width, uint32_t height, float aspect) // perform our special rendering //------------------------------------------------- -void submenu::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) +void submenu::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) { if (selectedref) { diff --git a/src/frontend/mame/ui/submenu.h b/src/frontend/mame/ui/submenu.h index 87e92cc3d9d..80c836e67e8 100644 --- a/src/frontend/mame/ui/submenu.h +++ b/src/frontend/mame/ui/submenu.h @@ -61,7 +61,7 @@ public: protected: virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; - virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; private: virtual void populate() override; diff --git a/src/frontend/mame/ui/text.cpp b/src/frontend/mame/ui/text.cpp index be7105c1938..ffbddad418f 100644 --- a/src/frontend/mame/ui/text.cpp +++ b/src/frontend/mame/ui/text.cpp @@ -605,18 +605,22 @@ bool text_layout::hit_test(float x, float y, size_t &start, size_t &span) // restyle //------------------------------------------------- -void text_layout::restyle(size_t start, size_t span, rgb_t *fgcolor, rgb_t *bgcolor) +void text_layout::restyle(size_t start, size_t span, rgb_t const *fgcolor, rgb_t const *bgcolor) { for (const auto &line : m_lines) { for (size_t i = 0; i < line->character_count(); i++) { auto &ch = line->character(i); - if (ch.source.start >= start && ch.source.start + ch.source.span <= start + span) + if ((ch.source.start + ch.source.span) > (start + span)) { - if (fgcolor != nullptr) + return; + } + else if (ch.source.start >= start) + { + if (fgcolor) ch.style.fgcolor = *fgcolor; - if (bgcolor != nullptr) + if (bgcolor) ch.style.bgcolor = *bgcolor; } } diff --git a/src/frontend/mame/ui/text.h b/src/frontend/mame/ui/text.h index 0e19970c3b0..a9169faa186 100644 --- a/src/frontend/mame/ui/text.h +++ b/src/frontend/mame/ui/text.h @@ -66,7 +66,7 @@ public: bool empty() const { return m_lines.empty(); } size_t lines() const { return m_lines.size(); } bool hit_test(float x, float y, size_t &start, size_t &span); - void restyle(size_t start, size_t span, rgb_t *fgcolor, rgb_t *bgcolor); + void restyle(size_t start, size_t span, rgb_t const *fgcolor, rgb_t const *bgcolor); void emit(render_container &container, float x, float y); void emit(render_container &container, size_t start, size_t lines, float x, float y); void add_text(std::string_view text, rgb_t fgcolor = rgb_t::white(), rgb_t bgcolor = rgb_t::transparent(), float size = 1.0) diff --git a/src/frontend/mame/ui/textbox.cpp b/src/frontend/mame/ui/textbox.cpp index 065fca79101..33319047bd8 100644 --- a/src/frontend/mame/ui/textbox.cpp +++ b/src/frontend/mame/ui/textbox.cpp @@ -12,7 +12,8 @@ #include "textbox.h" #include "ui/ui.h" -#include "ui/utils.h" + +#include "uiinput.h" #include <string_view> #include <utility> @@ -80,11 +81,18 @@ void populate_three_column_layout(std::string_view text, T &&l, U &&c, V &&r) menu_textbox::menu_textbox(mame_ui_manager &mui, render_container &container) : menu(mui, container) , m_layout() + , m_line_bounds(0.0F, 0.0F) + , m_visible_top(0.0F) , m_layout_width(-1.0F) , m_desired_width(-1.0F) , m_desired_lines(-1) , m_window_lines(0) , m_top_line(0) + , m_pointer_action(pointer_action::NONE) + , m_scroll_repeat(std::chrono::steady_clock::time_point::min()) + , m_base_pointer(0.0F, 0.0F) + , m_last_pointer(0.0F, 0.0F) + , m_scroll_base(0) { } @@ -97,50 +105,129 @@ menu_textbox::~menu_textbox() void menu_textbox::reset_layout() { // force recompute and scroll to top - m_layout = std::nullopt; + m_layout.reset(); m_top_line = 0; + m_pointer_action = pointer_action::NONE; } -bool menu_textbox::handle_key(int key) +void menu_textbox::recompute_metrics(uint32_t width, uint32_t height, float aspect) { - switch (key) - { - case IPT_UI_UP: - --m_top_line; - return true; - - case IPT_UI_DOWN: - ++m_top_line; - return true; - - case IPT_UI_PAGE_UP: - m_top_line -= m_window_lines - 3; - return true; + menu::recompute_metrics(width, height, aspect); - case IPT_UI_PAGE_DOWN: - m_top_line += m_window_lines - 3; - return true; + m_layout.reset(); + m_pointer_action = pointer_action::NONE; +} - case IPT_UI_HOME: - m_top_line = 0; - return true; - case IPT_UI_END: - m_top_line = m_layout->lines() - m_window_lines; - return true; +std::tuple<int, bool, bool> menu_textbox::custom_pointer_updated(bool changed, ui_event const &uievt) +{ + // no pointer input if we don't have up-to-date content on-screen + if (!m_layout || (ui_event::type::POINTER_ABORT == uievt.event_type)) + { + m_pointer_action = pointer_action::NONE; + return std::make_tuple(IPT_INVALID, false, false); + } - default: - return false; + // if nothing's happening, check for clicks + if (pointer_idle()) + { + if ((uievt.pointer_pressed & 0x01) && !(uievt.pointer_buttons & ~u32(0x01))) + { + auto const [x, y] = pointer_location(); + if ((x >= m_line_bounds.first) && (x < m_line_bounds.second)) + { + if (m_top_line && pointer_in_line(y, 0)) + { + // scroll up arrow + --m_top_line; + m_pointer_action = pointer_action::SCROLL_UP; + m_scroll_repeat = std::chrono::steady_clock::now() + std::chrono::milliseconds(300); + m_last_pointer = std::make_pair(x, y); + return std::make_tuple(IPT_INVALID, true, true); + } + else if (((m_top_line + m_window_lines) < m_layout->lines()) && pointer_in_line(y, m_window_lines - 1)) + { + // scroll down arrow + ++m_top_line; + m_pointer_action = pointer_action::SCROLL_DOWN; + m_scroll_repeat = std::chrono::steady_clock::now() + std::chrono::milliseconds(300); + m_last_pointer = std::make_pair(x, y); + return std::make_tuple(IPT_INVALID, true, true); + } + else if ((2 == uievt.pointer_clicks) && pointer_in_line(y, m_window_lines + 1)) + { + // return to previous menu item + // FIXME: this should really use the start point of the multi-click action + m_pointer_action = pointer_action::CHECK_EXIT; + return std::make_tuple(IPT_INVALID, true, false); + } + else if ((ui_event::pointer::TOUCH == uievt.pointer_type) && (y >= m_visible_top) && (y < (m_visible_top + (float(m_window_lines) * line_height())))) + { + m_pointer_action = pointer_action::SCROLL_DRAG; + m_base_pointer = std::make_pair(x, y); + m_last_pointer = m_base_pointer; + m_scroll_base = m_top_line; + return std::make_tuple(IPT_INVALID, true, false); + } + } + } + return std::make_tuple(IPT_INVALID, false, false); } -} + // handle in-progress actions + switch (m_pointer_action) + { + case pointer_action::NONE: + break; -void menu_textbox::recompute_metrics(uint32_t width, uint32_t height, float aspect) -{ - menu::recompute_metrics(width, height, aspect); + case pointer_action::SCROLL_UP: + case pointer_action::SCROLL_DOWN: + { + // check for re-entry + bool redraw(false); + float const linetop(m_visible_top + ((pointer_action::SCROLL_DOWN == m_pointer_action) ? (float(m_window_lines - 1) * line_height()) : 0.0F)); + float const linebottom(linetop + line_height()); + auto const [x, y] = pointer_location(); + bool const reentered(reentered_rect(m_last_pointer.first, m_last_pointer.second, x, y, m_line_bounds.first, linetop, m_line_bounds.second, linebottom)); + if (reentered) + { + auto const now(std::chrono::steady_clock::now()); + if (scroll_if_expired(now)) + { + redraw = true; + m_scroll_repeat = now + std::chrono::milliseconds(100); + } + } + m_last_pointer = std::make_pair(x, y); + if ((uievt.pointer_released & 0x01) || (uievt.pointer_pressed & ~u32(0x01))) + m_pointer_action = pointer_action::NONE; + return std::make_tuple(IPT_INVALID, pointer_action::NONE != m_pointer_action, redraw); + } - m_layout = std::nullopt; + case pointer_action::SCROLL_DRAG: + { + // scroll if it moved + auto const newtop(drag_scroll( + pointer_location().second, m_base_pointer.second, m_last_pointer.second, -line_height(), + m_scroll_base, 0, int(m_layout->lines() - m_window_lines))); + bool const scrolled(newtop != m_top_line); + m_top_line = newtop; + + // catch the end of the gesture + if ((uievt.pointer_released & 0x01) || (uievt.pointer_pressed & ~u32(0x01))) + m_pointer_action = pointer_action::NONE; + return std::make_tuple(IPT_INVALID, pointer_action::NONE != m_pointer_action, scrolled); + } + + case pointer_action::CHECK_EXIT: + if (uievt.pointer_released & 0x01) + return std::make_tuple((2 == uievt.pointer_clicks) ? IPT_UI_SELECT : IPT_INVALID, false, false); + else if (uievt.pointer_buttons & ~u32(0x01)) + return std::make_tuple(IPT_INVALID, false, false); + return std::make_tuple(IPT_INVALID, true, false); + } + return std::make_tuple(IPT_INVALID, false, false); } @@ -151,6 +238,62 @@ bool menu_textbox::custom_mouse_scroll(int lines) } +bool menu_textbox::handle(event const *ev) +{ + // deal with repeating scroll arrows + bool scrolled(false); + if ((pointer_action::SCROLL_UP == m_pointer_action) || (pointer_action::SCROLL_DOWN == m_pointer_action)) + { + float const linetop(m_visible_top + ((pointer_action::SCROLL_DOWN == m_pointer_action) ? (float(m_window_lines - 1) * line_height()) : 0.0F)); + float const linebottom(linetop + line_height()); + if (pointer_in_rect(m_line_bounds.first, linetop, m_line_bounds.second, linebottom)) + { + while (scroll_if_expired(std::chrono::steady_clock::now())) + { + scrolled = true; + m_scroll_repeat += std::chrono::milliseconds(100); + } + } + } + + if (ev) + { + switch (ev->iptkey) + { + case IPT_UI_SELECT: + stack_pop(); + return true; + + case IPT_UI_UP: + --m_top_line; + return true; + + case IPT_UI_DOWN: + ++m_top_line; + return true; + + case IPT_UI_PAGE_UP: + m_top_line -= m_window_lines - 3; + return true; + + case IPT_UI_PAGE_DOWN: + m_top_line += m_window_lines - 3; + return true; + + case IPT_UI_HOME: + m_top_line = 0; + return true; + + case IPT_UI_END: + m_top_line = m_layout->lines() - m_window_lines; + return true; + } + } + + return scrolled; +} + + void menu_textbox::draw(uint32_t flags) { float const visible_width = 1.0F - (2.0F * lr_border()); @@ -162,10 +305,9 @@ void menu_textbox::draw(uint32_t flags) float const maximum_width = visible_width - (2.0F * gutter_width()); draw_background(); - map_mouse(); // account for extra space at the top and bottom and the separator/item for closing - float visible_main_menu_height = 1.0F - 2.0F * tb_border() - visible_extra_menu_height; + float visible_main_menu_height = 1.0F - (2.0F * tb_border()) - visible_extra_menu_height; m_window_lines = int(std::trunc(visible_main_menu_height / line_height())); // lay out the text if necessary @@ -179,7 +321,7 @@ void menu_textbox::draw(uint32_t flags) visible_main_menu_height = float(m_window_lines) * line_height(); // compute top/left of inner menu area by centering, if the menu is at the bottom of the extra, adjust - float const visible_top = ((1.0F - (visible_main_menu_height + visible_extra_menu_height)) * 0.5F) + get_customtop(); + m_visible_top = ((1.0F - (visible_main_menu_height + visible_extra_menu_height)) * 0.5F) + get_customtop(); // get width required to draw the sole menu item menu_item const &pitem = item(0); @@ -189,54 +331,68 @@ void menu_textbox::draw(uint32_t flags) // compute text box size float const x1 = visible_left + ((maximum_width - draw_width) * 0.5F); - float const y1 = visible_top - tb_border(); + float const y1 = m_visible_top - tb_border(); float const x2 = visible_left + visible_width - ((maximum_width - draw_width) * 0.5F); - float const y2 = visible_top + visible_main_menu_height + tb_border() + extra_height; + float const y2 = m_visible_top + visible_main_menu_height + tb_border() + extra_height; float const effective_left = x1 + gutter_width(); - float const line_x0 = x1 + 0.5F * UI_LINE_WIDTH; - float const line_x1 = x2 - 0.5F * UI_LINE_WIDTH; - float const separator = visible_top + float(m_window_lines) * line_height(); + m_line_bounds = std::make_pair(x1 + (0.5F * UI_LINE_WIDTH), x2 - (0.5F * UI_LINE_WIDTH)); + float const separator = m_visible_top + float(m_window_lines) * line_height(); ui().draw_outlined_box(container(), x1, y1, x2, y2, ui().colors().background_color()); - int const visible_items = m_layout->lines(); - m_window_lines = (std::min)(visible_items, m_window_lines); + int const desired_lines = m_layout->lines(); + int const drawn_lines = (std::min)(desired_lines, m_window_lines); m_top_line = (std::max)(0, m_top_line); - if (m_top_line + m_window_lines >= visible_items) - m_top_line = visible_items - m_window_lines; + if ((m_top_line + drawn_lines) >= desired_lines) + m_top_line = desired_lines - drawn_lines; - clear_hover(); if (m_top_line) { - // if we're on the top line, display the up arrow - rgb_t fgcolor = ui().colors().text_color(); - if (mouse_in_rect(line_x0, visible_top, line_x1, visible_top + line_height())) + // if we're not showing the top line, display the up arrow + rgb_t fgcolor(ui().colors().text_color()); + bool const hovered(pointer_in_rect(m_line_bounds.first, m_visible_top, m_line_bounds.second, m_visible_top + line_height())); + if (hovered && (pointer_action::SCROLL_UP == m_pointer_action)) + { + fgcolor = ui().colors().selected_color(); + highlight( + m_line_bounds.first, m_visible_top, + m_line_bounds.second, m_visible_top + line_height(), + ui().colors().selected_bg_color()); + } + else if ((hovered && pointer_idle()) || (pointer_action::SCROLL_UP == m_pointer_action)) { fgcolor = ui().colors().mouseover_color(); highlight( - line_x0, visible_top, - line_x1, visible_top + line_height(), + m_line_bounds.first, m_visible_top, + m_line_bounds.second, m_visible_top + line_height(), ui().colors().mouseover_bg_color()); - set_hover(HOVER_ARROW_UP); } draw_arrow( - 0.5F * (x1 + x2 - ud_arrow_width()), visible_top + (0.25F * line_height()), - 0.5F * (x1 + x2 + ud_arrow_width()), visible_top + (0.75F * line_height()), + 0.5F * (x1 + x2 - ud_arrow_width()), m_visible_top + (0.25F * line_height()), + 0.5F * (x1 + x2 + ud_arrow_width()), m_visible_top + (0.75F * line_height()), fgcolor, ROT0); } - if ((m_top_line + m_window_lines) < visible_items) + if ((m_top_line + m_window_lines) < desired_lines) { - // if we're on the bottom line, display the down arrow - float const line_y = visible_top + float(m_window_lines - 1) * line_height(); - rgb_t fgcolor = ui().colors().text_color(); - if (mouse_in_rect(line_x0, line_y, line_x1, line_y + line_height())) + // if we're not showing the bottom line, display the down arrow + float const line_y(m_visible_top + float(m_window_lines - 1) * line_height()); + rgb_t fgcolor(ui().colors().text_color()); + bool const hovered(pointer_in_rect(m_line_bounds.first, line_y, m_line_bounds.second, line_y + line_height())); + if (hovered && (pointer_action::SCROLL_DOWN == m_pointer_action)) + { + fgcolor = ui().colors().selected_color(); + highlight( + m_line_bounds.first, line_y, + m_line_bounds.second, line_y + line_height(), + ui().colors().selected_bg_color()); + } + else if ((hovered && pointer_idle()) || (pointer_action::SCROLL_DOWN == m_pointer_action)) { fgcolor = ui().colors().mouseover_color(); highlight( - line_x0, line_y, - line_x1, line_y + line_height(), + m_line_bounds.first, line_y, + m_line_bounds.second, line_y + line_height(), ui().colors().mouseover_bg_color()); - set_hover(HOVER_ARROW_DOWN); } draw_arrow( 0.5F * (x1 + x2 - ud_arrow_width()), line_y + (0.25F * line_height()), @@ -245,11 +401,11 @@ void menu_textbox::draw(uint32_t flags) } // draw visible lines, minus 1 for top arrow and 1 for bottom arrow - auto const text_lines = m_window_lines - (m_top_line ? 1 : 0) - (m_top_line + m_window_lines != visible_items); + auto const text_lines = drawn_lines - (m_top_line ? 1 : 0) - ((m_top_line + drawn_lines) != desired_lines); m_layout->emit( container(), m_top_line ? (m_top_line + 1) : 0, text_lines, - effective_left, visible_top + (m_top_line ? line_height() : 0.0F)); + effective_left, m_visible_top + (m_top_line ? line_height() : 0.0F)); // add visual separator before the "return to prevous menu" item container().add_line( @@ -257,13 +413,10 @@ void menu_textbox::draw(uint32_t flags) x2, separator + (0.5F * line_height()), UI_LINE_WIDTH, ui().colors().text_color(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); - float const line_y0 = separator + line_height(); + float const line_y0 = m_visible_top + float(m_window_lines + 1) * line_height(); float const line_y1 = line_y0 + line_height(); - if (mouse_in_rect(line_x0, line_y0, line_x1, line_y1) && is_selectable(pitem)) - set_hover(0); - - highlight(line_x0, line_y0, line_x1, line_y1, ui().colors().selected_bg_color()); + highlight(m_line_bounds.first, line_y0, m_line_bounds.second, line_y1, ui().colors().selected_bg_color()); ui().draw_text_full( container(), itemtext, effective_left, line_y0, draw_width, @@ -274,7 +427,37 @@ void menu_textbox::draw(uint32_t flags) line_height()); // if there is something special to add, do it by calling the virtual method - custom_render(get_selection_ref(), get_customtop(), get_custombottom(), x1, y1, x2, y2); + custom_render(flags, get_selection_ref(), get_customtop(), get_custombottom(), x1, y1, x2, y2); +} + + +bool menu_textbox::scroll_if_expired(std::chrono::steady_clock::time_point now) +{ + if (now < m_scroll_repeat) + return false; + + if (pointer_action::SCROLL_DOWN == m_pointer_action) + { + if ((m_top_line + m_window_lines) < m_layout->lines()) + ++m_top_line; + if ((m_top_line + m_window_lines) == m_layout->lines()) + m_pointer_action = pointer_action::NONE; + } + else + { + if (0 < m_top_line) + --m_top_line; + if (!m_top_line) + m_pointer_action = pointer_action::NONE; + } + return true; +} + + +inline bool menu_textbox::pointer_in_line(float y, int line) const +{ + float const top(m_visible_top + (float(line) * line_height())); + return (top <= y) && ((top + line_height()) > y); } @@ -309,7 +492,7 @@ void menu_fixed_textbox::recompute_metrics(uint32_t width, uint32_t height, floa } -void menu_fixed_textbox::custom_render(void *selectedref, float top, float bottom, float x1, float y1, float x2, float y2) +void menu_fixed_textbox::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float x1, float y1, float x2, float y2) { std::string_view const toptext[] = { m_heading }; draw_text_box( @@ -367,10 +550,4 @@ void menu_fixed_textbox::populate() { } - -bool menu_fixed_textbox::handle(event const *ev) -{ - return ev && handle_key(ev->iptkey); -} - } // namespace ui diff --git a/src/frontend/mame/ui/textbox.h b/src/frontend/mame/ui/textbox.h index af755761d4b..3a123980c83 100644 --- a/src/frontend/mame/ui/textbox.h +++ b/src/frontend/mame/ui/textbox.h @@ -13,8 +13,11 @@ #include "ui/menu.h" #include "ui/text.h" +#include <chrono> #include <optional> #include <string> +#include <tuple> +#include <utility> namespace ui { @@ -28,22 +31,44 @@ protected: menu_textbox(mame_ui_manager &mui, render_container &container); void reset_layout(); - bool handle_key(int key); virtual void populate_text(std::optional<text_layout> &layout, float &width, int &lines) = 0; virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; + virtual std::tuple<int, bool, bool> custom_pointer_updated(bool changed, ui_event const &uievt) override; virtual bool custom_mouse_scroll(int lines) override; + virtual bool handle(event const *ev) override; + private: + enum class pointer_action + { + NONE, + SCROLL_UP, + SCROLL_DOWN, + SCROLL_DRAG, + CHECK_EXIT + }; + virtual void draw(uint32_t flags) override; + bool scroll_if_expired(std::chrono::steady_clock::time_point now); + bool pointer_in_line(float y, int line) const; + std::optional<text_layout> m_layout; + std::pair<float, float> m_line_bounds; + float m_visible_top; float m_layout_width; float m_desired_width; int m_desired_lines; int m_window_lines; int m_top_line; + + pointer_action m_pointer_action; + std::chrono::steady_clock::time_point m_scroll_repeat; + std::pair<float, float> m_base_pointer; + std::pair<float, float> m_last_pointer; + int m_scroll_base; }; @@ -59,13 +84,12 @@ public: protected: virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; - virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; virtual void populate_text(std::optional<text_layout> &layout, float &width, int &lines) override; private: virtual void populate() override; - virtual bool handle(event const *ev) override; std::string const m_heading; std::string const m_content; diff --git a/src/frontend/mame/ui/ui.cpp b/src/frontend/mame/ui/ui.cpp index 09a8d24bdd1..a9a4fd5ceed 100644 --- a/src/frontend/mame/ui/ui.cpp +++ b/src/frontend/mame/ui/ui.cpp @@ -153,6 +153,34 @@ static uint32_t const mouse_bitmap[32*32] = }; +enum class mame_ui_manager::ui_callback_type : int +{ + NOINPUT, + GENERAL, + MODAL, + MENU, + CUSTOM +}; + + +struct mame_ui_manager::active_pointer +{ + active_pointer(ui_event const &event) : target(event.target), type(event.pointer_type), ptrid(event.pointer_id), x(-1.0F), y(-1.0F) + { + } + + bool operator<(std::pair<render_target *, u16> const &val) const noexcept + { + return std::make_pair(target, ptrid) < val; + } + + render_target *target; + osd::ui_event_handler::pointer type; + u16 ptrid; + float x, y; +}; + + //------------------------------------------------- // ctor - set up the user interface //------------------------------------------------- @@ -161,7 +189,7 @@ mame_ui_manager::mame_ui_manager(running_machine &machine) : ui_manager(machine) , m_font() , m_handler_callback() - , m_handler_callback_type(ui_callback_type::GENERAL) + , m_handler_callback_type(ui_callback_type::NOINPUT) , m_ui_active(true) , m_single_step(false) , m_showfps(false) @@ -171,8 +199,8 @@ mame_ui_manager::mame_ui_manager(running_machine &machine) , m_mouse_bitmap(32, 32) , m_mouse_arrow_texture(nullptr) , m_mouse_show(false) - , m_mouse_target(-1) - , m_mouse_position(0, 0) + , m_update_pointers(false) + , m_pointers_changed(false) , m_target_font_height(0) , m_has_warnings(false) , m_unthrottle_mute(false) @@ -182,7 +210,8 @@ mame_ui_manager::mame_ui_manager(running_machine &machine) , m_imperfect_features() , m_last_launch_time(std::time_t(-1)) , m_last_warning_time(std::time_t(-1)) -{ } +{ +} mame_ui_manager::~mame_ui_manager() { @@ -203,7 +232,7 @@ void mame_ui_manager::init() // more initialization set_handler( - ui_callback_type::GENERAL, + ui_callback_type::NOINPUT, handler_callback_func( [this] (render_container &container) -> uint32_t { @@ -214,6 +243,7 @@ void mame_ui_manager::init() m_mouse_show = machine().system().flags & machine_flags::CLICKABLE_ARTWORK ? true : false; // request notification callbacks + machine().add_notifier(MACHINE_NOTIFY_FRAME, machine_notify_delegate(&mame_ui_manager::frame_update, this)); machine().add_notifier(MACHINE_NOTIFY_EXIT, machine_notify_delegate(&mame_ui_manager::exit, this)); machine().configuration().config_register( "ui_warnings", @@ -239,6 +269,25 @@ void mame_ui_manager::update_target_font_height() //------------------------------------------------- +// exit - called for each emulated frame +//------------------------------------------------- + +void mame_ui_manager::frame_update() +{ + // this hackery is needed to ensure natural keyboard and clickable artwork input is in sync with I/O ports + if (ui_callback_type::GENERAL == m_handler_callback_type) + { + process_ui_events(); + for (auto *target = machine().render().first_target(); target; target = target->next()) + { + if (!target->hidden()) + target->update_pointer_fields(); + } + } +} + + +//------------------------------------------------- // exit - clean up ourselves on exit //------------------------------------------------- @@ -366,6 +415,12 @@ void mame_ui_manager::initialize(running_machine &machine) void mame_ui_manager::set_handler(ui_callback_type callback_type, handler_callback_func &&callback) { + m_active_pointers.clear(); + if (!m_display_pointers.empty()) + { + m_display_pointers.clear(); + m_pointers_changed = true; + } m_handler_callback = std::move(callback); m_handler_callback_type = callback_type; } @@ -625,7 +680,11 @@ void mame_ui_manager::set_startup_text(const char *text, bool force) bool mame_ui_manager::update_and_render(render_container &container) { // always start clean - container.empty(); + for (auto &target : machine().render().targets()) + { + if (target.ui_container()) + target.ui_container()->empty(); + } // if we're paused, dim the whole screen if (machine().phase() >= machine_phase::RESET && (single_step() || machine().paused())) @@ -657,7 +716,12 @@ bool mame_ui_manager::update_and_render(render_container &container) if (machine().phase() >= machine_phase::RESET) mame_machine_manager::instance()->cheat().render_text(*this, container); + // draw the FPS counter if it should be visible + if (show_fps_counter()) + draw_fps_counter(container); + // call the current UI handler + machine().ui_input().check_ui_inputs(); uint32_t const handler_result = m_handler_callback(container); // display any popup messages @@ -666,43 +730,34 @@ bool mame_ui_manager::update_and_render(render_container &container) else m_popup_text_end = 0; - // display the internal mouse cursor - bool mouse_moved = false; + // display the internal pointers + bool const pointer_update = m_pointers_changed; + m_pointers_changed = false; if (m_mouse_show || (is_menu_active() && machine().options().ui_mouse())) { - int32_t mouse_target_x, mouse_target_y; - bool mouse_button; - render_target *const mouse_target = machine().ui_input().find_mouse(&mouse_target_x, &mouse_target_y, &mouse_button); - - float mouse_y = -1, mouse_x = -1; - if (mouse_target && mouse_target->map_point_container(mouse_target_x, mouse_target_y, container, mouse_x, mouse_y)) + const float cursor_size = 0.6 * get_line_height(); + for (auto const &pointer : m_display_pointers) { - const float cursor_size = 0.6 * get_line_height(); - container.add_quad(mouse_x, mouse_y, mouse_x + cursor_size * container.manager().ui_aspect(&container), mouse_y + cursor_size, colors().text_color(), m_mouse_arrow_texture, PRIMFLAG_ANTIALIAS(1) | PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); - if ((m_mouse_target != mouse_target->index()) || (std::make_pair(mouse_x, mouse_y) != m_mouse_position)) - { - m_mouse_target = mouse_target->index(); - m_mouse_position = std::make_pair(mouse_x, mouse_y); - mouse_moved = true; - } + render_container &container = *pointer.target.get().ui_container(); + container.add_quad( + pointer.x, + pointer.y, + pointer.x + cursor_size * container.manager().ui_aspect(&container), + pointer.y + cursor_size, + rgb_t::white(), + m_mouse_arrow_texture, + PRIMFLAG_ANTIALIAS(1) | PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); } - else if (0 <= m_mouse_target) - { - m_mouse_target = -1; - mouse_moved = true; - } - } - else if (0 <= m_mouse_target) - { - m_mouse_target = -1; - mouse_moved = true; } // cancel takes us back to the in-game handler if (handler_result & HANDLER_CANCEL) + { + machine().ui_input().reset(); set_handler(ui_callback_type::GENERAL, handler_callback_func(&mame_ui_manager::handler_ingame, this)); + } - return mouse_moved || (handler_result & HANDLER_UPDATE); + return pointer_update || (handler_result & HANDLER_UPDATE); } @@ -954,7 +1009,7 @@ void mame_ui_manager::draw_message_window(render_container &container, std::stri void mame_ui_manager::show_fps_temp(double seconds) { if (!m_showfps) - m_showfps_end = osd_ticks() + seconds * osd_ticks_per_second(); + m_showfps_end = std::max<osd_ticks_t>(osd_ticks() + seconds * osd_ticks_per_second(), m_showfps_end); } @@ -966,10 +1021,7 @@ void mame_ui_manager::set_show_fps(bool show) { m_showfps = show; if (!show) - { - m_showfps = 0; m_showfps_end = 0; - } } @@ -990,7 +1042,7 @@ bool mame_ui_manager::show_fps() const bool mame_ui_manager::show_fps_counter() { - bool result = m_showfps || osd_ticks() < m_showfps_end; + bool const result = m_showfps || (osd_ticks() < m_showfps_end); if (!result) m_showfps_end = 0; return result; @@ -1025,17 +1077,13 @@ bool mame_ui_manager::show_profiler() const void mame_ui_manager::show_menu() { - set_handler(ui_callback_type::MENU, ui::menu::get_ui_handler(*this)); -} - - -//------------------------------------------------- -// show_mouse - change mouse status -//------------------------------------------------- + for (auto *target = machine().render().first_target(); target; target = target->next()) + { + if (!target->hidden()) + target->forget_pointers(); + } -void mame_ui_manager::show_mouse(bool status) -{ - m_mouse_show = status; + set_handler(ui_callback_type::MENU, ui::menu::get_ui_handler(*this)); } @@ -1044,10 +1092,9 @@ void mame_ui_manager::show_mouse(bool status) // UI handler is active //------------------------------------------------- -bool mame_ui_manager::is_menu_active(void) +bool mame_ui_manager::is_menu_active() { - return m_handler_callback_type == ui_callback_type::MENU - || m_handler_callback_type == ui_callback_type::VIEWER; + return m_handler_callback_type == ui_callback_type::MENU; } @@ -1057,48 +1104,119 @@ bool mame_ui_manager::is_menu_active(void) ***************************************************************************/ //------------------------------------------------- -// process_natural_keyboard - processes any -// natural keyboard input +// process_ui_events - processes queued UI input +// events //------------------------------------------------- -void mame_ui_manager::process_natural_keyboard() +void mame_ui_manager::process_ui_events() { + // process UI events + bool const use_natkbd(machine().natkeyboard().in_use() && (machine().phase() == machine_phase::RUNNING)); ui_event event; - - // loop while we have interesting events while (machine().ui_input().pop_event(&event)) { - // if this was a UI_EVENT_CHAR event, post it - if (event.event_type == ui_event::type::IME_CHAR) - machine().natkeyboard().post_char(event.ch); - } + switch (event.event_type) + { + case ui_event::type::NONE: + case ui_event::type::WINDOW_FOCUS: + case ui_event::type::WINDOW_DEFOCUS: + case ui_event::type::MOUSE_WHEEL: + break; - // process natural keyboard keys that don't get UI_EVENT_CHARs - for (int i = 0; i < std::size(non_char_keys); i++) - { - // identify this keycode - input_item_id itemid = non_char_keys[i]; - input_code code = machine().input().code_from_itemid(itemid); + case ui_event::type::POINTER_UPDATE: + if (event.target) + { + if (osd::ui_event_handler::pointer::TOUCH != event.pointer_type) + { + m_update_pointers = true; + auto pos(std::lower_bound(m_active_pointers.begin(), m_active_pointers.end(), std::make_pair(event.target, event.pointer_id))); + if ((m_active_pointers.end() == pos) || (pos->target != event.target) || (pos->ptrid != event.pointer_id)) + pos = m_active_pointers.emplace(pos, event); + else + assert(pos->type == event.pointer_type); + event.target->map_point_container(event.pointer_x, event.pointer_y, *event.target->ui_container(), pos->x, pos->y); + } - // ...and determine if it is pressed - bool pressed = machine().input().code_pressed(code); + event.target->pointer_updated( + event.pointer_type, event.pointer_id, event.pointer_device, + event.pointer_x, event.pointer_y, + event.pointer_buttons, event.pointer_pressed, event.pointer_released, + event.pointer_clicks); + } + break; - // figure out whey we are in the key_down map - uint8_t *key_down_ptr = &m_non_char_keys_down[i / 8]; - uint8_t key_down_mask = 1 << (i % 8); + case ui_event::type::POINTER_LEAVE: + if (event.target) + { + auto const pos(std::lower_bound(m_active_pointers.begin(), m_active_pointers.end(), std::make_pair(event.target, event.pointer_id))); + if (m_active_pointers.end() != pos) + { + m_update_pointers = true; + m_active_pointers.erase(pos); + } - if (pressed && !(*key_down_ptr & key_down_mask)) - { - // this key is now down - *key_down_ptr |= key_down_mask; + event.target->pointer_left( + event.pointer_type, event.pointer_id, event.pointer_device, + event.pointer_x, event.pointer_y, + event.pointer_released, + event.pointer_clicks); + } + break; + + case ui_event::type::POINTER_ABORT: + if (event.target) + { + auto const pos(std::lower_bound(m_active_pointers.begin(), m_active_pointers.end(), std::make_pair(event.target, event.pointer_id))); + if (m_active_pointers.end() != pos) + { + m_update_pointers = true; + m_active_pointers.erase(pos); + } - // post the key - machine().natkeyboard().post_char(UCHAR_MAMEKEY_BEGIN + code.item_id()); + event.target->pointer_aborted( + event.pointer_type, event.pointer_id, event.pointer_device, + event.pointer_x, event.pointer_y, + event.pointer_released, + event.pointer_clicks); + } + break; + + case ui_event::type::IME_CHAR: + if (use_natkbd) + machine().natkeyboard().post_char(event.ch); + break; } - else if (!pressed && (*key_down_ptr & key_down_mask)) + } + + // process natural keyboard keys that don't get IME text input events + if (use_natkbd) + { + for (int i = 0; i < std::size(non_char_keys); i++) { - // this key is now up - *key_down_ptr &= ~key_down_mask; + // identify this keycode + input_item_id itemid = non_char_keys[i]; + input_code code = machine().input().code_from_itemid(itemid); + + // ...and determine if it is pressed + bool pressed = machine().input().code_pressed(code); + + // figure out whey we are in the key_down map + uint8_t *key_down_ptr = &m_non_char_keys_down[i / 8]; + uint8_t key_down_mask = 1 << (i % 8); + + if (pressed && !(*key_down_ptr & key_down_mask)) + { + // this key is now down + *key_down_ptr |= key_down_mask; + + // post the key + machine().natkeyboard().post_char(UCHAR_MAMEKEY_BEGIN + code.item_id()); + } + else if (!pressed && (*key_down_ptr & key_down_mask)) + { + // this key is now up + *key_down_ptr &= ~key_down_mask; + } } } } @@ -1226,10 +1344,6 @@ uint32_t mame_ui_manager::handler_ingame(render_container &container) bool is_paused = machine().paused(); - // first draw the FPS counter - if (show_fps_counter()) - draw_fps_counter(container); - // draw the profiler if visible if (show_profiler()) draw_profiler(container); @@ -1263,9 +1377,17 @@ uint32_t mame_ui_manager::handler_ingame(render_container &container) } } - // is the natural keyboard enabled? - if (machine().natkeyboard().in_use() && (machine().phase() == machine_phase::RUNNING)) - process_natural_keyboard(); + // process UI events and update pointers if necessary + process_ui_events(); + if (m_update_pointers) + { + display_pointer_vector pointers; + pointers.reserve(m_active_pointers.size()); + for (auto const &pointer : m_active_pointers) + pointers.emplace_back(display_pointer{ *pointer.target, pointer.type, pointer.x, pointer.y }); + set_pointers(pointers.begin(), pointers.end()); + m_update_pointers = false; + } if (!ui_disabled) { @@ -1309,11 +1431,17 @@ uint32_t mame_ui_manager::handler_ingame(render_container &container) // handle a request to display graphics/palette if (machine().ui_input().pressed(IPT_UI_SHOW_GFX)) { + for (auto *target = machine().render().first_target(); target; target = target->next()) + { + if (!target->hidden()) + target->forget_pointers(); + } + if (!is_paused) machine().pause(); using namespace std::placeholders; set_handler( - ui_callback_type::VIEWER, + ui_callback_type::MENU, handler_callback_func( [this, is_paused] (render_container &container) -> uint32_t { @@ -2266,6 +2394,23 @@ void mame_ui_manager::menu_reset() } +bool mame_ui_manager::set_ui_event_handler(std::function<bool ()> &&handler) +{ + // only allow takeover if there's nothing else happening + if (ui_callback_type::GENERAL != m_handler_callback_type) + return false; + + set_handler( + ui_callback_type::CUSTOM, + handler_callback_func( + [cb = std::move(handler)] (render_container &container) -> uint32_t + { + return !cb() ? HANDLER_CANCEL : 0; + })); + return true; +} + + //------------------------------------------------- // get_general_input_setting - get the current // default setting for an input type (useful for diff --git a/src/frontend/mame/ui/ui.h b/src/frontend/mame/ui/ui.h index 662d8d06fc1..231a6de15cb 100644 --- a/src/frontend/mame/ui/ui.h +++ b/src/frontend/mame/ui/ui.h @@ -13,18 +13,23 @@ #pragma once -#include "render.h" -#include "moptions.h" #include "language.h" #include "ui/uimain.h" #include "ui/menuitem.h" +#include "ui/moptions.h" #include "ui/slider.h" #include "ui/text.h" +#include "render.h" + +#include "interface/uievents.h" + #include <any> #include <cassert> #include <ctime> +#include <functional> #include <set> +#include <string> #include <string_view> #include <typeindex> #include <typeinfo> @@ -63,14 +68,6 @@ class laserdisc_device; class mame_ui_manager; -enum class ui_callback_type -{ - GENERAL, - MODAL, - MENU, - VIEWER -}; - // ======================> ui_colors class ui_colors @@ -132,6 +129,18 @@ public: OPAQUE_ }; + struct display_pointer + { + std::reference_wrapper<render_target> target; + osd::ui_event_handler::pointer type; + float x, y; + + bool operator!=(display_pointer const &that) const noexcept + { + return (&target.get() != &that.target.get()) || (type != that.type) || (x != that.x) || (y != that.y); + } + }; + // construction/destruction mame_ui_manager(running_machine &machine); ~mame_ui_manager(); @@ -184,7 +193,6 @@ public: void set_show_profiler(bool show); bool show_profiler() const; void show_menu(); - void show_mouse(bool status); virtual bool is_menu_active() override; bool can_paste(); bool found_machine_warnings() const { return m_has_warnings; } @@ -195,8 +203,39 @@ public: void draw_fps_counter(render_container &container); void draw_profiler(render_container &container); + // pointer display + template <typename T> + void set_pointers(T first, T last) + { + auto dest = m_display_pointers.begin(); + while ((m_display_pointers.end() != dest) && (first != last)) + { + if (*first != *dest) + { + *dest = *first; + m_pointers_changed = true; + } + ++dest; + ++first; + } + if (m_display_pointers.end() != dest) + { + m_display_pointers.erase(dest, m_display_pointers.end()); + m_pointers_changed = true; + } + else + { + while (first != last) + { + m_display_pointers.emplace_back(*first); + m_pointers_changed = true; + ++first; + } + } + } + // slider controls - std::vector<ui::menu_item>& get_slider_list(); + std::vector<ui::menu_item> &get_slider_list(); // metrics float target_font_height() const { return m_target_font_height; } @@ -205,7 +244,7 @@ public: void update_target_font_height(); // other - void process_natural_keyboard(); + void process_ui_events(); ui::text_layout create_layout(render_container &container, float width = 1.0, ui::text_layout::text_justify justify = ui::text_layout::text_justify::LEFT, ui::text_layout::word_wrapping wrap = ui::text_layout::word_wrapping::WORD); void set_image_display_enabled(bool image_display_enabled) { m_image_display_enabled = image_display_enabled; } bool image_display_enabled() const { return m_image_display_enabled; } @@ -215,6 +254,7 @@ public: virtual void popup_time_string(int seconds, std::string message) override; virtual void menu_reset() override; + virtual bool set_ui_event_handler(std::function<bool ()> &&handler) override; template <typename Owner, typename Data, typename... Param> Data &get_session_data(Param &&... args) @@ -232,9 +272,15 @@ public: std::string get_general_input_setting(ioport_type type, int player = 0, input_seq_type seqtype = SEQ_TYPE_STANDARD); private: + enum class ui_callback_type : int; + + struct active_pointer; + using handler_callback_func = delegate<uint32_t (render_container &)>; using device_feature_set = std::set<std::pair<std::string, std::string> >; using session_data_map = std::unordered_map<std::type_index, std::any>; + using active_pointer_vector = std::vector<active_pointer>; + using display_pointer_vector = std::vector<display_pointer>; // instance variables std::unique_ptr<render_font> m_font; @@ -248,11 +294,13 @@ private: osd_ticks_t m_popup_text_end; std::unique_ptr<uint8_t []> m_non_char_keys_down; + active_pointer_vector m_active_pointers; + display_pointer_vector m_display_pointers; bitmap_argb32 m_mouse_bitmap; render_texture * m_mouse_arrow_texture; bool m_mouse_show; - int m_mouse_target; - std::pair<float, float> m_mouse_position; + bool m_update_pointers; + bool m_pointers_changed; ui_options m_ui_options; ui_colors m_ui_colors; @@ -280,6 +328,7 @@ private: // private methods void set_handler(ui_callback_type callback_type, handler_callback_func &&callback); + void frame_update(); void exit(); void config_load(config_type cfg_type, config_level cfg_level, util::xml::data_node const *parentnode); void config_save(config_type cfg_type, util::xml::data_node *parentnode); diff --git a/src/frontend/mame/ui/utils.cpp b/src/frontend/mame/ui/utils.cpp index b83abc5c277..b0107589e04 100644 --- a/src/frontend/mame/ui/utils.cpp +++ b/src/frontend/mame/ui/utils.cpp @@ -204,23 +204,16 @@ public: virtual void show_ui(mame_ui_manager &mui, render_container &container, std::function<void (Base &)> &&handler) override { - if (m_choices.empty()) - { - handler(*this); - } - else - { - menu::stack_push<menu_selector>( - mui, container, - _("Filter"), // TODO: get localised name of filter in here somehow - std::vector<std::string>(m_choices), // ouch, a vector copy! - m_selection, - [this, cb = std::move(handler)] (int selection) - { - m_selection = selection; - cb(*this); - }); - } + menu::stack_push<menu_selector>( + mui, container, + _("Filter"), // TODO: get localised name of filter in here somehow + std::vector<std::string>(m_choices), // ouch, a vector copy! + m_selection, + [this, cb = std::move(handler)] (int selection) + { + m_selection = selection; + cb(*this); + }); } virtual bool wants_adjuster() const override { return have_choices(); } diff --git a/src/frontend/mame/ui/utils.h b/src/frontend/mame/ui/utils.h index c0a4e1d30ca..04cb25a840c 100644 --- a/src/frontend/mame/ui/utils.h +++ b/src/frontend/mame/ui/utils.h @@ -362,42 +362,12 @@ private: enum { - RP_FIRST = 0, - RP_IMAGES = RP_FIRST, - RP_INFOS, - RP_LAST = RP_INFOS -}; - -enum -{ SHOW_PANELS = 0, HIDE_LEFT_PANEL, HIDE_RIGHT_PANEL, HIDE_BOTH }; -enum -{ - HOVER_DAT_UP = -1000, - HOVER_DAT_DOWN, - HOVER_UI_LEFT, - HOVER_UI_RIGHT, - HOVER_ARROW_UP, - HOVER_ARROW_DOWN, - HOVER_B_FAV, - HOVER_B_EXPORT, - HOVER_B_AUDIT, - HOVER_B_DATS, - HOVER_BACKTRACK, - HOVER_RPANEL_ARROW, - HOVER_LPANEL_ARROW, - HOVER_FILTER_FIRST, - HOVER_FILTER_LAST = HOVER_FILTER_FIRST + std::max<int>(ui::machine_filter::COUNT, ui::software_filter::COUNT), - HOVER_RP_FIRST, - HOVER_RP_LAST = HOVER_RP_FIRST + 1 + RP_LAST, - HOVER_INFO_TEXT -}; - // FIXME: this stuff shouldn't all be globals // GLOBAL CLASS diff --git a/src/frontend/mame/ui/viewgfx.cpp b/src/frontend/mame/ui/viewgfx.cpp index c4a6ecdbf68..cbb104d5460 100644 --- a/src/frontend/mame/ui/viewgfx.cpp +++ b/src/frontend/mame/ui/viewgfx.cpp @@ -65,6 +65,81 @@ public: if (!m_machine.paused()) m_bitmap_dirty = true; + // handle pointer events to show hover info + ui_event event; + while (m_machine.ui_input().pop_event(&event)) + { + switch (event.event_type) + { + case ui_event::type::POINTER_UPDATE: + { + // ignore pointer input in windows other than the one that displays the UI + render_target &target(m_machine.render().ui_target()); + if (&target != event.target) + break; + + // don't change if the current pointer has buttons pressed and this one doesn't + if (event.pointer_id == m_current_pointer) + { + assert(m_pointer_type == event.pointer_type); + m_pointer_buttons = event.pointer_buttons; + m_pointer_inside = target.map_point_container( + event.pointer_x, + event.pointer_y, + container, + m_pointer_x, + m_pointer_y); + } + else if ((0 > m_current_pointer) || (!m_pointer_buttons && (!m_pointer_inside || event.pointer_buttons))) + { + float x, y; + bool const inside(target.map_point_container(event.pointer_x, event.pointer_y, container, x, y)); + if ((0 > m_current_pointer) || event.pointer_buttons || (!m_pointer_inside && inside)) + { + m_current_pointer = event.pointer_id; + m_pointer_type = event.pointer_type; + m_pointer_buttons = event.pointer_buttons; + m_pointer_x = x; + m_pointer_y = y; + m_pointer_inside = inside; + } + } + } + break; + + case ui_event::type::POINTER_LEAVE: + case ui_event::type::POINTER_ABORT: + { + // if this was our pointer, we've lost it + render_target &target(m_machine.render().ui_target()); + if ((&target == event.target) && (event.pointer_id == m_current_pointer)) + { + // keep the pointer position and type so we can show touch locations after release + m_current_pointer = -1; + m_pointer_buttons = 0U; + m_pointer_inside = target.map_point_container( + event.pointer_x, + event.pointer_y, + container, + m_pointer_x, + m_pointer_y); + } + } + break; + + // ignore anything that isn't pointer-related + default: + break; + } + } + + // always draw non-touch pointer + mame_ui_manager::display_pointer pointers[1]{ { m_machine.render().ui_target(), m_pointer_type, m_pointer_x, m_pointer_y } }; + if (m_pointer_inside && (0 <= m_current_pointer) && (ui_event::pointer::TOUCH != m_pointer_type)) + mui.set_pointers(std::begin(pointers), std::end(pointers)); + else + mui.set_pointers(std::begin(pointers), std::begin(pointers)); + // try to display the selected view while (true) { @@ -519,6 +594,13 @@ private: if (input.pressed(IPT_UI_SELECT)) { m_mode = view((int(m_mode) + 1) % 3); + if (0 > m_current_pointer) + { + m_pointer_type = ui_event::pointer::UNKNOWN; + m_pointer_x = -1.0F; + m_pointer_x = -1.0F; + m_pointer_inside = false; + } m_bitmap_dirty = true; } @@ -543,6 +625,12 @@ private: if (!uistate) m_machine.resume(); m_machine.ui_input().reset(); + m_current_pointer = -1; + m_pointer_type = ui_event::pointer::UNKNOWN; + m_pointer_buttons = 0U; + m_pointer_x = -1.0F; + m_pointer_y = -1.0F; + m_pointer_inside = false; m_bitmap_dirty = true; return mame_ui_manager::HANDLER_CANCEL; } @@ -595,20 +683,24 @@ private: bool map_mouse(render_container &container, render_bounds const &clip, float &x, float &y) const { - int32_t target_x, target_y; - bool button; - render_target *const target = m_machine.ui_input().find_mouse(&target_x, &target_y, &button); - if (!target) - return false; - else if (!target->map_point_container(target_x, target_y, container, x, y)) + if (((0 > m_current_pointer) && (m_pointer_type != ui_event::pointer::TOUCH)) || !m_pointer_inside) return false; - else - return clip.includes(x, y); + + x = m_pointer_x; + y = m_pointer_y; + return clip.includes(x, y); } running_machine &m_machine; view m_mode = view::PALETTE; + s32 m_current_pointer = -1; + ui_event::pointer m_pointer_type = ui_event::pointer::UNKNOWN; + u32 m_pointer_buttons = 0U; + float m_pointer_x = -1.0F; + float m_pointer_y = -1.0F; + bool m_pointer_inside = false; + bitmap_rgb32 m_bitmap; render_texture *m_texture = nullptr; bool m_bitmap_dirty = false; @@ -961,6 +1053,11 @@ uint32_t gfx_viewer::handle_palette(mame_ui_manager &mui, render_container &cont index, col.a(), col.r(), col.g(), col.b()); } + + // keep touch pointer displayed after release so they know what it's pointing at + mame_ui_manager::display_pointer pointers[1]{ { m_machine.render().ui_target(), m_pointer_type, m_pointer_x, m_pointer_y } }; + if (ui_event::pointer::TOUCH == m_pointer_type) + mui.set_pointers(std::begin(pointers), std::end(pointers)); } } @@ -1163,6 +1260,11 @@ uint32_t gfx_viewer::handle_gfxset(mame_ui_manager &mui, render_container &conta code, set.m_color, xpixel, ypixel, gfx.colorbase() + (set.m_color * gfx.granularity()) + pixdata); + + // keep touch pointer displayed after release so they know what it's pointing at + mame_ui_manager::display_pointer pointers[1]{ { m_machine.render().ui_target(), m_pointer_type, m_pointer_x, m_pointer_y } }; + if (ui_event::pointer::TOUCH == m_pointer_type) + mui.set_pointers(std::begin(pointers), std::end(pointers)); } } if (!found_pixel) @@ -1331,6 +1433,11 @@ uint32_t gfx_viewer::handle_tilemap(mame_ui_manager &mui, render_container &cont _("gfxview", " (%1$u %2$u) = GFX%3$u #%4$X:%5$X"), col * tilemap.tilewidth(), row * tilemap.tileheight(), gfxnum, code, color); + + // keep touch pointer displayed after release so they know what it's pointing at + mame_ui_manager::display_pointer pointers[1]{ { m_machine.render().ui_target(), m_pointer_type, m_pointer_x, m_pointer_y } }; + if (ui_event::pointer::TOUCH == m_pointer_type) + mui.set_pointers(std::begin(pointers), std::end(pointers)); } else { diff --git a/src/mame/layout/alphie.lay b/src/mame/layout/alphie.lay index 614ba89329d..3d3b591c31a 100644 --- a/src/mame/layout/alphie.lay +++ b/src/mame/layout/alphie.lay @@ -66,7 +66,7 @@ authors:hap <element ref="text_q"><bounds x="8" y="0" width="10" height="2" /></element> <element ref="text_ans"><bounds x="8" y="3" width="10" height="2" /></element> <element ref="text_act"><bounds x="8" y="6" width="10" height="2" /></element> - <element ref="nothing" inputtag="IN.1" inputmask="0x00" inputraw="yes"><bounds x="14" y="-1" width="4" height="10" /></element> + <element ref="nothing" clickthrough="no"><bounds x="14" y="-1" width="4" height="10" /></element> <element ref="switch_qa" inputtag="IN.1" inputmask="0x1f" inputraw="yes"><bounds x="15" y="0" width="2" height="2" /></element> <element ref="switch_qa" inputtag="IN.2" inputmask="0x1f" inputraw="yes"><bounds x="15" y="3" width="2" height="2" /></element> <element ref="switch_act" inputtag="IN.3" inputmask="0x0f" inputraw="yes"><bounds x="15" y="6" width="2" height="2" /></element> diff --git a/src/mame/layout/autorace.lay b/src/mame/layout/autorace.lay index 61652a4acf2..8a4b215cb35 100644 --- a/src/mame/layout/autorace.lay +++ b/src/mame/layout/autorace.lay @@ -61,7 +61,7 @@ authors:hap <!-- bezel --> <element ref="lane"><bounds x="18.69" y="9.7" width="2.62" height="24" /></element> - <element ref="white" inputtag="IN.0" inputmask="0x00" inputraw="yes"><bounds x="16" y="9.7" width="8" height="0.8" /></element> + <element ref="white" clickthrough="no"><bounds x="16" y="9.7" width="8" height="0.8" /></element> <collection name="Gear Position"> <element ref="gear" inputtag="IN.0" inputmask="0x0c" inputraw="yes"><bounds x="16" y="9.7" width="8" height="0.8" /></element> </collection> diff --git a/src/mame/layout/bcheetah.lay b/src/mame/layout/bcheetah.lay index 2f0fffa705e..b77a30dcb06 100644 --- a/src/mame/layout/bcheetah.lay +++ b/src/mame/layout/bcheetah.lay @@ -67,7 +67,7 @@ authors:hap <bounds x="0" y="5" width="15" height="11" /> <!-- pro/run switch --> - <element ref="nothing" inputtag="IN.0" inputmask="0x00" inputraw="yes"><bounds x="2" y="5" width="2.9" height="1.1" /></element> <!-- block clickable input --> + <element ref="nothing" clickthrough="no"><bounds x="2" y="5" width="2.9" height="1.1" /></element> <!-- block clickable input --> <element ref="switch1" inputtag="IN.0" inputmask="0x02" inputraw="yes"><bounds x="2" y="5" width="2.9" height="1.1" /></element> <element ref="gray" inputtag="IN.0" inputmask="0x02" inputraw="yes"><bounds x="2" y="6.2" width="2.9" height="2.2" /></element> diff --git a/src/mame/layout/compperf.lay b/src/mame/layout/compperf.lay index fde3964b50b..4ecbf9d5439 100644 --- a/src/mame/layout/compperf.lay +++ b/src/mame/layout/compperf.lay @@ -158,9 +158,9 @@ authors:hap <element ref="whited" inputtag="IN.1" inputmask="0x0c" inputraw="yes"><bounds xc="116" yc="111" width="3.5" height="3.5" /></element> <!-- block clickable input --> - <element ref="nothing" inputtag="IN.1" inputmask="0x00" inputraw="yes"><bounds xc="89.5" yc="114.5" width="20" height="3" /></element> - <element ref="nothing" inputtag="IN.0" inputmask="0x00" inputraw="yes"><bounds xc="106" yc="125.5" width="20" height="3" /></element> - <element ref="nothing" inputtag="IN.1" inputmask="0x00" inputraw="yes"><bounds xc="122" yc="114.5" width="20" height="3" /></element> + <element ref="nothing" clickthrough="no"><bounds xc="89.5" yc="114.5" width="20" height="3" /></element> + <element ref="nothing" clickthrough="no"><bounds xc="106" yc="125.5" width="20" height="3" /></element> + <element ref="nothing" clickthrough="no"><bounds xc="122" yc="114.5" width="20" height="3" /></element> <element ref="switch1" inputtag="IN.1" inputmask="0x03" inputraw="yes"><bounds xc="89.5" yc="114.5" width="20" height="3" /></element> <element ref="switch2" inputtag="IN.0" inputmask="0x07" inputraw="yes"><bounds xc="106" yc="125.5" width="20" height="3" /></element> diff --git a/src/mame/layout/copycat.lay b/src/mame/layout/copycat.lay index dfb9a65349d..61e7fe258e4 100644 --- a/src/mame/layout/copycat.lay +++ b/src/mame/layout/copycat.lay @@ -120,13 +120,13 @@ authors:hap <element ref="text_b2"><bounds x="5.125" y="6.7" width="1.75" height="0.25" /></element> <element ref="text_b3"><bounds x="6.2375" y="6.7" width="1.9" height="0.25" /></element> - <element ref="nothing" inputtag="IN.2" inputmask="0x00" inputraw="yes"><bounds x="6.25" y="4.9" width="1.2" height="0.5" /></element> + <element ref="nothing" clickthrough="no"><bounds x="6.25" y="4.9" width="1.2" height="0.5" /></element> <element ref="switch" inputtag="IN.1" inputmask="0x0f" inputraw="yes"><bounds x="6.3" y="4.925" width="1" height="0.25" /></element> <element ref="text_s1"><bounds x="5.15" y="4.925" width="1.5" height="0.25" /></element> <element ref="text_ro"><bounds x="5.825" y="4.875" width="0.35" height="0.35" /></element> <element ref="nothing" inputtag="IN.1" inputmask="0x0f" inputraw="yes"><bounds x="5.8" y="4.85" width="0.4" height="0.4" /></element> - <element ref="nothing" inputtag="IN.2" inputmask="0x00" inputraw="yes"><bounds x="6.25" y="7.2" width="1.2" height="0.5" /></element> + <element ref="nothing" clickthrough="no"><bounds x="6.25" y="7.2" width="1.2" height="0.5" /></element> <element ref="switch" inputtag="IN.2" inputmask="0x07" inputraw="yes"><bounds x="6.3" y="7.275" width="1" height="0.25" /></element> <element ref="text_s2"><bounds x="5.15" y="7.275" width="1.5" height="0.25" /></element> <element ref="text_rg"><bounds x="5.825" y="7.225" width="0.35" height="0.35" /></element> diff --git a/src/mame/layout/matchme.lay b/src/mame/layout/matchme.lay index e361585def4..c3ba6c269fd 100644 --- a/src/mame/layout/matchme.lay +++ b/src/mame/layout/matchme.lay @@ -115,7 +115,7 @@ authors:hap <element ref="nothing" inputtag="IN.3" inputmask="0x08" inputraw="yes"><bounds x="9.805" y="9.55" width="0.39" height="0.39" /></element> <element ref="nothing" inputtag="IN.3" inputmask="0x07" inputraw="yes"><bounds x="9.805" y="9.95" width="0.39" height="0.39" /></element> - <element ref="nothing" inputtag="FAKE" inputmask="0x00" inputraw="yes"><bounds x="10.2" y="8.7" width="1.1" height="1.8" /></element> + <element ref="nothing" clickthrough="no"><bounds x="10.2" y="8.7" width="1.1" height="1.8" /></element> <element ref="switch_music" inputtag="FAKE" inputmask="0x02" inputraw="yes"><bounds x="10.25" y="8.8" width="1" height="0.29" /></element> <element ref="switch_speed" inputtag="IN.4" inputmask="0x01" inputraw="yes"><bounds x="10.25" y="9.2" width="1" height="0.29" /></element> <element ref="switch_skill" inputtag="IN.3" inputmask="0x08" inputraw="yes"><bounds x="10.25" y="9.6" width="1" height="0.29" /></element> diff --git a/src/mame/layout/mdndclab.lay b/src/mame/layout/mdndclab.lay index bd2c4fbeb8d..8627e852475 100644 --- a/src/mame/layout/mdndclab.lay +++ b/src/mame/layout/mdndclab.lay @@ -5,101 +5,76 @@ authors:hap, Carl --> <mamelayout version="2"> -<!-- luascript (-plugin layout) --> -<!-- this adds support for placing wall pieces by clicking between buttons --> - <script><![CDATA[ - local layout = {} - local walls = { { 7.5, {0, 10, 20, 30, 40, 50, 60, 70}}, - {17.5, {0, 10, 20, 30, 40, 50, 60, 70}}, - {27.5, {0, 10, 20, 30, 40, 50, 60, 70}}, - {37.5, {0, 10, 20, 30, 40, 50, 60, 70}}, - {47.5, {0, 10, 20, 30, 40, 50, 60, 70}}, - {57.5, {0, 10, 20, 30, 40, 50, 60, 70}}, - {67.5, {0, 10, 20, 30, 40, 50, 60, 70}} } - - local last_state = false - - local function get_mouse() - local x, y, button, target = machine.uiinput:find_mouse() - if not button then - last_state = false - return nil - end - if last_state then - return nil - end - last_state = true - local h = target.height - local w = target.width - local vb = target.current_view.bounds - local vw = (vb.x1 - vb.x0) - local vh = (vb.y1 - vb.y0) - if machine.options.entries.keepaspect:value() then - if (vh / h) < (vw / w) then - local oh = h - h = w * (vh / vw) - y = y - ((oh - h) / 2) - else - local ow = w - w = h * (vw / vh) - x = x - ((ow - w) / 2) - end - end - - x = ((x / w) * vw) + vb.x0 - y = ((y / h) * vh) + vb.y0 - return x, y - end - - function layout.reset() - for num, col in pairs(walls) do - for num2, wall in pairs(col[2]) do - machine.output:set_indexed_value("colwall", (num * 10) + num2, 0) - end - end - for num, row in pairs(walls) do - for num2, wall in pairs(row[2]) do - machine.output:set_indexed_value("rowwall", (num * 10) + num2, 0) - end - end - end - - function layout.frame() - local x, y = get_mouse() - if not x or x < 0 or y < 0 then - return - end - for num, col in pairs(walls) do - if col[1] < x and (col[1] + 2.5) > x then - for num2, wall in pairs(col[2]) do - if wall < y and (wall + 7.5) > y then - local state = machine.output:get_indexed_value("colwall", (num * 10) + num2) - machine.output:set_indexed_value("colwall", (num * 10) + num2, (~state) & 1) - return + -- add support for placing wall pieces by clicking between buttons + file:set_resolve_tags_callback( + function () + local walls = { { 7.5, { 0, 10, 20, 30, 40, 50, 60, 70 } }, + { 17.5, { 0, 10, 20, 30, 40, 50, 60, 70 } }, + { 27.5, { 0, 10, 20, 30, 40, 50, 60, 70 } }, + { 37.5, { 0, 10, 20, 30, 40, 50, 60, 70 } }, + { 47.5, { 0, 10, 20, 30, 40, 50, 60, 70 } }, + { 57.5, { 0, 10, 20, 30, 40, 50, 60, 70 } }, + { 67.5, { 0, 10, 20, 30, 40, 50, 60, 70 } } } + + for num, col in pairs(walls) do + for num2, wall in pairs(col[2]) do + machine.output:set_indexed_value("colwall", (num * 10) + num2, 0) end end - end - end - for num, row in pairs(walls) do - if row[1] < y and (row[1] + 2.5) > y then - for num2, wall in pairs(row[2]) do - if wall < x and (wall + 7.5) > x then - local state = machine.output:get_indexed_value("rowwall", (num * 10) + num2) - machine.output:set_indexed_value("rowwall", (num * 10) + num2, (~state) & 1) - return + for num, row in pairs(walls) do + for num2, wall in pairs(row[2]) do + machine.output:set_indexed_value("rowwall", (num * 10) + num2, 0) end end - end - end - end - return layout + local view = file.views["Internal Layout"] + local boundsref = view.items["boundsref"] + local transform + + view:set_recomputed_callback( + function () + transform = boundsref.bounds + end) + + view:set_pointer_updated_callback( + function (pointertype, id, device, x, y, buttons, pressed, released, clicks) + if ((pressed & 0x1) ~= 0) and (1 == buttons) then + -- convert to layout units + x = (x - transform.x0) / transform.width + y = (y - transform.y0) / transform.height + + for num, col in pairs(walls) do + if col[1] < x and (col[1] + 2.5) > x then + for num2, wall in pairs(col[2]) do + if wall < y and (wall + 7.5) > y then + local state = machine.output:get_indexed_value("colwall", (num * 10) + num2) + machine.output:set_indexed_value("colwall", (num * 10) + num2, (~state) & 1) + return + end + end + end + end + for num, row in pairs(walls) do + if row[1] < y and (row[1] + 2.5) > y then + for num2, wall in pairs(row[2]) do + if wall < x and (wall + 7.5) > x then + local state = machine.output:get_indexed_value("rowwall", (num * 10) + num2) + machine.output:set_indexed_value("rowwall", (num * 10) + num2, (~state) & 1) + return + end + end + end + end + end + end) + end) ]]></script> <!-- define elements --> + <element name="empty" /> <element name="static_black"><rect><color red="0.07" green="0.07" blue="0.06" /></rect></element> <element name="static_red"><rect><color red="0.55" green="0.1" blue="0.05" /></rect></element> <element name="static_white"><rect><color red="0.8" green="0.75" blue="0.7" /></rect></element> @@ -155,6 +130,10 @@ authors:hap, Carl <view name="Internal Layout"> <bounds left="-15" right="80" top="-2.5" bottom="80" /> + + <!-- for script coordinate calculation --> + <element id="boundsref" ref="empty"><bounds x="0" y="0" width="1" height="1" /></element> + <element ref="static_black"> <bounds left="-16" right="81" top="-3" bottom="81" /> </element> diff --git a/src/mame/layout/microvision.lay b/src/mame/layout/microvision.lay index 6210d8ade72..445e50c06c8 100644 --- a/src/mame/layout/microvision.lay +++ b/src/mame/layout/microvision.lay @@ -54,7 +54,7 @@ authors:hap <collection name="Paddle Position"> <!-- show live paddle position --> - <element ref="nothing" inputtag="PADDLE" inputmask="0x00" inputraw="yes"><bounds x="12" y="0" width="6" height="1" /></element> <!-- block clickable input --> + <element ref="nothing" clickthrough="no"><bounds x="12" y="0" width="6" height="1" /></element> <!-- block clickable input --> <element ref="bar" blend="multiply" inputtag="PADDLE" inputmask="0xff" inputraw="yes"><bounds x="12.75" y="0.12" width="4.2" height="0.75" /></element> <element ref="text_p1" blend="add"><bounds x="10.5" y="0.1" width="5" height="0.75" /></element> diff --git a/src/mame/layout/novag_savant.lay b/src/mame/layout/novag_savant.lay index 7959df33a14..fed46185014 100644 --- a/src/mame/layout/novag_savant.lay +++ b/src/mame/layout/novag_savant.lay @@ -155,7 +155,7 @@ authors:hap </repeat> <!-- backlight --> - <element ref="nothing" blend="add" inputtag="LIGHT" inputmask="0x00" inputraw="yes"><bounds x="0" y="0" width="101" height="114" /></element> + <element ref="nothing" blend="add" clickthrough="no"><bounds x="0" y="0" width="101" height="114" /></element> <element ref="cb_light" blend="multiply" inputtag="LIGHT" inputmask="0x01" inputraw="yes"><bounds x="0" y="0" width="101" height="114" /></element> <!-- mask edges --> diff --git a/src/mame/layout/novag_supercon.lay b/src/mame/layout/novag_supercon.lay index 834b2c9b88c..b03b4ae3174 100644 --- a/src/mame/layout/novag_supercon.lay +++ b/src/mame/layout/novag_supercon.lay @@ -572,15 +572,15 @@ authors:hap <bounds left="-19" right="116" top="-0.5" bottom="88.5" /> <!-- block buttons --> - <element ref="hlb" inputtag="IN.1" inputmask="0x00" inputraw="yes"><bounds x="88" y="16" width="6" height="4" /></element> - <element ref="hlb" inputtag="IN.2" inputmask="0x00" inputraw="yes"><bounds x="88" y="26" width="6" height="4" /></element> - <element ref="hlb" inputtag="IN.5" inputmask="0x00" inputraw="yes"><bounds x="88" y="56" width="6" height="4" /></element> - - <element ref="hlb" inputtag="IN.1" inputmask="0x00" inputraw="yes"><bounds x="97" y="16" width="6" height="4" /></element> - <element ref="hlb" inputtag="IN.2" inputmask="0x00" inputraw="yes"><bounds x="97" y="26" width="6" height="4" /></element> - <element ref="hlb" inputtag="IN.4" inputmask="0x00" inputraw="yes"><bounds x="97" y="46" width="6" height="4" /></element> - <element ref="hlb" inputtag="IN.5" inputmask="0x00" inputraw="yes"><bounds x="97" y="56" width="6" height="4" /></element> - <element ref="hlb" inputtag="IN.6" inputmask="0x00" inputraw="yes"><bounds x="97" y="66" width="6" height="4" /></element> + <element ref="hlb" clickthrough="no"><bounds x="88" y="16" width="6" height="4" /></element> + <element ref="hlb" clickthrough="no"><bounds x="88" y="26" width="6" height="4" /></element> + <element ref="hlb" clickthrough="no"><bounds x="88" y="56" width="6" height="4" /></element> + + <element ref="hlb" clickthrough="no"><bounds x="97" y="16" width="6" height="4" /></element> + <element ref="hlb" clickthrough="no"><bounds x="97" y="26" width="6" height="4" /></element> + <element ref="hlb" clickthrough="no"><bounds x="97" y="46" width="6" height="4" /></element> + <element ref="hlb" clickthrough="no"><bounds x="97" y="56" width="6" height="4" /></element> + <element ref="hlb" clickthrough="no"><bounds x="97" y="66" width="6" height="4" /></element> <group ref="default"><bounds left="-19" right="116" top="-0.5" bottom="88.5" /></group> diff --git a/src/mame/layout/saitek_mark5.lay b/src/mame/layout/saitek_mark5.lay index 503638b7727..ed1878dcda4 100644 --- a/src/mame/layout/saitek_mark5.lay +++ b/src/mame/layout/saitek_mark5.lay @@ -219,7 +219,7 @@ authors:hap <element ref="hlc" blend="add" inputtag="IN.8" inputmask="0x80"><bounds x="18.333" y="12.333" width="4.666" height="4.666" /></element> <element ref="black"><bounds x="9" y="22.5" width="14" height="45" /></element> - <element ref="nothing" blend="add" inputtag="IN.0" inputmask="0x00" inputraw="yes"><bounds x="-1" y="-1" width="34" height="69" /></element> + <element ref="nothing" blend="add" clickthrough="no"><bounds x="-1" y="-1" width="34" height="69" /></element> <!-- button labels (upper) --> <element ref="text_b01e" blend="add" inputtag="IN.6" inputmask="0x04" inputraw="yes"><bounds x="0" y="1" width="6" height="1.4" /></element> @@ -405,7 +405,7 @@ authors:hap <group name="display"> <screen index="0" blend="alpha"><bounds x="0" y="0" width="100" height="114.65" /></screen> - <element ref="nothing" blend="add" inputtag="IN.7" inputmask="0x00" inputraw="yes"><bounds x="0" y="0" width="100" height="114.65" /></element> + <element ref="nothing" blend="add" clickthrough="no"><bounds x="0" y="0" width="100" height="114.65" /></element> <element ref="lcd_bg" blend="multiply" inputtag="IN.7" inputmask="0x02" inputraw="yes"><bounds x="0" y="0" width="100" height="114.65" /></element> <element ref="lcd_cb" blend="multiply"><bounds x="0" y="0" width="100" height="95.9" /></element> </group> diff --git a/src/mame/layout/saitek_mark6.lay b/src/mame/layout/saitek_mark6.lay index 260f5b3421b..d7fe6d5f1ee 100644 --- a/src/mame/layout/saitek_mark6.lay +++ b/src/mame/layout/saitek_mark6.lay @@ -513,7 +513,7 @@ authors:hap <element ref="hlc" blend="add" inputtag="IN.8" inputmask="0x80"><bounds x="18.333" y="12.333" width="4.666" height="4.666" /></element> <element ref="black"><bounds x="9" y="22.5" width="14" height="45" /></element> - <element ref="nothing" blend="add" inputtag="IN.0" inputmask="0x00" inputraw="yes"><bounds x="-1" y="-1" width="34" height="69" /></element> + <element ref="nothing" blend="add" clickthrough="no"><bounds x="-1" y="-1" width="34" height="69" /></element> <!-- button labels (upper) --> <element ref="text_b01e" blend="add" inputtag="IN.6" inputmask="0x04" inputraw="yes"><bounds x="0" y="1" width="6" height="1.4" /></element> @@ -699,7 +699,7 @@ authors:hap <group name="display"> <screen index="0" blend="alpha"><bounds x="0" y="0" width="100" height="114.65" /></screen> - <element ref="nothing" blend="add" inputtag="IN.7" inputmask="0x00" inputraw="yes"><bounds x="0" y="0" width="100" height="114.65" /></element> + <element ref="nothing" blend="add" clickthrough="no"><bounds x="0" y="0" width="100" height="114.65" /></element> <element ref="lcd_bg" blend="multiply" inputtag="IN.7" inputmask="0x02" inputraw="yes"><bounds x="0" y="0" width="100" height="114.65" /></element> <element ref="lcd_cb" blend="multiply"><bounds x="0" y="0" width="100" height="95.9" /></element> </group> diff --git a/src/mame/layout/saitek_ssystem3.lay b/src/mame/layout/saitek_ssystem3.lay index 0e45199ed6b..694194e3442 100644 --- a/src/mame/layout/saitek_ssystem3.lay +++ b/src/mame/layout/saitek_ssystem3.lay @@ -146,7 +146,7 @@ authors:hap <group name="lcd1"> <bounds x="-1" y="3" width="18" height="8" /> <screen index="0" blend="alpha"><bounds x="0" y="4" width="15.8" height="6" /></screen> - <element ref="nothing" blend="add" inputtag="IN.4" inputmask="0x00" inputraw="yes"><bounds x="-1" y="3" width="18" height="8" /></element> + <element ref="nothing" blend="add" clickthrough="no"><bounds x="-1" y="3" width="18" height="8" /></element> <element ref="lcd_bg" blend="multiply" inputtag="IN.4" inputmask="0x02" inputraw="yes"><bounds x="-1" y="3" width="18" height="8" /></element> </group> @@ -206,7 +206,7 @@ authors:hap <screen index="1" blend="alpha"><bounds x="9.8" y="10" width="106" height="108" /></screen> <element ref="lcd_cb" blend="multiply"><bounds x="1.5" y="2.9" width="122.6" height="122.1" /></element> - <element ref="nothing" blend="add" inputtag="IN.4" inputmask="0x00" inputraw="yes"><bounds x="6.2" y="7.6" width="113.3" height="112.8" /></element> + <element ref="nothing" blend="add" clickthrough="no"><bounds x="6.2" y="7.6" width="113.3" height="112.8" /></element> <element ref="lcd2m" blend="multiply" inputtag="IN.4" inputmask="0x04" inputraw="yes"><bounds x="6.2" y="7.6" width="113.3" height="112.8" /></element> </group> diff --git a/src/mame/layout/saitek_ssystem4.lay b/src/mame/layout/saitek_ssystem4.lay index cd72b1d0edc..5ff273b0e01 100644 --- a/src/mame/layout/saitek_ssystem4.lay +++ b/src/mame/layout/saitek_ssystem4.lay @@ -91,7 +91,7 @@ authors:hap <bounds left="8" right="52" top="1" bottom="39" /> <screen index="0" blend="alpha"><bounds x="10" y="4" width="15.8" height="6" /></screen> - <element ref="nothing" blend="add" inputtag="IN.4" inputmask="0x00" inputraw="yes"><bounds x="9" y="3" width="18" height="8" /></element> + <element ref="nothing" blend="add" clickthrough="no"><bounds x="9" y="3" width="18" height="8" /></element> <element ref="lcd_bg" blend="multiply" inputtag="IN.4" inputmask="0x02" inputraw="yes"><bounds x="9" y="3" width="18" height="8" /></element> <element ref="button" inputtag="IN.0" inputmask="0x04"><bounds x="28" y="4" width="5" height="6" /></element> diff --git a/src/mame/layout/simon.lay b/src/mame/layout/simon.lay index ef56c8eef3f..25993d3cb2c 100644 --- a/src/mame/layout/simon.lay +++ b/src/mame/layout/simon.lay @@ -130,7 +130,7 @@ authors:hap <!-- switches --> <element ref="text_game"><bounds x="68" y="93" width="20" height="4" /></element> - <element ref="nothing" inputtag="IN.0" inputmask="0x00" inputraw="yes"><bounds x="73.9" y="97.9" width="3.2" height="4.2" /></element> + <element ref="nothing" clickthrough="no"><bounds x="73.9" y="97.9" width="3.2" height="4.2" /></element> <element ref="switch_game" inputtag="IN.0" inputmask="0x07" inputraw="yes"><bounds x="74" y="98" width="3" height="4" /></element> <element ref="text_lg"><bounds x="66.5" y="97" width="6" height="6" /></element> <element ref="nothing" inputtag="SWITCH" inputmask="0x01"><bounds x="65.5" y="96" width="8" height="8" /></element> @@ -138,7 +138,7 @@ authors:hap <element ref="nothing" inputtag="SWITCH" inputmask="0x02"><bounds x="77" y="96" width="8" height="8" /></element> <element ref="text_skill"><bounds x="112" y="93" width="30" height="4" /></element> - <element ref="nothing" inputtag="IN.3" inputmask="0x00" inputraw="yes"><bounds x="122.9" y="97.9" width="3.2" height="4.2" /></element> + <element ref="nothing" clickthrough="no"><bounds x="122.9" y="97.9" width="3.2" height="4.2" /></element> <element ref="switch_skill" inputtag="IN.3" inputmask="0x0f" inputraw="yes"><bounds x="123" y="98" width="3" height="4" /></element> <element ref="text_lb"><bounds x="115.5" y="97" width="6" height="6" /></element> <element ref="nothing" inputtag="SWITCH" inputmask="0x04"><bounds x="114.5" y="96" width="8" height="8" /></element> diff --git a/src/mame/layout/ssimon.lay b/src/mame/layout/ssimon.lay index a725bf4d053..2f48a491466 100644 --- a/src/mame/layout/ssimon.lay +++ b/src/mame/layout/ssimon.lay @@ -174,7 +174,7 @@ authors:hap <element ref="text_s3"><bounds x="-3.9" y="32.15" width="3.5" height="0.9" /></element> <element ref="static_black2"><bounds x="-6.5" y="29.125" width="0.5" height="4.5" /></element> - <element ref="nothing" inputtag="IN.6" inputmask="0x00" inputraw="yes"><bounds x="-7.25" y="29.125" width="2" height="4.5" /></element> + <element ref="nothing" clickthrough="no"><bounds x="-7.25" y="29.125" width="2" height="4.5" /></element> <element ref="switch0" inputtag="IN.6" inputmask="0x03" inputraw="yes"><bounds x="-7" y="29.625" width="1.5" height="1" /></element> <element ref="switch1" inputtag="IN.6" inputmask="0x03" inputraw="yes"><bounds x="-7" y="30.875" width="1.5" height="1" /></element> <element ref="switch2" inputtag="IN.6" inputmask="0x03" inputraw="yes"><bounds x="-7" y="32.125" width="1.5" height="1" /></element> @@ -207,7 +207,7 @@ authors:hap <element ref="text_4"><bounds x="50.25" y="34.65" width="1" height="0.9" /></element> <element ref="static_black2"><bounds x="53" y="16.875" width="0.5" height="7" /></element> - <element ref="nothing" inputtag="IN.0" inputmask="0x00" inputraw="yes"><bounds x="52.25" y="16.875" width="2" height="7" /></element> + <element ref="nothing" clickthrough="no"><bounds x="52.25" y="16.875" width="2" height="7" /></element> <element ref="switch0" inputtag="IN.0" inputmask="0x0f" inputraw="yes"><bounds x="52.5" y="17.375" width="1.5" height="1" /></element> <element ref="switch8" inputtag="IN.0" inputmask="0x0f" inputraw="yes"><bounds x="52.5" y="18.625" width="1.5" height="1" /></element> <element ref="switch4" inputtag="IN.0" inputmask="0x0f" inputraw="yes"><bounds x="52.5" y="19.875" width="1.5" height="1" /></element> @@ -215,7 +215,7 @@ authors:hap <element ref="switch1" inputtag="IN.0" inputmask="0x0f" inputraw="yes"><bounds x="52.5" y="22.375" width="1.5" height="1" /></element> <element ref="static_black2"><bounds x="53" y="29.125" width="0.5" height="7" /></element> - <element ref="nothing" inputtag="IN.4" inputmask="0x00" inputraw="yes"><bounds x="52.25" y="29.125" width="2" height="7" /></element> + <element ref="nothing" clickthrough="no"><bounds x="52.25" y="29.125" width="2" height="7" /></element> <element ref="switch0" inputtag="IN.4" inputmask="0x0f" inputraw="yes"><bounds x="52.5" y="29.625" width="1.5" height="1" /></element> <element ref="switch2" inputtag="IN.4" inputmask="0x0f" inputraw="yes"><bounds x="52.5" y="30.875" width="1.5" height="1" /></element> <element ref="switch4" inputtag="IN.4" inputmask="0x0f" inputraw="yes"><bounds x="52.5" y="32.125" width="1.5" height="1" /></element> diff --git a/src/mame/layout/strobe.lay b/src/mame/layout/strobe.lay index 7947e1c3bc5..6d44a11acb1 100644 --- a/src/mame/layout/strobe.lay +++ b/src/mame/layout/strobe.lay @@ -134,7 +134,7 @@ authors:hap <element ref="nothing" inputtag="IN.5" inputmask="0x07" inputraw="yes"><bounds xc="100" yc="97" width="3.5" height="3.5" /></element> <element ref="nothing" inputtag="IN.6" inputmask="0x06" inputraw="yes"><bounds xc="106.5" yc="97" width="3.5" height="3.5" /></element> - <element ref="nothing" inputtag="IN.3" inputmask="0x00" inputraw="yes"><bounds xc="100" y="85" width="25" height="18" /></element> + <element ref="nothing" clickthrough="no"><bounds xc="100" y="85" width="25" height="18" /></element> <element ref="redd"><bounds xc="100" yc="100" width="20" height="20" /></element> <element ref="text_set" blend="multiply"><bounds xc="100" yc="106" width="20" height="5" /></element> diff --git a/src/mame/layout/tmbaskb.lay b/src/mame/layout/tmbaskb.lay index e1c509fddeb..6b24e829b88 100644 --- a/src/mame/layout/tmbaskb.lay +++ b/src/mame/layout/tmbaskb.lay @@ -245,7 +245,7 @@ authors:hap <group ref="digits"><bounds xc="150" y="33" width="45" height="43" /></group> <!-- other buttons --> - <element ref="nothing" inputtag="IN.3" inputmask="0x00" inputraw="yes"><bounds xc="150" y="232.5" width="230" height="4.5" /></element> + <element ref="nothing" clickthrough="no"><bounds xc="150" y="232.5" width="230" height="4.5" /></element> <element ref="switch1" inputtag="IN.3" inputmask="0x02" inputraw="yes"><bounds xc="111" y="232.5" width="30" height="4.5" /></element> <element ref="switch2" inputtag="IN.3" inputmask="0x01" inputraw="yes"><bounds xc="193" y="232.5" width="30" height="4.5" /></element> diff --git a/src/mame/layout/tmvolleyb.lay b/src/mame/layout/tmvolleyb.lay index ea09bf021ef..c5aacfb9e6f 100644 --- a/src/mame/layout/tmvolleyb.lay +++ b/src/mame/layout/tmvolleyb.lay @@ -178,7 +178,7 @@ authors:hap <element ref="text_serve"><bounds xc="271.5" yc="104" width="7" height="30" /><orientation rotate="270" /></element> <element ref="but1" inputtag="IN.2" inputmask="0x08"><bounds xc="281" yc="104" width="6" height="23" /></element> - <element ref="nothing" inputtag="IN.7" inputmask="0x00" inputraw="yes"><bounds xc="150" y="232.5" width="230" height="4.5" /></element> + <element ref="nothing" clickthrough="no"><bounds xc="150" y="232.5" width="230" height="4.5" /></element> <element ref="switch1" inputtag="IN.7" inputmask="0x01" inputraw="yes"><bounds xc="109" y="232.5" width="30" height="4.5" /></element> <element ref="switch2" inputtag="IN.6" inputmask="0x08" inputraw="yes"><bounds xc="193" y="232.5" width="30" height="4.5" /></element> diff --git a/src/mame/layout/vclock3.lay b/src/mame/layout/vclock3.lay index 8e0c0bd9cd8..cc02e133a32 100644 --- a/src/mame/layout/vclock3.lay +++ b/src/mame/layout/vclock3.lay @@ -41,7 +41,7 @@ authors:hap <element name="0.8" ref="led"><bounds x="-2" y="0" width="1.5" height="1.5" /></element> <element ref="text_pm"><bounds x="-4" y="2" width="5.5" height="1.5" /></element> - <element ref="nothing" inputtag="IN.6" inputmask="0x00" inputraw="yes"><bounds x="-2" y="9" width="1.5" height="1.5" /></element> <!-- block clickable input --> + <element ref="nothing" clickthrough="no"><bounds x="-2" y="9" width="1.5" height="1.5" /></element> <!-- block clickable input --> <element ref="led" inputtag="IN.6" inputmask="0x02"><bounds x="-2" y="9" width="1.5" height="1.5" /></element> <element ref="text_b1"><bounds x="-3" y="11" width="7.5" height="1.5" /></element> <element ref="text_b2"><bounds x="-3" y="12.5" width="7.5" height="1.5" /></element> diff --git a/src/mame/layout/videoart.lay b/src/mame/layout/videoart.lay index f8504dd9fa4..590a1bd99ea 100644 --- a/src/mame/layout/videoart.lay +++ b/src/mame/layout/videoart.lay @@ -64,7 +64,7 @@ authors:hap <element ref="text_erase" blend="multiply"><bounds x="16" yc="5" width="30" height="6" /></element> <element name="led" ref="led"><bounds x="33" yc="5" width="4" height="4" /></element> - <element ref="nothing" inputtag="AN0" inputmask="0x00" inputraw="yes"><bounds x="54" y="2" width="360" height="6" /></element> <!-- block clickable input --> + <element ref="nothing" clickthrough="no"><bounds x="54" y="2" width="360" height="6" /></element> <!-- block clickable input --> <element ref="color" inputtag="AN0" inputmask="0xff" inputraw="yes"><bounds x="54" y="2" width="360" height="6" /></element> <element ref="text_color" blend="multiply"><bounds x="55" yc="5" width="30" height="6" /></element> diff --git a/src/osd/interface/uievents.h b/src/osd/interface/uievents.h new file mode 100644 index 00000000000..199d612713a --- /dev/null +++ b/src/osd/interface/uievents.h @@ -0,0 +1,61 @@ +// license:BSD-3-Clause +// copyright-holders:Aaron Giles, Vas Crabb +/*************************************************************************** + + uievents.h + + OSD UI event interfaces + +***************************************************************************/ +#ifndef MAME_OSD_INTERFACE_UIEVENTS_H +#define MAME_OSD_INTERFACE_UIEVENTS_H + + +//************************************************************************** +// FORWARD DECLARATIONS +//************************************************************************** + +class render_target; + + +namespace osd { + +//************************************************************************** +// TYPE DEFINITIONS +//************************************************************************** + +// UI event handler interface + +class ui_event_handler +{ +protected: + ~ui_event_handler() = default; + +public: + enum class pointer + { + UNKNOWN, + MOUSE, + PEN, + TOUCH + }; + + // window events + virtual void push_window_focus_event(render_target *target) = 0; + virtual void push_window_defocus_event(render_target *target) = 0; + + // legacy mouse events + virtual void push_mouse_wheel_event(render_target *target, s32 x, s32 y, short delta, int lines) = 0; + + // pointer events + virtual void push_pointer_update(render_target *target, pointer type, u16 ptrid, u16 device, s32 x, s32 y, u32 buttons, u32 pressed, u32 released, s16 clicks) = 0; + virtual void push_pointer_leave(render_target *target, pointer type, u16 ptrid, u16 device, s32 x, s32 y, u32 released, s16 clicks) = 0; + virtual void push_pointer_abort(render_target *target, pointer type, u16 ptrid, u16 device, s32 x, s32 y, u32 released, s16 clicks) = 0; + + // text input events + virtual void push_char_event(render_target *target, char32_t ch) = 0; +}; + +} // namespace osd + +#endif // MAME_OSD_INTERFACE_UIEVENTS_H diff --git a/src/osd/modules/debugger/debugimgui.cpp b/src/osd/modules/debugger/debugimgui.cpp index 4ae514f857f..e2d4eba363a 100644 --- a/src/osd/modules/debugger/debugimgui.cpp +++ b/src/osd/modules/debugger/debugimgui.cpp @@ -18,6 +18,7 @@ #include "debug/debugcpu.h" #include "debugger.h" #include "render.h" +#include "ui/uimain.h" #include "uiinput.h" #include "formats/flopimg.h" @@ -36,16 +37,16 @@ class debug_area DISABLE_COPYING(debug_area); public: - debug_area(running_machine &machine, debug_view_type type) - : next(nullptr), - type(0), - ofs_x(0), - ofs_y(0), - is_collapsed(false), - exec_cmd(false), - scroll_end(false), - scroll_follow(false) - { + debug_area(running_machine &machine, debug_view_type type) : + next(nullptr), + type(0), + ofs_x(0), + ofs_y(0), + is_collapsed(false), + exec_cmd(false), + scroll_end(false), + scroll_follow(false) + { this->view = machine.debug_view().alloc_view(type, nullptr, this); this->type = type; this->m_machine = &machine; @@ -63,7 +64,7 @@ public: default: break; } - } + } ~debug_area() { //this->target->debug_free(*this->container); @@ -72,7 +73,7 @@ public: running_machine &machine() const { assert(m_machine != nullptr); return *m_machine; } - debug_area * next; + debug_area * next; int type; debug_view * view; @@ -99,9 +100,11 @@ public: class debug_imgui : public osd_module, public debug_module { public: - debug_imgui() - : osd_module(OSD_DEBUG_PROVIDER, "imgui"), debug_module(), + debug_imgui() : + osd_module(OSD_DEBUG_PROVIDER, "imgui"), debug_module(), m_machine(nullptr), + m_take_ui(false), + m_current_pointer(-1), m_mouse_x(0), m_mouse_y(0), m_mouse_button(false), @@ -155,9 +158,8 @@ private: std::string longname; }; - void handle_mouse(); + void handle_events(); void handle_mouse_views(); - void handle_keys(); void handle_keys_views(); void handle_console(running_machine* machine); void update(); @@ -184,15 +186,17 @@ private: static int history_set(ImGuiInputTextCallbackData* data); running_machine* m_machine; - int32_t m_mouse_x; - int32_t m_mouse_y; + bool m_take_ui; + int32_t m_current_pointer; + int32_t m_mouse_x; + int32_t m_mouse_y; bool m_mouse_button; bool m_prev_mouse_button; bool m_running; const char* font_name; float font_size; ImVec2 m_text_size; // size of character (assumes monospaced font is in use) - uint8_t m_key_char; + uint8_t m_key_char; bool m_hide; int m_win_count; // number of active windows, does not decrease, used to ID individual windows bool m_has_images; // true if current system has any image devices @@ -271,72 +275,18 @@ bool debug_imgui::get_view_source(void* data, int idx, const char** out_text) return true; } -void debug_imgui::handle_mouse() +void debug_imgui::handle_events() { - m_prev_mouse_button = m_mouse_button; - m_machine->ui_input().find_mouse(&m_mouse_x, &m_mouse_y, &m_mouse_button); ImGuiIO& io = ImGui::GetIO(); - io.MousePos = ImVec2(m_mouse_x,m_mouse_y); - io.MouseDown[0] = m_mouse_button; -} - -void debug_imgui::handle_mouse_views() -{ - rectangle rect; - bool clicked = false; - if(m_mouse_button == true && m_prev_mouse_button == false) - clicked = true; - - // check all views, and pass mouse clicks to them - if(!m_mouse_button) - return; - rect.min_x = view_main_disasm->ofs_x; - rect.min_y = view_main_disasm->ofs_y; - rect.max_x = view_main_disasm->ofs_x + view_main_disasm->view_width; - rect.max_y = view_main_disasm->ofs_y + view_main_disasm->view_height; - if(rect.contains(m_mouse_x,m_mouse_y) && clicked && view_main_disasm->has_focus) - { - debug_view_xy topleft = view_main_disasm->view->visible_position(); - debug_view_xy newpos; - newpos.x = topleft.x + (m_mouse_x-view_main_disasm->ofs_x) / m_text_size.x; - newpos.y = topleft.y + (m_mouse_y-view_main_disasm->ofs_y) / m_text_size.y; - view_main_disasm->view->set_cursor_position(newpos); - view_main_disasm->view->set_cursor_visible(true); - } - for(auto it = view_list.begin();it != view_list.end();++it) - { - rect.min_x = (*it)->ofs_x; - rect.min_y = (*it)->ofs_y; - rect.max_x = (*it)->ofs_x + (*it)->view_width; - rect.max_y = (*it)->ofs_y + (*it)->view_height; - if(rect.contains(m_mouse_x,m_mouse_y) && clicked && (*it)->has_focus) - { - if((*it)->view->cursor_supported()) - { - debug_view_xy topleft = (*it)->view->visible_position(); - debug_view_xy newpos; - newpos.x = topleft.x + (m_mouse_x-(*it)->ofs_x) / m_text_size.x; - newpos.y = topleft.y + (m_mouse_y-(*it)->ofs_y) / m_text_size.y; - (*it)->view->set_cursor_position(newpos); - (*it)->view->set_cursor_visible(true); - } - } - } -} - -void debug_imgui::handle_keys() -{ - ImGuiIO& io = ImGui::GetIO(); - ui_event event; - debug_area* focus_view = nullptr; // find view that has focus (should only be one at a time) - for(auto view_ptr = view_list.begin();view_ptr != view_list.end();++view_ptr) + debug_area* focus_view = nullptr; + for(auto view_ptr = view_list.begin();view_ptr != view_list.end(); ++view_ptr) if((*view_ptr)->has_focus) focus_view = *view_ptr; // check views in main views also (only the disassembler view accepts inputs) - if(view_main_disasm != nullptr) + if(view_main_disasm) if(view_main_disasm->has_focus) focus_view = view_main_disasm; @@ -355,21 +305,65 @@ void debug_imgui::handle_keys() for(input_item_id id = ITEM_ID_A; id <= ITEM_ID_CANCEL; ++id) { - if(m_machine->input().code_pressed(input_code(DEVICE_CLASS_KEYBOARD, 0, ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, id))) { - if (m_mapping.count(id)) io.AddKeyEvent(m_mapping[id], true); - } else { - if (m_mapping.count(id)) io.AddKeyEvent(m_mapping[id], false); + if(m_machine->input().code_pressed(input_code(DEVICE_CLASS_KEYBOARD, 0, ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, id))) + { + if(m_mapping.count(id)) + io.AddKeyEvent(m_mapping[id], true); + } + else + { + if(m_mapping.count(id)) + io.AddKeyEvent(m_mapping[id], false); } } + m_prev_mouse_button = m_mouse_button; m_key_char = 0; - while (m_machine->ui_input().pop_event(&event)) + ui_event event; + while(m_machine->ui_input().pop_event(&event)) { switch (event.event_type) { + case ui_event::type::POINTER_UPDATE: + if(&m_machine->render().ui_target() != event.target) + break; + if(event.pointer_id != m_current_pointer) + { + if((0 > m_current_pointer) || ((event.pointer_pressed & 1) && !m_mouse_button)) + m_current_pointer = event.pointer_id; + } + if(event.pointer_id == m_current_pointer) + { + bool changed = (m_mouse_x != event.pointer_x) || (m_mouse_y != event.pointer_y) || (m_mouse_button != bool(event.pointer_buttons & 1)); + m_mouse_x = event.pointer_x; + m_mouse_y = event.pointer_y; + m_mouse_button = bool(event.pointer_buttons & 1); + if(changed) + { + io.MousePos = ImVec2(m_mouse_x,m_mouse_y); + io.MouseDown[0] = m_mouse_button; + } + } + break; + case ui_event::type::POINTER_LEAVE: + case ui_event::type::POINTER_ABORT: + if((&m_machine->render().ui_target() == event.target) && (event.pointer_id == m_current_pointer)) + { + m_current_pointer = -1; + bool changed = (m_mouse_x != event.pointer_x) || (m_mouse_y != event.pointer_y) || m_mouse_button; + m_mouse_x = event.pointer_x; + m_mouse_y = event.pointer_y; + m_mouse_button = false; + if(changed) + { + io.MousePos = ImVec2(m_mouse_x,m_mouse_y); + io.MouseDown[0] = m_mouse_button; + } + } + break; case ui_event::type::IME_CHAR: - m_key_char = event.ch; - if(focus_view != nullptr) + m_key_char = event.ch; // FIXME: assigning 4-byte UCS4 character to 8-bit variable + if(focus_view) focus_view->view->process_char(m_key_char); return; default: @@ -431,6 +425,50 @@ void debug_imgui::handle_keys() } +void debug_imgui::handle_mouse_views() +{ + rectangle rect; + bool clicked = false; + if(m_mouse_button == true && m_prev_mouse_button == false) + clicked = true; + + // check all views, and pass mouse clicks to them + if(!m_mouse_button) + return; + rect.min_x = view_main_disasm->ofs_x; + rect.min_y = view_main_disasm->ofs_y; + rect.max_x = view_main_disasm->ofs_x + view_main_disasm->view_width; + rect.max_y = view_main_disasm->ofs_y + view_main_disasm->view_height; + if(rect.contains(m_mouse_x,m_mouse_y) && clicked && view_main_disasm->has_focus) + { + debug_view_xy topleft = view_main_disasm->view->visible_position(); + debug_view_xy newpos; + newpos.x = topleft.x + (m_mouse_x-view_main_disasm->ofs_x) / m_text_size.x; + newpos.y = topleft.y + (m_mouse_y-view_main_disasm->ofs_y) / m_text_size.y; + view_main_disasm->view->set_cursor_position(newpos); + view_main_disasm->view->set_cursor_visible(true); + } + for(auto it = view_list.begin();it != view_list.end();++it) + { + rect.min_x = (*it)->ofs_x; + rect.min_y = (*it)->ofs_y; + rect.max_x = (*it)->ofs_x + (*it)->view_width; + rect.max_y = (*it)->ofs_y + (*it)->view_height; + if(rect.contains(m_mouse_x,m_mouse_y) && clicked && (*it)->has_focus) + { + if((*it)->view->cursor_supported()) + { + debug_view_xy topleft = (*it)->view->visible_position(); + debug_view_xy newpos; + newpos.x = topleft.x + (m_mouse_x-(*it)->ofs_x) / m_text_size.x; + newpos.y = topleft.y + (m_mouse_y-(*it)->ofs_y) / m_text_size.y; + (*it)->view->set_cursor_position(newpos); + (*it)->view->set_cursor_visible(true); + } + } + } +} + void debug_imgui::handle_keys_views() { debug_area* focus_view = nullptr; @@ -1557,10 +1595,15 @@ void debug_imgui::wait_for_debugger(device_t &device, bool firststop) device.machine().ui_input().reset(); m_running = false; } + if(!m_take_ui) + { + m_take_ui = m_machine->ui().set_ui_event_handler([this] () { return m_take_ui; }); + if(m_take_ui) + m_machine->ui_input().reset(); + } m_hide = false; m_machine->osd().input_update(true); - handle_mouse(); - handle_keys(); + handle_events(); handle_console(m_machine); update_cpu_view(&device); imguiBeginFrame(m_mouse_x,m_mouse_y,m_mouse_button ? IMGUI_MBUT_LEFT : 0, 0, width, height,m_key_char); @@ -1576,19 +1619,31 @@ void debug_imgui::wait_for_debugger(device_t &device, bool firststop) void debug_imgui::debugger_update() { - if(view_main_disasm == nullptr || view_main_regs == nullptr || view_main_console == nullptr) + if(!view_main_disasm || !view_main_regs || !view_main_console || !m_machine || (m_machine->phase() != machine_phase::RUNNING)) return; - if (m_machine && (m_machine->phase() == machine_phase::RUNNING) && !m_machine->debugger().cpu().is_stopped() && !m_hide) + if(!m_machine->debugger().cpu().is_stopped()) { - uint32_t width = m_machine->render().ui_target().width(); - uint32_t height = m_machine->render().ui_target().height(); - m_machine->osd().input_update(true); - handle_mouse(); - handle_keys(); - imguiBeginFrame(m_mouse_x,m_mouse_y,m_mouse_button ? IMGUI_MBUT_LEFT : 0, 0, width, height, m_key_char); - update(); - imguiEndFrame(); + if(m_take_ui) + { + m_take_ui = false; + m_current_pointer = -1; + m_prev_mouse_button = m_mouse_button; + if(m_mouse_button) + { + m_mouse_button = false; + ImGuiIO& io = ImGui::GetIO(); + io.MouseDown[0] = false; + } + } + if(!m_hide) + { + uint32_t width = m_machine->render().ui_target().width(); + uint32_t height = m_machine->render().ui_target().height(); + imguiBeginFrame(m_mouse_x, m_mouse_y, 0, 0, width, height, m_key_char); + update(); + imguiEndFrame(); + } } } diff --git a/src/osd/modules/input/input_dinput.cpp b/src/osd/modules/input/input_dinput.cpp index 7732d610bba..6f539c2f7fc 100644 --- a/src/osd/modules/input/input_dinput.cpp +++ b/src/osd/modules/input/input_dinput.cpp @@ -232,7 +232,7 @@ public: virtual void configure(input_device &device) override; private: - mouse_state m_mouse; + DIMOUSESTATE2 m_mouse; }; dinput_mouse_device::dinput_mouse_device( diff --git a/src/osd/modules/input/input_rawinput.cpp b/src/osd/modules/input/input_rawinput.cpp index d6b99649c4a..437abf598a4 100644 --- a/src/osd/modules/input/input_rawinput.cpp +++ b/src/osd/modules/input/input_rawinput.cpp @@ -433,7 +433,8 @@ public: m_mouse({0}), m_x(0), m_y(0), - m_z(0) + m_v(0), + m_h(0) { } @@ -444,28 +445,44 @@ public: { m_mouse.lX = std::exchange(m_x, 0); m_mouse.lY = std::exchange(m_y, 0); - m_mouse.lZ = std::exchange(m_z, 0); + m_mouse.lV = std::exchange(m_v, 0); + m_mouse.lH = std::exchange(m_h, 0); } } virtual void reset() override { memset(&m_mouse, 0, sizeof(m_mouse)); - m_x = m_y = m_z = 0; + m_x = m_y = m_v = m_h = 0; } virtual void configure(input_device &device) override { // populate the axes - for (int axisnum = 0; axisnum < 3; axisnum++) - { - device.add_item( - default_axis_name[axisnum], - std::string_view(), - input_item_id(ITEM_ID_XAXIS + axisnum), - generic_axis_get_state<LONG>, - &m_mouse.lX + axisnum); - } + device.add_item( + "X", + std::string_view(), + ITEM_ID_XAXIS, + generic_axis_get_state<LONG>, + &m_mouse.lX); + device.add_item( + "Y", + std::string_view(), + ITEM_ID_YAXIS, + generic_axis_get_state<LONG>, + &m_mouse.lY); + device.add_item( + "Scroll V", + std::string_view(), + ITEM_ID_ZAXIS, + generic_axis_get_state<LONG>, + &m_mouse.lV); + device.add_item( + "Scroll H", + std::string_view(), + ITEM_ID_RZAXIS, + generic_axis_get_state<LONG>, + &m_mouse.lH); // populate the buttons for (int butnum = 0; butnum < 5; butnum++) @@ -489,9 +506,11 @@ public: m_x += rawinput.data.mouse.lLastX * input_device::RELATIVE_PER_PIXEL; m_y += rawinput.data.mouse.lLastY * input_device::RELATIVE_PER_PIXEL; - // update Z axis (vertical scroll) + // update Z/Rz axes (vertical/horizontal scroll) if (rawinput.data.mouse.usButtonFlags & RI_MOUSE_WHEEL) - m_z += int16_t(rawinput.data.mouse.usButtonData) * input_device::RELATIVE_PER_PIXEL; + m_v += int16_t(rawinput.data.mouse.usButtonData) * input_device::RELATIVE_PER_PIXEL; + if (rawinput.data.mouse.usButtonFlags & RI_MOUSE_HWHEEL) + m_h += int16_t(rawinput.data.mouse.usButtonData) * input_device::RELATIVE_PER_PIXEL; // update the button states; always update the corresponding mouse buttons if (rawinput.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_1_DOWN) m_mouse.rgbButtons[0] = 0x80; @@ -509,7 +528,7 @@ public: private: mouse_state m_mouse; - LONG m_x, m_y, m_z; + LONG m_x, m_y, m_v, m_h; }; @@ -523,7 +542,8 @@ public: rawinput_lightgun_device(std::string &&name, std::string &&id, input_module &module, HANDLE handle) : rawinput_device(std::move(name), std::move(id), module, handle), m_lightgun({0}), - m_z(0) + m_v(0), + m_h(0) { } @@ -531,13 +551,17 @@ public: { rawinput_device::poll(relative_reset); if (relative_reset) - m_lightgun.lZ = std::exchange(m_z, 0); + { + m_lightgun.lV = std::exchange(m_v, 0); + m_lightgun.lH = std::exchange(m_h, 0); + } } virtual void reset() override { memset(&m_lightgun, 0, sizeof(m_lightgun)); - m_z = 0; + m_v = 0; + m_h = 0; } virtual void configure(input_device &device) override @@ -553,13 +577,19 @@ public: &m_lightgun.lX + axisnum); } - // scroll wheel is always relative if present + // scroll wheels are always relative if present device.add_item( - default_axis_name[2], + "Scroll V", std::string_view(), ITEM_ID_ADD_RELATIVE1, generic_axis_get_state<LONG>, - &m_lightgun.lZ); + &m_lightgun.lV); + device.add_item( + "Scroll H", + std::string_view(), + ITEM_ID_ADD_RELATIVE2, + generic_axis_get_state<LONG>, + &m_lightgun.lH); // populate the buttons for (int butnum = 0; butnum < 5; butnum++) @@ -583,9 +613,11 @@ public: m_lightgun.lX = normalize_absolute_axis(rawinput.data.mouse.lLastX, 0, input_device::ABSOLUTE_MAX); m_lightgun.lY = normalize_absolute_axis(rawinput.data.mouse.lLastY, 0, input_device::ABSOLUTE_MAX); - // update zaxis + // update Z/Rz axes if (rawinput.data.mouse.usButtonFlags & RI_MOUSE_WHEEL) - m_z += int16_t(rawinput.data.mouse.usButtonData) * input_device::RELATIVE_PER_PIXEL; + m_v += int16_t(rawinput.data.mouse.usButtonData) * input_device::RELATIVE_PER_PIXEL; + if (rawinput.data.mouse.usButtonFlags & RI_MOUSE_HWHEEL) + m_h += int16_t(rawinput.data.mouse.usButtonData) * input_device::RELATIVE_PER_PIXEL; // update the button states; always update the corresponding mouse buttons if (rawinput.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_1_DOWN) m_lightgun.rgbButtons[0] = 0x80; @@ -603,7 +635,7 @@ public: private: mouse_state m_lightgun; - LONG m_z; + LONG m_v, m_h; }; diff --git a/src/osd/modules/input/input_sdl.cpp b/src/osd/modules/input/input_sdl.cpp index cc7cb2f1451..62e13cbf960 100644 --- a/src/osd/modules/input/input_sdl.cpp +++ b/src/osd/modules/input/input_sdl.cpp @@ -736,12 +736,13 @@ public: break; case SDL_MOUSEWHEEL: + // adjust SDL 1-per-click to match Win32 120-per-click #if SDL_VERSION_ATLEAST(2, 0, 18) - m_v += event.wheel.preciseY * input_device::RELATIVE_PER_PIXEL; - m_h += event.wheel.preciseX * input_device::RELATIVE_PER_PIXEL; + m_v += std::lround(event.wheel.preciseY * 120 * input_device::RELATIVE_PER_PIXEL); + m_h += std::lround(event.wheel.preciseX * 120 * input_device::RELATIVE_PER_PIXEL); #else - m_v += event.wheel.y * input_device::RELATIVE_PER_PIXEL; - m_h += event.wheel.x * input_device::RELATIVE_PER_PIXEL; + m_v += event.wheel.y * 120 * input_device::RELATIVE_PER_PIXEL; + m_h += event.wheel.x * 120 * input_device::RELATIVE_PER_PIXEL; #endif break; } diff --git a/src/osd/modules/input/input_win32.cpp b/src/osd/modules/input/input_win32.cpp index 3803bc2a180..49c211740c0 100644 --- a/src/osd/modules/input/input_win32.cpp +++ b/src/osd/modules/input/input_win32.cpp @@ -129,13 +129,15 @@ public: // win32_mouse_device //============================================================ -class win32_mouse_device : public event_based_device<MouseButtonEventArgs> +class win32_mouse_device : public event_based_device<MouseUpdateEventArgs> { public: win32_mouse_device(std::string &&name, std::string &&id, input_module &module) : event_based_device(std::move(name), std::move(id), module), m_mouse({0}), - m_win32_mouse({{0}}) + m_win32_mouse({{0}}), + m_vscroll(0), + m_hscroll(0) { } @@ -146,7 +148,7 @@ public: if (!relative_reset) return; - CURSORINFO cursor_info = {0}; + CURSORINFO cursor_info = { 0 }; cursor_info.cbSize = sizeof(CURSORINFO); GetCursorInfo(&cursor_info); @@ -169,23 +171,42 @@ public: SetCursorPos(m_win32_mouse.last_point.x, m_win32_mouse.last_point.y); } + + // update scroll axes + m_mouse.lV = std::exchange(m_vscroll, 0) * input_device::RELATIVE_PER_PIXEL; + m_mouse.lH = std::exchange(m_hscroll, 0) * input_device::RELATIVE_PER_PIXEL; } virtual void configure(input_device &device) override { // populate the axes - for (int axisnum = 0; axisnum < 2; axisnum++) - { - device.add_item( - default_axis_name[axisnum], - std::string_view(), - input_item_id(ITEM_ID_XAXIS + axisnum), - generic_axis_get_state<LONG>, - &m_mouse.lX + axisnum); - } + device.add_item( + "X", + std::string_view(), + ITEM_ID_XAXIS, + generic_axis_get_state<LONG>, + &m_mouse.lX); + device.add_item( + "Y", + std::string_view(), + ITEM_ID_YAXIS, + generic_axis_get_state<LONG>, + &m_mouse.lY); + device.add_item( + "Scroll V", + std::string_view(), + ITEM_ID_ZAXIS, + generic_axis_get_state<LONG>, + &m_mouse.lV); + device.add_item( + "Scroll H", + std::string_view(), + ITEM_ID_RZAXIS, + generic_axis_get_state<LONG>, + &m_mouse.lH); // populate the buttons - for (int butnum = 0; butnum < 2; butnum++) + for (int butnum = 0; butnum < 5; butnum++) { device.add_item( default_button_name(butnum), @@ -200,13 +221,25 @@ public: { memset(&m_mouse, 0, sizeof(m_mouse)); memset(&m_win32_mouse, 0, sizeof(m_win32_mouse)); + m_vscroll = m_hscroll = 0; } protected: - virtual void process_event(MouseButtonEventArgs const &args) override + virtual void process_event(MouseUpdateEventArgs const &args) override { // set the button state - m_mouse.rgbButtons[args.button] = args.keydown ? 0x80 : 0x00; + assert(!(args.pressed & args.released)); + for (unsigned i = 0; 5 > i; ++i) + { + if (BIT(args.pressed, i)) + m_mouse.rgbButtons[i] = 0x80; + else if (BIT(args.released, i)) + m_mouse.rgbButtons[i] = 0x00; + } + + // accumulate scroll delta + m_vscroll += args.vdelta; + m_hscroll += args.hdelta; } private: @@ -217,6 +250,7 @@ private: mouse_state m_mouse; win32_mouse_state m_win32_mouse; + long m_vscroll, m_hscroll; }; @@ -244,14 +278,18 @@ public: virtual bool handle_input_event(input_event eventid, void *eventdata) override { - if (!manager().class_enabled(DEVICE_CLASS_MOUSE) || eventid != INPUT_EVENT_MOUSE_BUTTON) - return false; - - auto const *const args = static_cast<MouseButtonEventArgs *>(eventdata); - devicelist().for_each_device( - [args] (auto &device) { device.queue_events(args, 1); }); + if (manager().class_enabled(DEVICE_CLASS_MOUSE)) + { + if ((eventid == INPUT_EVENT_MOUSE_BUTTON) || (eventid == INPUT_EVENT_MOUSE_WHEEL)) + { + auto const *const args = reinterpret_cast<MouseUpdateEventArgs *>(eventdata); + devicelist().for_each_device( + [args] (auto &device) { device.queue_events(args, 1); }); + return true; + } + } - return true; + return false; } }; @@ -260,7 +298,7 @@ public: // win32_lightgun_device_base //============================================================ -class win32_lightgun_device_base : public event_based_device<MouseButtonEventArgs> +class win32_lightgun_device_base : public event_based_device<MouseUpdateEventArgs> { public: virtual void reset() override @@ -268,7 +306,17 @@ public: memset(&m_mouse, 0, sizeof(m_mouse)); } - virtual void configure(input_device &device) override +protected: + win32_lightgun_device_base( + std::string &&name, + std::string &&id, + input_module &module) : + event_based_device(std::move(name), std::move(id), module), + m_mouse({ 0 }) + { + } + + void do_configure(input_device &device, unsigned buttons) { // populate the axes for (int axisnum = 0; axisnum < 2; axisnum++) @@ -282,7 +330,7 @@ public: } // populate the buttons - for (int butnum = 0; butnum < 2; butnum++) + for (int butnum = 0; butnum < buttons; butnum++) { device.add_item( default_button_name(butnum), @@ -293,16 +341,6 @@ public: } } -protected: - win32_lightgun_device_base( - std::string &&name, - std::string &&id, - input_module &module) : - event_based_device(std::move(name), std::move(id), module), - m_mouse({ 0 }) - { - } - mouse_state m_mouse; }; @@ -319,7 +357,9 @@ public: std::string &&name, std::string &&id, input_module &module) : - win32_lightgun_device_base(std::move(name), std::move(id), module) + win32_lightgun_device_base(std::move(name), std::move(id), module), + m_vscroll(0), + m_hscroll(0) { } @@ -348,14 +388,60 @@ public: // update the X/Y positions m_mouse.lX = xpos; m_mouse.lY = ypos; + + // update the scroll axes if appropriate + if (relative_reset) + { + m_mouse.lV = std::exchange(m_vscroll, 0) * input_device::RELATIVE_PER_PIXEL; + m_mouse.lH = std::exchange(m_hscroll, 0) * input_device::RELATIVE_PER_PIXEL; + } + } + + virtual void configure(input_device &device) override + { + do_configure(device, 5); + + // add scroll axes + device.add_item( + "Scroll V", + std::string_view(), + ITEM_ID_ADD_RELATIVE1, + generic_axis_get_state<LONG>, + &m_mouse.lV); + device.add_item( + "Scroll H", + std::string_view(), + ITEM_ID_ADD_RELATIVE2, + generic_axis_get_state<LONG>, + &m_mouse.lH); + } + + virtual void reset() override + { + win32_lightgun_device_base::reset(); + m_vscroll = m_hscroll = 0; } protected: - virtual void process_event(MouseButtonEventArgs const &args) override + virtual void process_event(MouseUpdateEventArgs const &args) override { // In non-shared axis mode, just update the button state - m_mouse.rgbButtons[args.button] = args.keydown ? 0x80 : 0x00; + assert(!(args.pressed & args.released)); + for (unsigned i = 0; 5 > i; ++i) + { + if (BIT(args.pressed, i)) + m_mouse.rgbButtons[i] = 0x80; + else if (BIT(args.released, i)) + m_mouse.rgbButtons[i] = 0x00; + } + + // accumulate scroll delta + m_vscroll += args.vdelta; + m_hscroll += args.hdelta; } + +private: + long m_vscroll, m_hscroll; }; @@ -376,38 +462,42 @@ public: { } -protected: - virtual void process_event(MouseButtonEventArgs const &args) override + virtual void configure(input_device &device) override { - int const button = args.button; - - if (button > 3) - return; // We only handle the first four buttons in shared axis mode - else if (button >= 2 && m_gun_index == 0) - return; // First gun doesn't handle buttons 2 and 3 - else if (button < 2 && m_gun_index == 1) - return; // Second gun doesn't handle buttons 0 and 1 - - // Adjust the button if we're the second lightgun - int const logical_button = (m_gun_index == 1) ? (button - 2) : button; + do_configure(device, 2); + } - // set the button state - m_mouse.rgbButtons[logical_button] = args.keydown ? 0x80 : 0x00; - if (args.keydown) +protected: + virtual void process_event(MouseUpdateEventArgs const &args) override + { + // We only handle the first four buttons in shared axis mode + assert(!(args.pressed & args.released)); + for (unsigned i = 0; 2 > i; ++i) { - // get the position relative to the window - HWND const hwnd = dynamic_cast<win_window_info &>(*osd_common_t::window_list().front()).platform_window(); - RECT client_rect; - GetClientRect(hwnd, &client_rect); - - POINT mousepos; - mousepos.x = args.xpos; - mousepos.y = args.ypos; - ScreenToClient(hwnd, &mousepos); - - // convert to absolute coordinates - m_mouse.lX = normalize_absolute_axis(mousepos.x, client_rect.left, client_rect.right); - m_mouse.lY = normalize_absolute_axis(mousepos.y, client_rect.top, client_rect.bottom); + // Adjust the button if we're the second lightgun + unsigned const bit = i + ((1 == m_gun_index) ? 2 : 0); + if (BIT(args.pressed, bit)) + { + m_mouse.rgbButtons[i] = 0x80; + + // get the position relative to the window + HWND const hwnd = dynamic_cast<win_window_info &>(*osd_common_t::window_list().front()).platform_window(); + RECT client_rect; + GetClientRect(hwnd, &client_rect); + + POINT mousepos; + mousepos.x = args.xpos; + mousepos.y = args.ypos; + ScreenToClient(hwnd, &mousepos); + + // convert to absolute coordinates + m_mouse.lX = normalize_absolute_axis(mousepos.x, client_rect.left, client_rect.right); + m_mouse.lY = normalize_absolute_axis(mousepos.y, client_rect.top, client_rect.bottom); + } + else if (BIT(args.released, bit)) + { + m_mouse.rgbButtons[i] = 0x00; + } } } @@ -449,14 +539,18 @@ public: virtual bool handle_input_event(input_event eventid, void *eventdata) override { - if (!manager().class_enabled(DEVICE_CLASS_LIGHTGUN) || eventid != INPUT_EVENT_MOUSE_BUTTON) - return false; - - auto const *const args = static_cast<MouseButtonEventArgs *>(eventdata); - devicelist().for_each_device( - [args] (auto &device) { device.queue_events(args, 1); }); + if (manager().class_enabled(DEVICE_CLASS_LIGHTGUN)) + { + if ((eventid == INPUT_EVENT_MOUSE_BUTTON) || (eventid == INPUT_EVENT_MOUSE_WHEEL)) + { + auto const *const args = reinterpret_cast<MouseUpdateEventArgs *>(eventdata); + devicelist().for_each_device( + [args] (auto &device) { device.queue_events(args, 1); }); + return true; + } + } - return true; + return false; } }; diff --git a/src/osd/modules/input/input_wincommon.h b/src/osd/modules/input/input_wincommon.h index d77ab8dce9a..6b34d60abca 100644 --- a/src/osd/modules/input/input_wincommon.h +++ b/src/osd/modules/input/input_wincommon.h @@ -25,12 +25,13 @@ struct keyboard_state int8_t currkey[MAX_KEYS]; }; -// state information for a mouse (matches DIMOUSESTATE exactly) +// state information for a mouse struct mouse_state { LONG lX; LONG lY; - LONG lZ; + LONG lV; + LONG lH; BYTE rgbButtons[8]; }; diff --git a/src/osd/modules/osdwindow.h b/src/osd/modules/osdwindow.h index f3d39878b5e..9167a775682 100644 --- a/src/osd/modules/osdwindow.h +++ b/src/osd/modules/osdwindow.h @@ -15,6 +15,8 @@ #include "osdhelper.h" #include "../frontend/mame/ui/menuitem.h" +#include <cassert> +#include <chrono> #include <memory> #include <string> #include <vector> @@ -94,6 +96,65 @@ public: virtual void complete_destroy() = 0; protected: + static inline constexpr int CLICK_DISTANCE = 16; // in pointer units squared + static inline constexpr int TAP_DISTANCE = 49; // taps are less repeatable than clicks + + struct prev_touch + { + prev_touch() = default; + prev_touch(prev_touch const &) = default; + prev_touch(prev_touch &&) = default; + prev_touch &operator=(prev_touch const &) = default; + prev_touch &operator=(prev_touch &&) = default; + + std::chrono::steady_clock::time_point when = std::chrono::steady_clock::time_point::min(); + int x = 0, y = 0, cnt = 0; + }; + + struct pointer_dev_info + { + pointer_dev_info() = default; + pointer_dev_info(pointer_dev_info const &) = default; + pointer_dev_info(pointer_dev_info &&) = default; + pointer_dev_info &operator=(pointer_dev_info const &) = default; + pointer_dev_info &operator=(pointer_dev_info &&) = default; + + unsigned clear_expired_touches(std::chrono::steady_clock::time_point const &now, std::chrono::steady_clock::duration const &time); + unsigned consume_touch(unsigned i); + + prev_touch touches[8]; + }; + + struct pointer_info + { + pointer_info(pointer_info const &) = default; + pointer_info(pointer_info &&) = default; + pointer_info &operator=(pointer_info const &) = default; + pointer_info &operator=(pointer_info &&) = default; + + pointer_info(unsigned i, unsigned d); + + void primary_down( + int cx, + int cy, + std::chrono::steady_clock::duration const &time, + int tolerance, + bool checkprev, + std::vector<pointer_dev_info> &devices); + void check_primary_hold_drag( + int cx, + int cy, + std::chrono::steady_clock::duration const &time, + int tolerance); + + std::chrono::steady_clock::time_point pressed; + unsigned index, device; + int x, y; + unsigned buttons; + int pressedx, pressedy; + int clickcnt; + }; + osd_window( running_machine &machine, render_module &renderprovider, @@ -127,6 +188,140 @@ private: const std::string m_title; }; + +inline unsigned osd_window::pointer_dev_info::clear_expired_touches( + std::chrono::steady_clock::time_point const &now, + std::chrono::steady_clock::duration const &time) +{ + // find first non-expired touch (if any) + unsigned end(0); + while ((std::size(touches) > end) && touches[end].cnt && ((touches[end].when + time) < now)) + touches[end++].cnt = 0; + + // shift non-expired touches back if necessary + if (end) + { + unsigned pos(0); + while ((std::size(touches) > end) && touches[end].cnt) + { + touches[pos++] = std::move(touches[end]); + touches[end++].cnt = 0; + } + return pos; + } + else + { + while ((std::size(touches) > end) && touches[end].cnt) + ++end; + return end; + } +} + +inline unsigned osd_window::pointer_dev_info::consume_touch(unsigned i) +{ + assert(std::size(touches) > i); + touches[i].cnt = 0; + unsigned pos(i + 1); + while ((std::size(touches) > pos) && touches[pos].cnt) + { + touches[i++] = std::move(touches[pos]); + touches[pos++].cnt = 0; + } + return i; +} + +inline osd_window::pointer_info::pointer_info(unsigned i, unsigned d) : + pressed(std::chrono::steady_clock::time_point::min()), + index(i), + device(d), + x(-1), + y(-1), + buttons(0), + pressedx(0), + pressedy(0), + clickcnt(0) +{ +} + +inline void osd_window::pointer_info::primary_down( + int cx, + int cy, + std::chrono::steady_clock::duration const &time, + int tolerance, + bool checkprev, + std::vector<pointer_dev_info> &devices) +{ + auto const now(std::chrono::steady_clock::now()); + auto const exp(time + pressed); + if (0 > clickcnt) + { + // previous click turned into a hold/drag + clickcnt = 1; + } + else if (clickcnt) + { + // potential multi-click action + int const dx(cx - pressedx); + int const dy(cy - pressedy); + int const distance((dx * dx) + (dy * dy)); + if ((exp < now) || (tolerance < distance)) + clickcnt = 1; + else + ++clickcnt; + } + else + { + // first click for this pointer, but may need to check previous touches + clickcnt = 1; + if (checkprev) + { + if (devices.size() > device) + { + auto &devinfo(devices[device]); + unsigned const end(devinfo.clear_expired_touches(now, time)); + for (int i = 0; end > i; ++i) + { + int const dx(cx - devinfo.touches[i].x); + int const dy(cy - devinfo.touches[i].y); + int const distance((dx * dx) + (dy * dy)); + if (tolerance >= distance) + { + // close match - consume it + clickcnt = devinfo.touches[i].cnt + 1; + devinfo.consume_touch(i); + break; + } + } + } + } + } + + // record the time/location where the pointer was pressed + pressed = now; + pressedx = cx; + pressedy = cy; +} + +inline void osd_window::pointer_info::check_primary_hold_drag( + int cx, + int cy, + std::chrono::steady_clock::duration const &time, + int tolerance) +{ + // check for conversion to a (multi-)click-and-hold/drag + if (0 < clickcnt) + { + auto const now(std::chrono::steady_clock::now()); + auto const exp(time + pressed); + int const dx(cx - pressedx); + int const dy(cy - pressedy); + int const distance((dx * dx) + (dy * dy)); + if ((exp < now) || (tolerance < distance)) + clickcnt = -clickcnt; + } +} + + template <class TWindowHandle> class osd_window_t : public osd_window { diff --git a/src/osd/sdl/osdsdl.cpp b/src/osd/sdl/osdsdl.cpp index 2d3e5e4f02c..87af652733e 100644 --- a/src/osd/sdl/osdsdl.cpp +++ b/src/osd/sdl/osdsdl.cpp @@ -17,8 +17,8 @@ #include "ui/uimain.h" -#include <SDL2/SDL.h> - +#include <algorithm> +#include <cmath> #include <cstdio> #include <cstring> @@ -168,7 +168,9 @@ sdl_osd_interface::sdl_osd_interface(sdl_options &options) : m_modifier_keys(0), m_last_click_time(std::chrono::steady_clock::time_point::min()), m_last_click_x(0), - m_last_click_y(0) + m_last_click_y(0), + m_enable_touch(false), + m_next_ptrdev(0) { } @@ -260,6 +262,34 @@ void sdl_osd_interface::init(running_machine &machine) } } + /* do we want touch support or will we use mouse emulation? */ + m_enable_touch = options().enable_touch(); + try + { + if (m_enable_touch) + { + int const count(SDL_GetNumTouchDevices()); + m_ptrdev_map.reserve(std::max<int>(count + 1, 8)); + map_pointer_device(SDL_MOUSE_TOUCHID); + for (int i = 0; count > i; ++i) + { + SDL_TouchID const device(SDL_GetTouchDevice(i)); + if (device) + map_pointer_device(device); + } + } + else + { + m_ptrdev_map.reserve(1); + map_pointer_device(SDL_MOUSE_TOUCHID); + } + } + catch (std::bad_alloc const &) + { + osd_printf_error("sdl_osd_interface: error allocating pointer data\n"); + // survivable - it will still attempt to allocate mappings when it first sees devices + } + #if defined(SDLMAME_ANDROID) SDL_SetHint(SDL_HINT_VIDEO_EXTERNAL_CONTEXT, "1"); #endif @@ -502,74 +532,126 @@ void sdl_osd_interface::process_events() break; case SDL_MOUSEMOTION: + if (!m_enable_touch || (SDL_TOUCH_MOUSEID != event.motion.which)) { - int cx, cy; - auto const window = focus_window(event.motion); - if (window && window->xy_to_render_target(event.motion.x, event.motion.y, &cx, &cy)) - machine().ui_input().push_mouse_move_event(window->target(), cx, cy); + auto const window = window_from_id(event.motion.windowID); + if (!window) + break; + + unsigned device; + try + { + device = map_pointer_device(SDL_MOUSE_TOUCHID); + } + catch (std::bad_alloc const &) + { + osd_printf_error("sdl_osd_interface: error allocating pointer data\n"); + break; + } + + int x, y; + window->xy_to_render_target(event.motion.x, event.motion.y, &x, &y); + window->mouse_moved(device, x, y); } break; case SDL_MOUSEBUTTONDOWN: - //printf("But down %d %d %d %d %s\n", event.button.which, event.button.button, event.button.x, event.button.y, devinfo->name.c_str()); - if (event.button.button == 1) + case SDL_MOUSEBUTTONUP: + if (!m_enable_touch || (SDL_TOUCH_MOUSEID != event.button.which)) + { + auto const window = window_from_id(event.button.windowID); + if (!window) + break; + + unsigned device; + try + { + device = map_pointer_device(SDL_MOUSE_TOUCHID); + } + catch (std::bad_alloc const &) + { + osd_printf_error("sdl_osd_interface: error allocating pointer data\n"); + break; + } + + int x, y; + window->xy_to_render_target(event.button.x, event.button.y, &x, &y); + unsigned button(event.button.button - 1); + if ((1 == button) || (2 == button)) + button ^= 3; + if (SDL_PRESSED == event.button.state) + window->mouse_down(device, x, y, button); + else + window->mouse_up(device, x, y, button); + } + break; + + case SDL_MOUSEWHEEL: { - int cx, cy; - auto const window = focus_window(event.button); - if (window && window->xy_to_render_target(event.button.x, event.button.y, &cx, &cy)) + auto const window = window_from_id(event.wheel.windowID); + if (window) { - auto const double_click_speed = std::chrono::milliseconds(250); - auto const click = std::chrono::steady_clock::now(); - machine().ui_input().push_mouse_down_event(window->target(), cx, cy); - - // avoid overflow with std::chrono::time_point::min() by adding rather than subtracting - if (click < (m_last_click_time + double_click_speed) - && (cx >= (m_last_click_x - 4) && cx <= (m_last_click_x + 4)) - && (cy >= (m_last_click_y - 4) && cy <= (m_last_click_y + 4))) + unsigned device; + try { - m_last_click_time = std::chrono::time_point<std::chrono::steady_clock>::min(); - machine().ui_input().push_mouse_double_click_event(window->target(), cx, cy); + device = map_pointer_device(SDL_MOUSE_TOUCHID); } - else + catch (std::bad_alloc const &) { - m_last_click_time = click; - m_last_click_x = cx; - m_last_click_y = cy; + osd_printf_error("sdl_osd_interface: error allocating pointer data\n"); + break; } +#if SDL_VERSION_ATLEAST(2, 0, 18) + window->mouse_wheel(device, std::lround(event.wheel.preciseY * 120)); +#else + window->mouse_wheel(device, event.wheel.y); +#endif } } - else if (event.button.button == 3) - { - int cx, cy; - auto const window = focus_window(event.button); - if (window != nullptr && window->xy_to_render_target(event.button.x, event.button.y, &cx, &cy)) - machine().ui_input().push_mouse_rdown_event(window->target(), cx, cy); - } break; - case SDL_MOUSEBUTTONUP: - //printf("But up %d %d %d %d\n", event.button.which, event.button.button, event.button.x, event.button.y); - if (event.button.button == 1) + case SDL_FINGERMOTION: + case SDL_FINGERDOWN: + case SDL_FINGERUP: + if (m_enable_touch && (SDL_MOUSE_TOUCHID != event.tfinger.touchId)) { - int cx, cy; - auto const window = focus_window(event.button); - if (window && window->xy_to_render_target(event.button.x, event.button.y, &cx, &cy)) - machine().ui_input().push_mouse_up_event(window->target(), cx, cy); - } - else if (event.button.button == 3) - { - int cx, cy; - auto window = focus_window(event.button); - if (window && window->xy_to_render_target(event.button.x, event.button.y, &cx, &cy)) - machine().ui_input().push_mouse_rup_event(window->target(), cx, cy); - } - break; + // ignore if it doesn't map to a window we own + auto const window = window_from_id(event.tfinger.windowID); + if (!window) + break; + + // map SDL touch device ID to a zero-based device number + unsigned device; + try + { + device = map_pointer_device(event.tfinger.touchId); + } + catch (std::bad_alloc const &) + { + osd_printf_error("sdl_osd_interface: error allocating pointer data\n"); + break; + } - case SDL_MOUSEWHEEL: - { - auto const window = focus_window(event.wheel); - if (window) - machine().ui_input().push_mouse_wheel_event(window->target(), 0, 0, event.wheel.y, 3); + // convert normalised coordinates to what MAME wants + auto const size = window->get_size(); + int const winx = std::lround(event.tfinger.x * size.width()); + int const winy = std::lround(event.tfinger.y * size.height()); + int x, y; + window->xy_to_render_target(winx, winy, &x, &y); + + // call appropriate window method + switch (event.type) + { + case SDL_FINGERMOTION: + window->finger_moved(event.tfinger.fingerId, device, x, y); + break; + case SDL_FINGERDOWN: + window->finger_down(event.tfinger.fingerId, device, x, y); + break; + case SDL_FINGERUP: + window->finger_up(event.tfinger.fingerId, device, x, y); + break; + } } break; } @@ -626,12 +708,37 @@ void sdl_osd_interface::process_window_event(SDL_Event const &event) break; case SDL_WINDOWEVENT_ENTER: - m_mouse_over_window = 1; + { + m_mouse_over_window = 1; + unsigned device; + try + { + device = map_pointer_device(SDL_MOUSE_TOUCHID); + } + catch (std::bad_alloc const &) + { + osd_printf_error("sdl_osd_interface: error allocating pointer data\n"); + break; + } + window->mouse_entered(device); + } break; case SDL_WINDOWEVENT_LEAVE: - machine().ui_input().push_mouse_leave_event(window->target()); - m_mouse_over_window = 0; + { + m_mouse_over_window = 0; + unsigned device; + try + { + device = map_pointer_device(SDL_MOUSE_TOUCHID); + } + catch (std::bad_alloc const &) + { + osd_printf_error("sdl_osd_interface: error allocating pointer data\n"); + break; + } + window->mouse_left(device); + } break; case SDL_WINDOWEVENT_FOCUS_GAINED: @@ -717,9 +824,28 @@ void sdl_osd_interface::check_osd_inputs() template <typename T> sdl_window_info *sdl_osd_interface::focus_window(T const &event) const { - // FIXME: SDL does not properly report the window for certain OS. - if (false) + // FIXME: SDL does not properly report the window for certain versions of Ubuntu - is this still relevant? + if (m_enable_touch) return window_from_id(event.windowID); else return m_focus_window; } + + +unsigned sdl_osd_interface::map_pointer_device(SDL_TouchID device) +{ + auto devpos(std::lower_bound( + m_ptrdev_map.begin(), + m_ptrdev_map.end(), + device, + [] (std::pair<SDL_TouchID, unsigned> const &mapping, SDL_TouchID id) + { + return mapping.first < id; + })); + if ((m_ptrdev_map.end() == devpos) || (device != devpos->first)) + { + devpos = m_ptrdev_map.emplace(devpos, device, m_next_ptrdev); + ++m_next_ptrdev; + } + return devpos->second; +} diff --git a/src/osd/sdl/osdsdl.h b/src/osd/sdl/osdsdl.h index c8ce167d699..e1d472d9e45 100644 --- a/src/osd/sdl/osdsdl.h +++ b/src/osd/sdl/osdsdl.h @@ -10,11 +10,14 @@ #include "modules/lib/osdobj_common.h" #include "modules/osdmodule.h" +#include <SDL2/SDL.h> + #include <cassert> #include <chrono> #include <memory> #include <mutex> #include <unordered_map> +#include <utility> #include <string> #include <vector> @@ -128,8 +131,6 @@ public: }; -union SDL_Event; - using sdl_event_manager = event_subscription_manager<SDL_Event, uint32_t>; @@ -194,6 +195,8 @@ private: bool mouse_over_window() const { return m_mouse_over_window > 0; } template <typename T> sdl_window_info *focus_window(T const &event) const; + unsigned map_pointer_device(SDL_TouchID device); + sdl_options &m_options; sdl_window_info *m_focus_window; int m_mouse_over_window; @@ -202,6 +205,10 @@ private: std::chrono::steady_clock::time_point m_last_click_time; int m_last_click_x; int m_last_click_y; + + bool m_enable_touch; + unsigned m_next_ptrdev; + std::vector<std::pair<SDL_TouchID, unsigned> > m_ptrdev_map; }; //============================================================ diff --git a/src/osd/sdl/sdlopts.cpp b/src/osd/sdl/sdlopts.cpp index 8e922cb7a4e..f08dc41f963 100644 --- a/src/osd/sdl/sdlopts.cpp +++ b/src/osd/sdl/sdlopts.cpp @@ -66,7 +66,8 @@ const options_entry f_sdl_option_entries[] = { SDLOPTION_KEYMAP_FILE, "keymap.dat", core_options::option_type::PATH, "keymap filename" }, // joystick mapping - { nullptr, nullptr, core_options::option_type::HEADER, "SDL JOYSTICK MAPPING" }, + { nullptr, nullptr, core_options::option_type::HEADER, "SDL INPUT OPTIONS" }, + { SDLOPTION_ENABLE_TOUCH, "0", core_options::option_type::BOOLEAN, "enable touch input support" }, { SDLOPTION_SIXAXIS, "0", core_options::option_type::BOOLEAN, "use special handling for PS3 Sixaxis controllers" }, #if (USE_XINPUT) diff --git a/src/osd/sdl/sdlopts.h b/src/osd/sdl/sdlopts.h index d754a0e8619..fe75e097407 100644 --- a/src/osd/sdl/sdlopts.h +++ b/src/osd/sdl/sdlopts.h @@ -26,6 +26,7 @@ #define SDLOPTION_KEYMAP "keymap" #define SDLOPTION_KEYMAP_FILE "keymap_file" +#define SDLOPTION_ENABLE_TOUCH "enable_touch" #define SDLOPTION_SIXAXIS "sixaxis" #if defined(USE_XINPUT) && USE_XINPUT #define SDLOPTION_LIGHTGUNINDEX "lightgun_index" @@ -82,7 +83,8 @@ public: bool keymap() const { return bool_value(SDLOPTION_KEYMAP); } const char *keymap_file() const { return value(SDLOPTION_KEYMAP_FILE); } - // joystick mapping + // input options + bool enable_touch() const { return bool_value(SDLOPTION_ENABLE_TOUCH); } bool sixaxis() const { return bool_value(SDLOPTION_SIXAXIS); } const char *video_driver() const { return value(SDLOPTION_VIDEODRIVER); } diff --git a/src/osd/sdl/window.cpp b/src/osd/sdl/window.cpp index 4bb8531df2a..1e6318f57a9 100644 --- a/src/osd/sdl/window.cpp +++ b/src/osd/sdl/window.cpp @@ -13,24 +13,27 @@ #include "emuopts.h" #include "render.h" #include "screen.h" +#include "uiinput.h" #include "ui/uimain.h" // OSD headers -#include "window.h" -#include "osdsdl.h" #include "modules/monitor/monitor_common.h" +#include "osdsdl.h" +#include "window.h" // standard SDL headers -#include <SDL2/SDL.h> #include <SDL2/SDL_syswm.h> // standard C headers +#include <algorithm> +#include <cassert> #include <cmath> +#include <list> +#include <memory> + #ifndef _MSC_VER #include <unistd.h> #endif -#include <list> -#include <memory> #ifdef SDLMAME_WIN32 #include <windows.h> @@ -61,11 +64,6 @@ #define SDL_VERSION_EQUALS(v1, vnum2) (SDL_VERSIONNUM(v1.major, v1.minor, v1.patch) == vnum2) -class SDL_DM_Wrapper -{ -public: - SDL_DisplayMode mode; -}; //============================================================ @@ -248,9 +246,9 @@ void sdl_window_info::toggle_full_screen() #endif if (fullscreen() && (video_config.switchres || is_osx)) { - SDL_SetWindowFullscreen(platform_window(), 0); // Try to set mode - SDL_SetWindowDisplayMode(platform_window(), &m_original_mode->mode); // Try to set mode - SDL_SetWindowFullscreen(platform_window(), SDL_WINDOW_FULLSCREEN); // Try to set mode + SDL_SetWindowFullscreen(platform_window(), 0); + SDL_SetWindowDisplayMode(platform_window(), &m_original_mode); + SDL_SetWindowFullscreen(platform_window(), SDL_WINDOW_FULLSCREEN); } SDL_DestroyWindow(platform_window()); set_platform_window(nullptr); @@ -334,6 +332,293 @@ int sdl_window_info::xy_to_render_target(int x, int y, int *xt, int *yt) return renderer().xy_to_render_target(x, y, xt, yt); } +void sdl_window_info::mouse_entered(unsigned device) +{ + m_mouse_inside = true; +} + +void sdl_window_info::mouse_left(unsigned device) +{ + m_mouse_inside = false; + + auto info(std::lower_bound(m_active_pointers.begin(), m_active_pointers.end(), SDL_FingerID(-1), &sdl_pointer_info::compare)); + if ((m_active_pointers.end() == info) || (info->finger != SDL_FingerID(-1))) + return; + + // leaving implicitly releases buttons, so check hold/drag if necessary + if (BIT(info->buttons, 0)) + { + assert(0 <= info->clickcnt); + + auto const now(std::chrono::steady_clock::now()); + auto const exp(std::chrono::milliseconds(250) + info->pressed); + int const dx(info->x - info->pressedx); + int const dy(info->y - info->pressedy); + int const distance((dx * dx) + (dy * dy)); + if ((exp < now) || (CLICK_DISTANCE < distance)) + info->clickcnt = -info->clickcnt; + } + + // push to UI manager + machine().ui_input().push_pointer_leave( + target(), + osd::ui_event_handler::pointer::MOUSE, + info->index, + device, + info->x, info->y, + info->buttons, info->clickcnt); + + // dump pointer data + m_pointer_mask &= ~(decltype(m_pointer_mask)(1) << info->index); + if (info->index < m_next_pointer) + m_next_pointer = info->index; + m_active_pointers.erase(info); +} + +void sdl_window_info::mouse_down(unsigned device, int x, int y, unsigned button) +{ + if (!m_mouse_inside) + return; + + auto const info(map_pointer(SDL_FingerID(-1), device)); + if (m_active_pointers.end() == info) + return; + + if ((x == info->x) && (y == info->y) && BIT(info->buttons, button)) + return; + + // detect multi-click actions + if (0 == button) + { + info->primary_down( + x, + y, + std::chrono::milliseconds(250), + CLICK_DISTANCE, + false, + m_ptrdev_info); + } + + // update info and push to UI manager + auto const pressed(decltype(info->buttons)(1) << button); + info->x = x; + info->y = y; + info->buttons |= pressed; + machine().ui_input().push_pointer_update( + target(), + osd::ui_event_handler::pointer::MOUSE, + info->index, + device, + x, y, + info->buttons, pressed, 0, info->clickcnt); +} + +void sdl_window_info::mouse_up(unsigned device, int x, int y, unsigned button) +{ + if (!m_mouse_inside) + return; + + auto const info(map_pointer(SDL_FingerID(-1), device)); + if (m_active_pointers.end() == info) + return; + + if ((x == info->x) && (y == info->y) && !BIT(info->buttons, button)) + return; + + // detect multi-click actions + if (0 == button) + { + info->check_primary_hold_drag( + x, + y, + std::chrono::milliseconds(250), + CLICK_DISTANCE); + } + + // update info and push to UI manager + auto const released(decltype(info->buttons)(1) << button); + info->x = x; + info->y = y; + info->buttons &= ~released; + machine().ui_input().push_pointer_update( + target(), + osd::ui_event_handler::pointer::MOUSE, + info->index, + device, + x, y, + info->buttons, 0, released, info->clickcnt); +} + +void sdl_window_info::mouse_moved(unsigned device, int x, int y) +{ + if (!m_mouse_inside) + return; + + auto const info(map_pointer(SDL_FingerID(-1), device)); + if (m_active_pointers.end() == info) + return; + + // detect multi-click actions + if (BIT(info->buttons, 0)) + { + info->check_primary_hold_drag( + x, + y, + std::chrono::milliseconds(250), + CLICK_DISTANCE); + } + + // update info and push to UI manager + info->x = x; + info->y = y; + machine().ui_input().push_pointer_update( + target(), + osd::ui_event_handler::pointer::MOUSE, + info->index, + device, + x, y, + info->buttons, 0, 0, info->clickcnt); +} + +void sdl_window_info::mouse_wheel(unsigned device, int y) +{ + if (!m_mouse_inside) + return; + + auto const info(map_pointer(SDL_FingerID(-1), device)); + if (m_active_pointers.end() == info) + return; + + // push to UI manager + machine().ui_input().push_mouse_wheel_event(target(), info->x, info->y, y, 3); +} + +void sdl_window_info::finger_down(SDL_FingerID finger, unsigned device, int x, int y) +{ + auto const info(map_pointer(finger, device)); + if (m_active_pointers.end() == info) + return; + + assert(!info->buttons); + + // detect multi-click actions + info->primary_down( + x, + y, + std::chrono::milliseconds(250), + TAP_DISTANCE, + true, + m_ptrdev_info); + + // update info and push to UI manager + info->x = x; + info->y = y; + info->buttons = 1; + machine().ui_input().push_pointer_update( + target(), + osd::ui_event_handler::pointer::TOUCH, + info->index, + device, + x, y, + 1, 1, 0, info->clickcnt); +} + +void sdl_window_info::finger_up(SDL_FingerID finger, unsigned device, int x, int y) +{ + auto info(std::lower_bound(m_active_pointers.begin(), m_active_pointers.end(), finger, &sdl_pointer_info::compare)); + if ((m_active_pointers.end() == info) || (info->finger != finger)) + return; + + assert(1 == info->buttons); + + // check for conversion to a (multi-)click-and-hold/drag + info->check_primary_hold_drag( + x, + y, + std::chrono::milliseconds(250), + TAP_DISTANCE); + + // need to remember touches to recognise multi-tap gestures + if (0 < info->clickcnt) + { + auto const now(std::chrono::steady_clock::now()); + auto const time = std::chrono::milliseconds(250); + if ((time + info->pressed) >= now) + { + try + { + unsigned i(0); + if (m_ptrdev_info.size() > device) + i = m_ptrdev_info[device].clear_expired_touches(now, time); + else + m_ptrdev_info.resize(device + 1); + + if (std::size(m_ptrdev_info[device].touches) > i) + { + m_ptrdev_info[device].touches[i].when = info->pressed; + m_ptrdev_info[device].touches[i].x = info->pressedx; + m_ptrdev_info[device].touches[i].y = info->pressedy; + m_ptrdev_info[device].touches[i].cnt = info->clickcnt; + } + } + catch (std::bad_alloc const &) + { + osd_printf_error("win_window_info: error allocating pointer data\n"); + } + } + } + + // push to UI manager + machine().ui_input().push_pointer_update( + target(), + osd::ui_event_handler::pointer::TOUCH, + info->index, + device, + x, y, + 0, 0, 1, info->clickcnt); + machine().ui_input().push_pointer_leave( + target(), + osd::ui_event_handler::pointer::TOUCH, + info->index, + device, + x, y, + 0, info->clickcnt); + + // dump pointer data + m_pointer_mask &= ~(decltype(m_pointer_mask)(1) << info->index); + if (info->index < m_next_pointer) + m_next_pointer = info->index; + m_active_pointers.erase(info); +} + +void sdl_window_info::finger_moved(SDL_FingerID finger, unsigned device, int x, int y) +{ + auto info(std::lower_bound(m_active_pointers.begin(), m_active_pointers.end(), finger, &sdl_pointer_info::compare)); + if ((m_active_pointers.end() == info) || (info->finger != finger)) + + assert(1 == info->buttons); + + if ((x != info->x) || (y != info->y)) + { + info->check_primary_hold_drag( + x, + y, + std::chrono::milliseconds(250), + TAP_DISTANCE); + + // update info and push to UI manager + info->x = x; + info->y = y; + machine().ui_input().push_pointer_update( + target(), + osd::ui_event_handler::pointer::TOUCH, + info->index, + device, + x, y, + 1, 0, 0, info->clickcnt); + } +} + //============================================================ // sdlwindow_video_window_create // (main thread) @@ -373,9 +658,9 @@ void sdl_window_info::complete_destroy() if (fullscreen() && video_config.switchres) { - SDL_SetWindowFullscreen(platform_window(), 0); // Try to set mode - SDL_SetWindowDisplayMode(platform_window(), &m_original_mode->mode); // Try to set mode - SDL_SetWindowFullscreen(platform_window(), SDL_WINDOW_FULLSCREEN); // Try to set mode + SDL_SetWindowFullscreen(platform_window(), 0); + SDL_SetWindowDisplayMode(platform_window(), &m_original_mode); + SDL_SetWindowFullscreen(platform_window(), SDL_WINDOW_FULLSCREEN); } renderer_reset(); @@ -684,7 +969,7 @@ int sdl_window_info::complete_create() SDL_DisplayMode mode; //SDL_GetCurrentDisplayMode(window().monitor()->handle, &mode); SDL_GetWindowDisplayMode(platform_window(), &mode); - m_original_mode->mode = mode; + m_original_mode = mode; mode.w = temp.width(); mode.h = temp.height(); if (m_win_config.refresh) @@ -1021,6 +1306,52 @@ osd_dim sdl_window_info::get_max_bounds(int constrain) return maximum.dim(); } + +std::vector<sdl_window_info::sdl_pointer_info>::iterator sdl_window_info::map_pointer(SDL_FingerID finger, unsigned device) +{ + auto found(std::lower_bound(m_active_pointers.begin(), m_active_pointers.end(), finger, &sdl_pointer_info::compare)); + if ((m_active_pointers.end() != found) && (found->finger == finger)) + return found; + + if ((sizeof(m_next_pointer) * 8) <= m_next_pointer) + { + assert(~decltype(m_pointer_mask)(0) == m_pointer_mask); + osd_printf_warning("sdl_window_info: exceeded maximum number of active pointers\n"); + return m_active_pointers.end(); + } + assert(!BIT(m_pointer_mask, m_next_pointer)); + + try + { + found = m_active_pointers.emplace( + found, + sdl_pointer_info(finger, m_next_pointer, device)); + m_pointer_mask |= decltype(m_pointer_mask)(1) << m_next_pointer; + do + { + ++m_next_pointer; + } + while (((sizeof(m_next_pointer) * 8) > m_next_pointer) && BIT(m_pointer_mask, m_next_pointer)); + + return found; + } + catch (std::bad_alloc const &) + { + osd_printf_error("sdl_window_info: error allocating pointer data\n"); + return m_active_pointers.end(); + } +} + + +inline sdl_window_info::sdl_pointer_info::sdl_pointer_info(SDL_FingerID f, unsigned i, unsigned d) + : pointer_info(i, d) + , finger(f) +{ +} + + + + //============================================================ // construction and destruction //============================================================ @@ -1040,13 +1371,18 @@ sdl_window_info::sdl_window_info( , m_extra_flags(0) , m_mouse_captured(false) , m_mouse_hidden(false) + , m_pointer_mask(0) + , m_next_pointer(0) + , m_mouse_inside(false) { //FIXME: these should be per_window in config-> or even better a bit set m_fullscreen = !video_config.windowed; m_prescale = video_config.prescale; m_windowed_dim = osd_dim(config->width, config->height); - m_original_mode = std::make_unique<SDL_DM_Wrapper>(); + + m_ptrdev_info.reserve(1); + m_active_pointers.reserve(16); } sdl_window_info::~sdl_window_info() diff --git a/src/osd/sdl/window.h b/src/osd/sdl/window.h index 77477176b51..49fe452192f 100644 --- a/src/osd/sdl/window.h +++ b/src/osd/sdl/window.h @@ -14,22 +14,20 @@ #include "modules/osdwindow.h" #include "osdsync.h" +#include <SDL2/SDL.h> + +#include <chrono> #include <cstdint> #include <memory> +#include <vector> //============================================================ // TYPE DEFINITIONS //============================================================ -struct SDL_Window; - class render_target; -// forward of SDL_DisplayMode not possible (typedef struct) - define wrapper - -class SDL_DM_Wrapper; - typedef uintptr_t HashT; #define OSDWORK_CALLBACK(name) void *name(void *param, int threadid) @@ -65,26 +63,34 @@ public: int xy_to_render_target(int x, int y, int *xt, int *yt); -private: - // window handle and info - int m_startmaximized; + void mouse_entered(unsigned device); + void mouse_left(unsigned device); + void mouse_down(unsigned device, int x, int y, unsigned button); + void mouse_up(unsigned device, int x, int y, unsigned button); + void mouse_moved(unsigned device, int x, int y); + void mouse_wheel(unsigned device, int y); + void finger_down(SDL_FingerID finger, unsigned device, int x, int y); + void finger_up(SDL_FingerID finger, unsigned device, int x, int y); + void finger_moved(SDL_FingerID finger, unsigned device, int x, int y); - // dimensions - osd_dim m_minimum_dim; - osd_dim m_windowed_dim; +private: + struct sdl_pointer_info : public pointer_info + { + static constexpr bool compare(sdl_pointer_info const &info, SDL_FingerID finger) { return info.finger < finger; } - // rendering info - osd_event m_rendered_event; + sdl_pointer_info(sdl_pointer_info const &) = default; + sdl_pointer_info(sdl_pointer_info &&) = default; + sdl_pointer_info &operator=(sdl_pointer_info const &) = default; + sdl_pointer_info &operator=(sdl_pointer_info &&) = default; - // Original display_mode - std::unique_ptr<SDL_DM_Wrapper> m_original_mode; + sdl_pointer_info(SDL_FingerID, unsigned i, unsigned d); - int m_extra_flags; + SDL_FingerID finger; + }; // returns 0 on success, else 1 int complete_create(); -private: int wnd_extra_width(); int wnd_extra_height(); osd_rect constrain_to_aspect_ratio(const osd_rect &rect, int adjustment); @@ -94,12 +100,35 @@ private: osd_dim pick_best_mode(); void set_fullscreen(int afullscreen) { m_fullscreen = afullscreen; } - // monitor info - bool m_mouse_captured; - bool m_mouse_hidden; - void measure_fps(int update); + std::vector<sdl_pointer_info>::iterator map_pointer(SDL_FingerID finger, unsigned device); + + // window handle and info + int m_startmaximized; + + // dimensions + osd_dim m_minimum_dim; + osd_dim m_windowed_dim; + + // rendering info + osd_event m_rendered_event; + + // Original display_mode + SDL_DisplayMode m_original_mode; + + int m_extra_flags; + + // monitor info + bool m_mouse_captured; + bool m_mouse_hidden; + + // info on currently active pointers - 64 pointers ought to be enough for anyone + uint64_t m_pointer_mask; + unsigned m_next_pointer; + bool m_mouse_inside; + std::vector<pointer_dev_info> m_ptrdev_info; + std::vector<sdl_pointer_info> m_active_pointers; }; #endif // MAME_OSD_SDL_WINDOW_H diff --git a/src/osd/windows/window.cpp b/src/osd/windows/window.cpp index 87467a7e06f..690cc90658c 100644 --- a/src/osd/windows/window.cpp +++ b/src/osd/windows/window.cpp @@ -1,5 +1,5 @@ // license:BSD-3-Clause -// copyright-holders:Aaron Giles +// copyright-holders:Aaron Giles, Vas Crabb //============================================================ // // window.cpp - Win32 window handling @@ -64,6 +64,29 @@ #define WM_USER_SET_MINSIZE (WM_USER + 5) +namespace { + +constexpr unsigned get_pointer_buttons(WPARAM wparam) +{ + return + (IS_POINTER_FIRSTBUTTON_WPARAM(wparam) ? 0x01 : 0x00) | + (IS_POINTER_SECONDBUTTON_WPARAM(wparam) ? 0x02 : 0x00) | + (IS_POINTER_THIRDBUTTON_WPARAM(wparam) ? 0x04 : 0x00) | + (IS_POINTER_FOURTHBUTTON_WPARAM(wparam) ? 0x08 : 0x00) | + (IS_POINTER_FIFTHBUTTON_WPARAM(wparam) ? 0x10 : 0x00); +} + +constexpr osd::ui_event_handler::pointer convert_pointer_type(POINTER_INPUT_TYPE type) +{ + // PT_POINTER isn't a real type, and we'll leave PT_TOUCHPAD as unknown for now + return + (PT_TOUCH == type) ? osd::ui_event_handler::pointer::TOUCH : + (PT_PEN == type) ? osd::ui_event_handler::pointer::PEN : + (PT_MOUSE == type) ? osd::ui_event_handler::pointer::MOUSE : + osd::ui_event_handler::pointer::UNKNOWN; +} + +} // anonymous namespace //============================================================ // GLOBAL VARIABLES @@ -190,6 +213,14 @@ void windows_osd_interface::window_exit() } +inline win_window_info::win_pointer_info::win_pointer_info(WORD p, POINTER_INPUT_TYPE t, unsigned i, unsigned d) + : pointer_info(i, d) + , ptrid(p) + , type(t) +{ +} + + win_window_info::win_window_info( running_machine &machine, render_module &renderprovider, @@ -216,6 +247,10 @@ win_window_info::win_window_info( , m_resize_state(RESIZE_STATE_NORMAL) , m_main(nullptr) , m_attached_mode(false) + , m_pointer_mask(0) + , m_next_pointer(0) + , m_next_ptrdev(0) + , m_tracking_mouse(false) { m_non_fullscreen_bounds.left = 0; m_non_fullscreen_bounds.top = 0; @@ -224,6 +259,10 @@ win_window_info::win_window_info( m_fullscreen = !video_config.windowed; m_prescale = video_config.prescale; + + m_ptrdev_map.reserve(8); + m_ptrdev_info.reserve(1); + m_active_pointers.reserve(16); } POINT win_window_info::s_saved_cursor_pos = { -1, -1 }; @@ -314,9 +353,11 @@ static bool is_mame_window(HWND hwnd) inline static BOOL handle_mouse_button(windows_osd_interface *osd, int button, int down, int x, int y) { - MouseButtonEventArgs args; - args.button = button; - args.keydown = down; + MouseUpdateEventArgs args; + args.pressed = (down ? 1 : 0) << button; + args.released = (down ? 0 : 1) << button; + args.vdelta = 0; + args.hdelta = 0; args.xpos = x; args.ypos = y; @@ -328,6 +369,24 @@ inline static BOOL handle_mouse_button(windows_osd_interface *osd, int button, i return handled && !osd->options().lightgun() && !osd->options().mouse(); } +inline static BOOL handle_mouse_wheel(windows_osd_interface *osd, int v, int h, int x, int y) +{ + MouseUpdateEventArgs args; + args.pressed = 0; + args.released = 0; + args.vdelta = v; + args.hdelta = h; + args.xpos = x; + args.ypos = y; + + bool handled = osd->handle_input_event(INPUT_EVENT_MOUSE_WHEEL, &args); + + // When in lightgun mode or mouse mode, the mouse wheel may be routed to the input system + // because the mouse interactions in the UI are routed from the video_window_proc below + // we need to make sure they aren't suppressed in these cases. + return handled && !osd->options().lightgun() && !osd->options().mouse(); +} + inline static BOOL handle_keypress(windows_osd_interface *osd, int vkey, int down, int scancode, BOOL extended_key) { KeyPressEventArgs args; @@ -408,6 +467,15 @@ void windows_osd_interface::process_events(bool ingame, bool nodispatch) dispatch = !handle_mouse_button(this, 3, FALSE, GET_X_LPARAM(message.lParam), GET_Y_LPARAM(message.lParam)); break; + // forward mouse wheel movement to the input system + case WM_MOUSEWHEEL: + dispatch = !handle_mouse_wheel(this, GET_WHEEL_DELTA_WPARAM(message.wParam), 0, GET_X_LPARAM(message.lParam), GET_Y_LPARAM(message.lParam)); + break; + + case WM_MOUSEHWHEEL: + dispatch = !handle_mouse_wheel(this, 0, GET_WHEEL_DELTA_WPARAM(message.wParam), GET_X_LPARAM(message.lParam), GET_Y_LPARAM(message.lParam)); + break; + case WM_KEYDOWN: if (NOT_ALREADY_DOWN(message.lParam)) dispatch = !handle_keypress(this, message.wParam, TRUE, SCAN_CODE(message.lParam), IS_EXTENDED(message.lParam)); @@ -1066,49 +1134,7 @@ LRESULT CALLBACK win_window_info::video_window_proc(HWND wnd, UINT message, WPAR case WM_SYSKEYDOWN: break; - // input events - case WM_MOUSEMOVE: - window->machine().ui_input().push_mouse_move_event(window->target(), GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam)); - break; - - case WM_MOUSELEAVE: - window->machine().ui_input().push_mouse_leave_event(window->target()); - break; - - case WM_LBUTTONDOWN: - { - auto const ticks = std::chrono::steady_clock::now(); - window->machine().ui_input().push_mouse_down_event(window->target(), GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam)); - - // check for a double-click - avoid overflow by adding times rather than subtracting - if (ticks < (window->m_lastclicktime + std::chrono::milliseconds(GetDoubleClickTime())) && - GET_X_LPARAM(lparam) >= window->m_lastclickx - 4 && GET_X_LPARAM(lparam) <= window->m_lastclickx + 4 && - GET_Y_LPARAM(lparam) >= window->m_lastclicky - 4 && GET_Y_LPARAM(lparam) <= window->m_lastclicky + 4) - { - window->m_lastclicktime = std::chrono::steady_clock::time_point::min(); - window->machine().ui_input().push_mouse_double_click_event(window->target(), GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam)); - } - else - { - window->m_lastclicktime = ticks; - window->m_lastclickx = GET_X_LPARAM(lparam); - window->m_lastclicky = GET_Y_LPARAM(lparam); - } - } - break; - - case WM_LBUTTONUP: - window->machine().ui_input().push_mouse_up_event(window->target(), GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam)); - break; - - case WM_RBUTTONDOWN: - window->machine().ui_input().push_mouse_rdown_event(window->target(), GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam)); - break; - - case WM_RBUTTONUP: - window->machine().ui_input().push_mouse_rup_event(window->target(), GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam)); - break; - + // text input events case WM_CHAR: { char16_t const ch = char16_t(wparam); @@ -1140,14 +1166,61 @@ LRESULT CALLBACK win_window_info::video_window_proc(HWND wnd, UINT message, WPAR window->machine().ui_input().push_char_event(window->target(), char32_t(wparam)); break; + // legacy mouse events + case WM_MOUSEMOVE: + window->mouse_updated(wparam, lparam); + break; + + case WM_MOUSELEAVE: + window->mouse_left(wparam, lparam); + break; + + case WM_LBUTTONDOWN: + window->mouse_updated(wparam, lparam); + break; + + case WM_LBUTTONUP: + window->mouse_updated(wparam, lparam); + break; + + case WM_RBUTTONDOWN: + case WM_RBUTTONUP: + case WM_MBUTTONDOWN: + case WM_MBUTTONUP: + window->mouse_updated(wparam, lparam); + break; + + case WM_XBUTTONDOWN: + case WM_XBUTTONUP: + window->mouse_updated(wparam, lparam); + return TRUE; + case WM_MOUSEWHEEL: { + POINT where{ GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam) }; + ScreenToClient(wnd, &where); UINT ucNumLines = 3; // default SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &ucNumLines, 0); - window->machine().ui_input().push_mouse_wheel_event(window->target(), GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam), GET_WHEEL_DELTA_WPARAM(wparam), ucNumLines); + window->machine().ui_input().push_mouse_wheel_event(window->target(), where.x, where.y, GET_WHEEL_DELTA_WPARAM(wparam), ucNumLines); } break; + // new-style pointer handling (mouse/pen/touch) + case WM_POINTERENTER: + window->pointer_entered(wparam, lparam); + break; + case WM_POINTERLEAVE: + window->pointer_left(wparam, lparam); + break; + case WM_POINTERDOWN: + case WM_POINTERUP: + case WM_POINTERUPDATE: + window->pointer_updated(wparam, lparam); + break; + case WM_POINTERCAPTURECHANGED: + window->pointer_capture_changed(wparam, lparam); + break; + // pause the system when we start a menu or resize case WM_ENTERSIZEMOVE: window->m_resize_state = RESIZE_STATE_RESIZING; @@ -1834,11 +1907,405 @@ win_window_focus win_window_info::focus() const } +void win_window_info::pointer_entered(WPARAM wparam, LPARAM lparam) +{ + assert(!IS_POINTER_CANCELED_WPARAM(wparam)); + auto const info(map_pointer(wparam)); + if (m_active_pointers.end() != info) + { + POINT where{ GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam) }; + ScreenToClient(platform_window(), &where); + info->x = where.x; + info->y = where.y; + machine().ui_input().push_pointer_update( + target(), + convert_pointer_type(info->type), + info->index, + info->device, + where.x, where.y, + 0U, 0U, 0U, 0U); + } +} + +void win_window_info::pointer_left(WPARAM wparam, LPARAM lparam) +{ + auto const info(find_pointer(wparam)); + if (m_active_pointers.end() != info) + { + bool const canceled(IS_POINTER_CANCELED_WPARAM(wparam)); + POINT where{ GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam) }; + ScreenToClient(platform_window(), &where); + expire_pointer(info, where, canceled); + } +} + +void win_window_info::pointer_updated(WPARAM wparam, LPARAM lparam) +{ + bool const canceled(IS_POINTER_CANCELED_WPARAM(wparam)); + auto const info(canceled ? find_pointer(wparam) : map_pointer(wparam)); + if (m_active_pointers.end() != info) + { + POINT where{ GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam) }; + ScreenToClient(platform_window(), &where); + unsigned const buttons(canceled ? 0 : get_pointer_buttons(wparam)); + update_pointer(*info, where, buttons, canceled); + } +} + +void win_window_info::pointer_capture_changed(WPARAM wparam, LPARAM lparam) +{ + auto const info(find_pointer(wparam)); + if (m_active_pointers.end() != info) + { + // treat this as the pointer being stolen - fail any gestures + if (BIT(info->buttons, 0)) + { + assert(0 <= info->clickcnt); + info->clickcnt = -info->clickcnt; + } + + // push to UI manager and dump pointer data + machine().ui_input().push_pointer_abort( + target(), + convert_pointer_type(info->type), + info->index, + info->device, + info->x, info->y, + info->buttons, info->clickcnt); + m_pointer_mask &= ~(decltype(m_pointer_mask)(1) << info->index); + if (info->index < m_next_pointer) + m_next_pointer = info->index; + m_active_pointers.erase(info); + } +} + +void win_window_info::mouse_left(WPARAM wparam, LPARAM lparam) +{ + m_tracking_mouse = false; + auto const info(find_mouse_pointer()); + if (m_active_pointers.end() != info) + { + POINT where{ GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam) }; + expire_pointer(info, where, false); + } +} + +void win_window_info::mouse_updated(WPARAM wparam, LPARAM lparam) +{ + auto const info(map_mouse_pointer()); + if (m_active_pointers.end() == info) + return; + + POINT const where{ GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam) }; + unsigned const buttons( + ((MK_LBUTTON & wparam) ? 0x01 : 0x00) | + ((MK_RBUTTON & wparam) ? 0x02 : 0x00) | + ((MK_MBUTTON & wparam) ? 0x04 : 0x00) | + ((MK_XBUTTON1 & wparam) ? 0x08 : 0x00) | + ((MK_XBUTTON2 & wparam) ? 0x10 : 0x00)); + + // continue to track the mouse outside the window if buttons are down + if (buttons && !info->buttons) + { + SetCapture(platform_window()); + + TRACKMOUSEEVENT tm; + std::memset(&tm, 0, sizeof(tm)); + tm.cbSize = sizeof(tm); + tm.dwFlags = TME_CANCEL | TME_LEAVE; + tm.hwndTrack = platform_window(); + TrackMouseEvent(&tm); + m_tracking_mouse = false; + } + else + { + if (!buttons && info->buttons) + { + if (GetCapture() == platform_window()) + ReleaseCapture(); + } + + if (!m_tracking_mouse) + { + TRACKMOUSEEVENT tm; + std::memset(&tm, 0, sizeof(tm)); + tm.cbSize = sizeof(tm); + tm.dwFlags = TME_LEAVE; + tm.hwndTrack = platform_window(); + TrackMouseEvent(&tm); + m_tracking_mouse = true; + } + } + + update_pointer(*info, where, buttons, false); +} + +void win_window_info::expire_pointer(std::vector<win_pointer_info>::iterator info, POINT const &where, bool canceled) +{ + // leaving implicitly releases buttons, so check hold/drag if necessary + if (BIT(info->buttons, 0)) + { + assert(0 <= info->clickcnt); + if (!canceled) + { + auto const now(std::chrono::steady_clock::now()); + auto const exp(std::chrono::milliseconds(GetDoubleClickTime()) + info->pressed); + int const dx(where.x - info->pressedx); + int const dy(where.y - info->pressedy); + int const distance((dx * dx) + (dy * dy)); + int const tolerance((PT_TOUCH == info->type) ? TAP_DISTANCE : CLICK_DISTANCE); + if ((exp < now) || (tolerance < distance)) + info->clickcnt = -info->clickcnt; + } + else + { + info->clickcnt = -info->clickcnt; + } + } + + // need to remember touches to recognise multi-tap gestures + if (!canceled && (PT_TOUCH == info->type) && (0 < info->clickcnt)) + { + auto const now(std::chrono::steady_clock::now()); + auto const time = std::chrono::milliseconds(GetDoubleClickTime()); + if ((time + info->pressed) >= now) + { + try + { + unsigned i(0); + if (m_ptrdev_info.size() > info->device) + i = m_ptrdev_info[info->device].clear_expired_touches(now, time); + else + m_ptrdev_info.resize(info->device + 1); + + if (std::size(m_ptrdev_info[info->device].touches) > i) + { + m_ptrdev_info[info->device].touches[i].when = info->pressed; + m_ptrdev_info[info->device].touches[i].x = info->pressedx; + m_ptrdev_info[info->device].touches[i].y = info->pressedy; + m_ptrdev_info[info->device].touches[i].cnt = info->clickcnt; + } + } + catch (std::bad_alloc const &) + { + osd_printf_error("win_window_info: error allocating pointer data\n"); + } + } + } + + // push to UI manager and dump pointer data + if (!canceled) + { + machine().ui_input().push_pointer_leave( + target(), + convert_pointer_type(info->type), + info->index, + info->device, + where.x, where.y, + info->buttons, info->clickcnt); + } + else + { + machine().ui_input().push_pointer_abort( + target(), + convert_pointer_type(info->type), + info->index, + info->device, + where.x, where.y, + info->buttons, info->clickcnt); + } + m_pointer_mask &= ~(decltype(m_pointer_mask)(1) << info->index); + if (info->index < m_next_pointer) + m_next_pointer = info->index; + m_active_pointers.erase(info); +} + +void win_window_info::update_pointer(win_pointer_info &info, POINT const &where, unsigned buttons, bool canceled) +{ + if (!canceled && (where.x == info.x) && (where.y == info.y) && (buttons == info.buttons)) + return; + + // detect multi-click actions + unsigned const pressed(canceled ? 0 : (buttons & ~info.buttons)); + unsigned const released(canceled ? ~info.buttons : (~buttons & info.buttons)); + if (BIT(pressed, 0)) + { + info.primary_down( + where.x, where.y, + std::chrono::milliseconds(GetDoubleClickTime()), + (PT_TOUCH == info.type) ? TAP_DISTANCE : CLICK_DISTANCE, + PT_TOUCH == info.type, + m_ptrdev_info); + } + else if (BIT(info.buttons, 0)) + { + info.check_primary_hold_drag( + where.x, where.y, + std::chrono::milliseconds(GetDoubleClickTime()), + (PT_TOUCH == info.type) ? TAP_DISTANCE : CLICK_DISTANCE); + } + + // update info and push to UI manager + info.x = where.x; + info.y = where.y; + info.buttons = buttons; + if (!canceled) + { + machine().ui_input().push_pointer_update( + target(), + convert_pointer_type(info.type), + info.index, + info.device, + where.x, where.y, + buttons, pressed, released, info.clickcnt); + } + else + { + machine().ui_input().push_pointer_abort( + target(), + convert_pointer_type(info.type), + info.index, + info.device, + where.x, where.y, + released, info.clickcnt); + } +} + +std::vector<win_window_info::win_pointer_info>::iterator win_window_info::map_pointer(WPARAM wparam) +{ + WORD const ptrid(GET_POINTERID_WPARAM(wparam)); + auto found(std::lower_bound(m_active_pointers.begin(), m_active_pointers.end(), ptrid, &win_pointer_info::compare)); + if ((m_active_pointers.end() != found) && (found->ptrid == ptrid)) + return found; + + if ((sizeof(m_next_pointer) * 8) <= m_next_pointer) + { + assert(~decltype(m_pointer_mask)(0) == m_pointer_mask); + osd_printf_warning("win_window_info: exceeded maximum number of active pointers\n"); + return m_active_pointers.end(); + } + assert(!BIT(m_pointer_mask, m_next_pointer)); + + POINTER_INFO info = { 0 }; + if (!OSD_DYNAMIC_CALL(GetPointerInfo, ptrid, &info)) + { + osd_printf_error("win_window_info: failed to get info for pointer ID %u\n", ptrid); + return m_active_pointers.end(); + } + + auto devpos(std::lower_bound( + m_ptrdev_map.begin(), + m_ptrdev_map.end(), + info.sourceDevice, + [] (std::pair<HANDLE, unsigned> const &mapping, HANDLE device) + { + return mapping.first < device; + })); + + try + { + if ((m_ptrdev_map.end() == devpos) || (devpos->first != info.sourceDevice)) + { + devpos = m_ptrdev_map.emplace(devpos, info.sourceDevice, m_next_ptrdev); + ++m_next_ptrdev; + } + + found = m_active_pointers.emplace( + found, + win_pointer_info(ptrid, info.pointerType, m_next_pointer, devpos->second)); + m_pointer_mask |= decltype(m_pointer_mask)(1) << m_next_pointer; + do + { + ++m_next_pointer; + } + while (((sizeof(m_next_pointer) * 8) > m_next_pointer) && BIT(m_pointer_mask, m_next_pointer)); + + return found; + } + catch (std::bad_alloc const &) + { + osd_printf_error("win_window_info: error allocating pointer data\n"); + return m_active_pointers.end(); + } +} + +std::vector<win_window_info::win_pointer_info>::iterator win_window_info::find_pointer(WPARAM wparam) +{ + WORD const ptrid(GET_POINTERID_WPARAM(wparam)); + auto const found(std::lower_bound(m_active_pointers.begin(), m_active_pointers.end(), ptrid, &win_pointer_info::compare)); + if ((m_active_pointers.end() != found) && (found->ptrid == ptrid)) + return found; + else + return m_active_pointers.end(); +} + +std::vector<win_window_info::win_pointer_info>::iterator win_window_info::map_mouse_pointer() +{ + WORD const ptrid(~WORD(0)); + auto found(std::lower_bound(m_active_pointers.begin(), m_active_pointers.end(), ptrid, &win_pointer_info::compare)); + if ((m_active_pointers.end() != found) && (found->ptrid == ptrid)) + return found; + + if ((sizeof(m_next_pointer) * 8) <= m_next_pointer) + { + assert(~decltype(m_pointer_mask)(0) == m_pointer_mask); + osd_printf_warning("win_window_info: exceeded maximum number of active pointers\n"); + return m_active_pointers.end(); + } + assert(!BIT(m_pointer_mask, m_next_pointer)); + + auto devpos(std::lower_bound( + m_ptrdev_map.begin(), + m_ptrdev_map.end(), + INVALID_HANDLE_VALUE, + [] (std::pair<HANDLE, unsigned> const &mapping, HANDLE device) + { + return mapping.first < device; + })); + + try + { + if ((m_ptrdev_map.end() == devpos) || (devpos->first != INVALID_HANDLE_VALUE)) + { + devpos = m_ptrdev_map.emplace(devpos, INVALID_HANDLE_VALUE, m_next_ptrdev); + ++m_next_ptrdev; + } + + found = m_active_pointers.emplace( + found, + win_pointer_info(ptrid, PT_MOUSE, m_next_pointer, devpos->second)); + m_pointer_mask |= decltype(m_pointer_mask)(1) << m_next_pointer; + do + { + ++m_next_pointer; + } + while (((sizeof(m_next_pointer) * 8) > m_next_pointer) && BIT(m_pointer_mask, m_next_pointer)); + + return found; + } + catch (std::bad_alloc const &) + { + osd_printf_error("win_window_info: error allocating pointer data\n"); + return m_active_pointers.end(); + } +} + +std::vector<win_window_info::win_pointer_info>::iterator win_window_info::find_mouse_pointer() +{ + WORD const ptrid(~WORD(0)); + auto const found(std::lower_bound(m_active_pointers.begin(), m_active_pointers.end(), ptrid, &win_pointer_info::compare)); + if ((m_active_pointers.end() != found) && (found->ptrid == ptrid)) + return found; + else + return m_active_pointers.end(); +} + + +#if (USE_QTDEBUG) //============================================================ // winwindow_qt_filter //============================================================ -#if (USE_QTDEBUG) bool winwindow_qt_filter(void *message) { MSG *msg = (MSG *)message; diff --git a/src/osd/windows/window.h b/src/osd/windows/window.h index 53021e43a78..bdfd8a79b61 100644 --- a/src/osd/windows/window.h +++ b/src/osd/windows/window.h @@ -1,5 +1,5 @@ // license:BSD-3-Clause -// copyright-holders:Aaron Giles +// copyright-holders:Aaron Giles, Vas Crabb //============================================================ // // window.h - Win32 window handling @@ -128,6 +128,21 @@ public: int m_resize_state; private: + struct win_pointer_info : public pointer_info + { + static constexpr bool compare(win_pointer_info const &info, WORD ptrid) { return info.ptrid < ptrid; } + + win_pointer_info(win_pointer_info const &) = default; + win_pointer_info(win_pointer_info &&) = default; + win_pointer_info &operator=(win_pointer_info const &) = default; + win_pointer_info &operator=(win_pointer_info &&) = default; + + win_pointer_info(WORD p, POINTER_INPUT_TYPE t, unsigned i, unsigned d); + + WORD ptrid; + POINTER_INPUT_TYPE type; + }; + void draw_video_contents(HDC dc, bool update); int complete_create(); int wnd_extra_width(); @@ -141,9 +156,38 @@ private: void adjust_window_position_after_major_change(); void set_fullscreen(int fullscreen); + // pointer handling helpers + void pointer_entered(WPARAM wparam, LPARAM lparam); + void pointer_left(WPARAM wparam, LPARAM lparam); + void pointer_updated(WPARAM wparam, LPARAM lparam); + void pointer_capture_changed(WPARAM wparam, LPARAM lparam); + void mouse_left(WPARAM wparam, LPARAM lparam); + void mouse_updated(WPARAM wparam, LPARAM lparam); + void expire_pointer(std::vector<win_pointer_info>::iterator info, POINT const &where, bool canceled); + void update_pointer(win_pointer_info &info, POINT const &where, unsigned buttons, bool canceled); + std::vector<win_pointer_info>::iterator map_pointer(WPARAM wparam); + std::vector<win_pointer_info>::iterator find_pointer(WPARAM wparam); + std::vector<win_pointer_info>::iterator map_mouse_pointer(); + std::vector<win_pointer_info>::iterator find_mouse_pointer(); + win_window_info * m_main; bool m_attached_mode; + // these functions first appear in Windows 8/Server 2012 + OSD_DYNAMIC_API(user32, "User32.dll", "User32.dll"); + OSD_DYNAMIC_API_FN(user32, BOOL, WINAPI, GetPointerType, UINT32, POINTER_INPUT_TYPE *); + OSD_DYNAMIC_API_FN(user32, BOOL, WINAPI, GetPointerInfo, UINT32, POINTER_INFO *); + OSD_DYNAMIC_API_FN(user32, BOOL, WINAPI, GetPointerPenInfo, UINT32, POINTER_PEN_INFO *); + OSD_DYNAMIC_API_FN(user32, BOOL, WINAPI, GetPointerTouchInfo, UINT32, POINTER_TOUCH_INFO *); + + // info on currently active pointers - 64 pointers ought to be enough for anyone + uint64_t m_pointer_mask; + unsigned m_next_pointer, m_next_ptrdev; + bool m_tracking_mouse; + std::vector<std::pair<HANDLE, unsigned> > m_ptrdev_map; + std::vector<pointer_dev_info> m_ptrdev_info; + std::vector<win_pointer_info> m_active_pointers; + static POINT s_saved_cursor_pos; }; diff --git a/src/osd/windows/winmain.h b/src/osd/windows/winmain.h index bb96ec2a3e4..2ba8335c175 100644 --- a/src/osd/windows/winmain.h +++ b/src/osd/windows/winmain.h @@ -30,7 +30,8 @@ enum input_event INPUT_EVENT_RAWINPUT, INPUT_EVENT_ARRIVAL, INPUT_EVENT_REMOVAL, - INPUT_EVENT_MOUSE_BUTTON + INPUT_EVENT_MOUSE_BUTTON, + INPUT_EVENT_MOUSE_WHEEL }; struct KeyPressEventArgs @@ -40,10 +41,12 @@ struct KeyPressEventArgs uint8_t scancode; }; -struct MouseButtonEventArgs +struct MouseUpdateEventArgs { - int button; - int keydown; + unsigned pressed; + unsigned released; + int vdelta; + int hdelta; int xpos; int ypos; }; |