mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-02 06:40:42 -07:00
64600826b7
## Link to GitHub Issue or related Pull Request, if one exists n/a ## Description of change Touch piano mode. Allows piano keys to be played with the touch screen. Since the touch screen is still needed for menu navigation (including during songs) a toggle switch is added so that the user can flip between navigation mode and piano mode. The touch targets are pixel-perfect. Don't let the game's piano animations fool you. ## Testing
150 lines
6.3 KiB
C++
150 lines
6.3 KiB
C++
#include "nostalgia_touch_piano.h"
|
|
|
|
#include <cmath>
|
|
|
|
#include "external/imgui/imgui_internal.h"
|
|
#include "games/nost/touch_mode.h"
|
|
|
|
namespace overlay::windows {
|
|
|
|
static constexpr float BUTTON_WIDTH = 144.f;
|
|
static constexpr float BUTTON_HEIGHT = 40.f;
|
|
static constexpr float WINDOW_PADDING = 4.f;
|
|
static constexpr float EDGE_MARGIN = 4.f;
|
|
static constexpr float PIANO_HEIGHT_RATIO = 0.08f;
|
|
static constexpr float PIANO_LEFT_GAP = 11.f;
|
|
static constexpr float PIANO_RIGHT_GAP = 10.f;
|
|
static constexpr uint32_t PIANO_KEY_COUNT = 28;
|
|
|
|
static constexpr ImU32 PIANO_KEY_COLOR = IM_COL32(255, 255, 255, 50);
|
|
static constexpr ImU32 PIANO_KEY_ACTIVE_COLOR = IM_COL32(255, 48, 48, 160);
|
|
static constexpr ImU32 PIANO_KEY_BORDER_COLOR = IM_COL32(0, 0, 0, 100);
|
|
|
|
struct ButtonPalette {
|
|
ImVec4 normal;
|
|
ImVec4 hovered;
|
|
ImVec4 active;
|
|
};
|
|
|
|
static const ButtonPalette NAV_MODE_PALETTE {
|
|
ImVec4(0.10f, 0.45f, 0.28f, 0.72f),
|
|
ImVec4(0.14f, 0.58f, 0.36f, 0.82f),
|
|
ImVec4(0.08f, 0.34f, 0.21f, 0.90f),
|
|
};
|
|
static const ButtonPalette PIANO_MODE_PALETTE {
|
|
ImVec4(0.15f, 0.32f, 0.62f, 0.72f),
|
|
ImVec4(0.20f, 0.42f, 0.78f, 0.82f),
|
|
ImVec4(0.10f, 0.24f, 0.50f, 0.90f),
|
|
};
|
|
|
|
static void draw_piano_keys(
|
|
const ImVec2 &display_size,
|
|
LONG client_width,
|
|
uint32_t key_state) {
|
|
|
|
if (display_size.x <= 0.f || display_size.y <= 0.f || client_width <= 0) {
|
|
return;
|
|
}
|
|
|
|
// this is only a visual guide; native touch routing owns the actual input
|
|
const float left_gap = PIANO_LEFT_GAP * display_size.x / client_width;
|
|
const float right_gap = PIANO_RIGHT_GAP * display_size.x / client_width;
|
|
const float piano_width = display_size.x - left_gap - right_gap;
|
|
if (piano_width <= 0.f) {
|
|
return;
|
|
}
|
|
|
|
const float key_width = piano_width / PIANO_KEY_COUNT;
|
|
const float key_top = display_size.y * (1.f - PIANO_HEIGHT_RATIO);
|
|
auto *draw_list = ImGui::GetBackgroundDrawList();
|
|
|
|
for (uint32_t key = 0; key < PIANO_KEY_COUNT; key++) {
|
|
const ImVec2 key_min(left_gap + key * key_width, key_top);
|
|
const ImVec2 key_max(left_gap + (key + 1) * key_width, display_size.y);
|
|
const bool active = (key_state & (UINT32_C(1) << key)) != 0;
|
|
draw_list->AddRectFilled(
|
|
key_min,
|
|
key_max,
|
|
active ? PIANO_KEY_ACTIVE_COLOR : PIANO_KEY_COLOR);
|
|
draw_list->AddRect(key_min, key_max, PIANO_KEY_BORDER_COLOR);
|
|
}
|
|
}
|
|
|
|
NostalgiaTouchPiano::NostalgiaTouchPiano(SpiceOverlay *overlay) : Window(overlay) {
|
|
this->title = "Nostalgia Touch Piano";
|
|
this->flags = ImGuiWindowFlags_NoTitleBar
|
|
| ImGuiWindowFlags_NoResize
|
|
| ImGuiWindowFlags_NoCollapse
|
|
| ImGuiWindowFlags_NoMove
|
|
| ImGuiWindowFlags_NoDocking
|
|
| ImGuiWindowFlags_NoBackground
|
|
| ImGuiWindowFlags_NoSavedSettings
|
|
| ImGuiWindowFlags_NoNav
|
|
| ImGuiWindowFlags_NoBringToFrontOnFocus;
|
|
this->window_padding = overlay::apply_scaling_to_vector(WINDOW_PADDING, WINDOW_PADDING);
|
|
this->set_active(true);
|
|
}
|
|
|
|
void NostalgiaTouchPiano::calculate_initial_window() {
|
|
this->init_size = overlay::apply_scaling_to_vector(
|
|
BUTTON_WIDTH + WINDOW_PADDING * 2,
|
|
BUTTON_HEIGHT + WINDOW_PADDING * 2);
|
|
this->init_pos = overlay::apply_scaling_to_vector(EDGE_MARGIN, EDGE_MARGIN);
|
|
}
|
|
|
|
void NostalgiaTouchPiano::build_content() {
|
|
// keep the control anchored while the game window changes size or mode
|
|
ImGui::SetWindowPos(
|
|
overlay::apply_scaling_to_vector(EDGE_MARGIN, EDGE_MARGIN),
|
|
ImGuiCond_Always);
|
|
|
|
// stay above regular overlay windows, but never cover a blocking modal
|
|
ImGuiWindow *mode_window = ImGui::GetCurrentWindow();
|
|
if (ImGuiWindow *modal = ImGui::GetTopMostPopupModal()) {
|
|
ImGui::BringWindowToDisplayBehind(mode_window, modal);
|
|
} else {
|
|
ImGui::BringWindowToDisplayFront(mode_window);
|
|
}
|
|
|
|
const bool nav_mode =
|
|
games::nost::touch_mode::current_mode() == games::nost::touch_mode::Mode::Nav;
|
|
const char *label = nav_mode ? "Nav Mode" : "Piano Mode";
|
|
|
|
// make the active routing mode recognizable without reading the label
|
|
const auto &palette = nav_mode ? NAV_MODE_PALETTE : PIANO_MODE_PALETTE;
|
|
ImGui::PushStyleColor(ImGuiCol_Button, palette.normal);
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, palette.hovered);
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonActive, palette.active);
|
|
ImGui::Button(label, overlay::apply_scaling_to_vector(BUTTON_WIDTH, BUTTON_HEIGHT));
|
|
ImGui::PopStyleColor(3);
|
|
|
|
const auto &io = ImGui::GetIO();
|
|
RECT client_rect {};
|
|
if (io.DisplaySize.x > 0.f && io.DisplaySize.y > 0.f &&
|
|
GetClientRect(this->overlay->get_window(), &client_rect)) {
|
|
|
|
// convert the rendered imgui rectangle into the client coordinates
|
|
// used by hardware touch publication and piano-key mapping
|
|
const auto item_min = ImGui::GetItemRectMin();
|
|
const auto item_max = ImGui::GetItemRectMax();
|
|
const auto client_width = client_rect.right - client_rect.left;
|
|
const auto client_height = client_rect.bottom - client_rect.top;
|
|
if (!nav_mode) {
|
|
draw_piano_keys(
|
|
io.DisplaySize,
|
|
client_width,
|
|
games::nost::touch_mode::piano_key_state());
|
|
}
|
|
|
|
RECT button_bounds {
|
|
static_cast<LONG>(std::lround(item_min.x * client_width / io.DisplaySize.x)),
|
|
static_cast<LONG>(std::lround(item_min.y * client_height / io.DisplaySize.y)),
|
|
static_cast<LONG>(std::lround(item_max.x * client_width / io.DisplaySize.x)),
|
|
static_cast<LONG>(std::lround(item_max.y * client_height / io.DisplaySize.y)),
|
|
};
|
|
games::nost::touch_mode::publish_button_bounds(
|
|
this->overlay->get_window(), button_bounds);
|
|
}
|
|
}
|
|
}
|