mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-02 14:50:41 -07:00
nost: touch piano mode (#830)
## 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
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
#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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user