diff options
Diffstat (limited to 'src')
108 files changed, 7792 insertions, 3201 deletions
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; }; |