// license:BSD-3-Clause // copyright-holders:Nicola Salmoria, Aaron Giles, Nathan Woods /********************************************************************* ui/videoopt.cpp Internal menus for video options *********************************************************************/ #include "emu.h" #include "ui/videoopt.h" #include "rendfont.h" #include "rendlay.h" #include "rendutil.h" #include namespace ui { namespace { constexpr uintptr_t ITEM_ROTATE = 0x00000100; constexpr uintptr_t ITEM_ZOOM = 0x00000101; constexpr uintptr_t ITEM_UNEVENSTRETCH = 0x00000102; constexpr uintptr_t ITEM_KEEPASPECT = 0x00000103; constexpr uintptr_t ITEM_POINTERTIMEOUT = 0x00000104; constexpr uintptr_t ITEM_TOGGLE_FIRST = 0x00000200; constexpr uintptr_t ITEM_VIEW_FIRST = 0x00000300; } // anonymous namespace /*------------------------------------------------- menu_video_targets_populate - populate the video targets menu -------------------------------------------------*/ menu_video_targets::menu_video_targets(mame_ui_manager &mui, render_container &container) : menu(mui, container) { set_heading(_("Video Options")); } menu_video_targets::~menu_video_targets() { } void menu_video_targets::populate() { // find the targets for (unsigned targetnum = 0; ; targetnum++) { // stop when we run out render_target *const target = machine().render().target_by_index(targetnum); if (!target) break; // add a menu item item_append(util::string_format(_("Screen #%d"), targetnum), 0, target); } // add option for snapshot target item_append("Snapshot", 0, &machine().video().snapshot_target()); item_append(menu_item_type::SEPARATOR); } /*------------------------------------------------- menu_video_targets - handle the video targets menu -------------------------------------------------*/ bool menu_video_targets::handle(event const *ev) { if (ev && (ev->iptkey == IPT_UI_SELECT)) { render_target *const target = reinterpret_cast(ev->itemref); menu::stack_push( ui(), container(), std::string(selected_item().text()), *target, &machine().video().snapshot_target() == target); } return false; } /*------------------------------------------------- menu_video_options_populate - populate the video options menu -------------------------------------------------*/ menu_video_options::menu_video_options( mame_ui_manager &mui, render_container &container, std::string_view title, render_target &target, bool snapshot) : menu(mui, container) , m_target(target) , m_snapshot(snapshot) { set_heading(util::string_format(_("Video Options: %1$s"), title)); if (!m_snapshot || !machine().video().snap_native()) { set_selected_index(target.view()); reset(reset_options::REMEMBER_POSITION); } } menu_video_options::~menu_video_options() { } void menu_video_options::populate() { uintptr_t ref; // add items for each view if (!m_snapshot || !machine().video().snap_native()) { for (char const *name = m_target.view_name(ref = 0); name; name = m_target.view_name(++ref)) item_append(name, convert_command_glyph(ref == m_target.view() ? "_>" : "_<"), 0, reinterpret_cast(ITEM_VIEW_FIRST + ref)); item_append(menu_item_type::SEPARATOR); } // add items for visibility toggles layout_view const &curview = m_target.current_view(); auto const &toggles = curview.visibility_toggles(); if (!toggles.empty()) { ref = 0U; auto const current_mask(m_target.visibility_mask()); for (auto toggle = toggles.begin(); toggles.end() != toggle; ++toggle, ++ref) { auto const toggle_mask(toggle->mask()); bool const enabled(BIT(current_mask, ref)); bool eclipsed(false); for (auto it = toggles.begin(); !eclipsed && (toggle != it); ++it) eclipsed = ((current_mask & it->mask()) != it->mask()) && ((toggle_mask & it->mask()) == it->mask()); item_append_on_off(toggle->name(), enabled, eclipsed ? (FLAG_INVERT | FLAG_DISABLE) : 0U, reinterpret_cast(ITEM_TOGGLE_FIRST + ref)); } item_append(menu_item_type::SEPARATOR); } const char *subtext = ""; // add a rotate item switch (m_target.orientation()) { case ROT0: subtext = "None"; break; case ROT90: subtext = u8"CW 90°"; break; case ROT180: subtext = u8"180°"; break; case ROT270: subtext = u8"CCW 90°"; break; } item_append(_("Rotate"), subtext, FLAG_LEFT_ARROW | FLAG_RIGHT_ARROW, reinterpret_cast(ITEM_ROTATE)); // cropping bool const canzoom(curview.has_art() && !curview.visible_screens().empty()); item_append_on_off(_("Zoom to Screen Area"), m_target.zoom_to_screen(), canzoom ? 0U : (FLAG_INVERT | FLAG_DISABLE), reinterpret_cast(ITEM_ZOOM)); if (!m_snapshot) { // uneven stretch switch (m_target.scale_mode()) { case SCALE_FRACTIONAL: subtext = _("On"); break; case SCALE_FRACTIONAL_X: subtext = _("X Only"); break; case SCALE_FRACTIONAL_Y: subtext = _("Y Only"); break; case SCALE_FRACTIONAL_AUTO: subtext = _("X or Y (Auto)"); break; case SCALE_INTEGER: subtext = _("Off"); break; } item_append(_("Non-Integer Scaling"), subtext, FLAG_LEFT_ARROW | FLAG_RIGHT_ARROW, reinterpret_cast(ITEM_UNEVENSTRETCH)); // keep aspect item_append_on_off(_("Maintain Aspect Ratio"), m_target.keepaspect(), 0, reinterpret_cast(ITEM_KEEPASPECT)); } // add pointer display options if (!m_target.hidden()) { item_append(menu_item_type::SEPARATOR); // use millisecond precision for timeout display auto const timeout = std::chrono::duration_cast(ui().pointer_activity_timeout(m_target.index())); bool const hide = ui().hide_inactive_pointers(m_target.index()); if (hide) { int const precision = (timeout.count() % 10) ? 3 : (timeout.count() % 100) ? 2 : 1; item_append( _("Hide Inactive Pointers After Delay"), util::string_format(_("%1$.*2$f s"), timeout.count() * 1e-3, precision), ((timeout > std::chrono::milliseconds(100)) ? FLAG_LEFT_ARROW : 0) | FLAG_RIGHT_ARROW, reinterpret_cast(ITEM_POINTERTIMEOUT)); } else { item_append(_("Hide Inactive Pointers After Delay"), _("Never"), FLAG_LEFT_ARROW, reinterpret_cast(ITEM_POINTERTIMEOUT)); } } item_append(menu_item_type::SEPARATOR); } /*------------------------------------------------- menu_video_options - handle the video options menu -------------------------------------------------*/ bool menu_video_options::handle(event const *ev) { auto const lockout_popup( [this] () { machine().popmessage(_("Cannot change options while recording!")); return true; }); bool const snap_lockout(m_snapshot && machine().video().is_recording()); bool changed(false); set_process_flags((reinterpret_cast(get_selection_ref()) == ITEM_POINTERTIMEOUT) ? PROCESS_LR_REPEAT : 0); // process the menu if (ev && uintptr_t(ev->itemref)) { switch (reinterpret_cast(ev->itemref)) { // rotate adds rotation depending on the direction case ITEM_ROTATE: if (ev->iptkey == IPT_UI_LEFT || ev->iptkey == IPT_UI_RIGHT) { if (snap_lockout) return lockout_popup(); int const delta((ev->iptkey == IPT_UI_LEFT) ? ROT270 : ROT90); m_target.set_orientation(orientation_add(delta, m_target.orientation())); if (m_target.is_ui_target()) { render_container::user_settings settings = container().get_user_settings(); settings.m_orientation = orientation_add(delta ^ ROT180, settings.m_orientation); container().set_user_settings(settings); } changed = true; } break; // layer config bitmasks handle left/right keys the same (toggle) case ITEM_ZOOM: if ((ev->iptkey == IPT_UI_LEFT) || (ev->iptkey == IPT_UI_RIGHT)) { if (snap_lockout) return lockout_popup(); m_target.set_zoom_to_screen(ev->iptkey == IPT_UI_RIGHT); changed = true; } break; // non-integer scaling: rotate through options case ITEM_UNEVENSTRETCH: if (ev->iptkey == IPT_UI_LEFT) { if (snap_lockout) return lockout_popup(); switch (m_target.scale_mode()) { case SCALE_FRACTIONAL: m_target.set_scale_mode(SCALE_INTEGER); break; case SCALE_FRACTIONAL_X: m_target.set_scale_mode(SCALE_FRACTIONAL); break; case SCALE_FRACTIONAL_Y: m_target.set_scale_mode(SCALE_FRACTIONAL_X); break; case SCALE_FRACTIONAL_AUTO: m_target.set_scale_mode(SCALE_FRACTIONAL_Y); break; case SCALE_INTEGER: m_target.set_scale_mode(SCALE_FRACTIONAL_AUTO); break; } changed = true; } else if (ev->iptkey == IPT_UI_RIGHT) { if (snap_lockout) return lockout_popup(); switch (m_target.scale_mode()) { case SCALE_FRACTIONAL: m_target.set_scale_mode(SCALE_FRACTIONAL_X); break; case SCALE_FRACTIONAL_X: m_target.set_scale_mode(SCALE_FRACTIONAL_Y); break; case SCALE_FRACTIONAL_Y: m_target.set_scale_mode(SCALE_FRACTIONAL_AUTO); break; case SCALE_FRACTIONAL_AUTO: m_target.set_scale_mode(SCALE_INTEGER); break; case SCALE_INTEGER: m_target.set_scale_mode(SCALE_FRACTIONAL); break; } changed = true; } break; // keep aspect handles left/right keys identically (toggle) case ITEM_KEEPASPECT: if ((ev->iptkey == IPT_UI_LEFT) || (ev->iptkey == IPT_UI_RIGHT)) { if (snap_lockout) return lockout_popup(); m_target.set_keepaspect(ev->iptkey == IPT_UI_RIGHT); changed = true; } break; // pointer inactivity timeout case ITEM_POINTERTIMEOUT: if (ev->iptkey == IPT_UI_SELECT) { // toggle hide after delay ui().set_hide_inactive_pointers(m_target.index(), !ui().hide_inactive_pointers(m_target.index())); changed = true; } else if (ev->iptkey == IPT_UI_LEFT) { if (!ui().hide_inactive_pointers(m_target.index())) { ui().set_hide_inactive_pointers(m_target.index(), true); ui().set_pointer_activity_timeout(m_target.index(), std::chrono::milliseconds(10'000)); changed = true; } else { bool const ctrl_pressed = machine().input().code_pressed(KEYCODE_LCONTROL) || machine().input().code_pressed(KEYCODE_RCONTROL); std::chrono::milliseconds const increment(ctrl_pressed ? 1'000 : 100); auto timeout = ui().pointer_activity_timeout(m_target.index()); auto const remainder = timeout % increment; timeout -= remainder.count() ? remainder : increment; if (std::chrono::milliseconds(100) <= timeout) { ui().set_pointer_activity_timeout(m_target.index(), timeout); changed = true; } } } else if (ev->iptkey == IPT_UI_RIGHT) { if (ui().hide_inactive_pointers(m_target.index())) { auto const timeout = ui().pointer_activity_timeout(m_target.index()); if (std::chrono::milliseconds(10'000) <= timeout) { ui().set_hide_inactive_pointers(m_target.index(), false); } else { bool const ctrl_pressed = machine().input().code_pressed(KEYCODE_LCONTROL) || machine().input().code_pressed(KEYCODE_RCONTROL); int const increment(ctrl_pressed ? 1'000 : 100); ui().set_pointer_activity_timeout( m_target.index(), std::chrono::milliseconds((1 + (timeout / std::chrono::milliseconds(increment))) * increment)); } changed = true; } } break; // anything else is a view item default: if (reinterpret_cast(ev->itemref) >= ITEM_VIEW_FIRST) { if (snap_lockout) return lockout_popup(); if (ev->iptkey == IPT_UI_SELECT) { m_target.set_view(reinterpret_cast(ev->itemref) - ITEM_VIEW_FIRST); changed = true; } } else if (reinterpret_cast(ev->itemref) >= ITEM_TOGGLE_FIRST) { if (snap_lockout) return lockout_popup(); if ((ev->iptkey == IPT_UI_LEFT) || (ev->iptkey == IPT_UI_RIGHT)) { m_target.set_visibility_toggle(reinterpret_cast(ev->itemref) - ITEM_TOGGLE_FIRST, ev->iptkey == IPT_UI_RIGHT); changed = true; } } break; } } // if something changed, rebuild the menu if (changed) reset(reset_options::REMEMBER_REF); return false; } } // namespace ui