Files
FileManager/ContextMenu.cpp
T
HoRnEyDvL 06741b208d Code Refactor Cleanup
Added Comments

Added Clear Cache folder on E & Format XYZ Partitions
2025-09-02 14:26:19 +10:00

241 lines
8.6 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "ContextMenu.h"
#include "GfxPrims.h"
#include <wchar.h>
/*
===============================================================================
ContextMenu
- Small, controller-driven popup menu for the file browser.
- Works on OG Xbox (XDK / VS2003): ANSI strings, no STL allocations here.
- Supports non-selectable separator rows that are skipped by navigation.
Life cycle:
* AddItem() / AddSeparator()
* OpenAt(x,y,width,rowH) -> becomes active and edge-debounced
* Draw(font, device) -> render when m_open = true
* OnPad(pad, outAct) -> handles movement / choose / close
* Close() -> hide
Input:
* D-Pad Up/Down or stick Y to move selection (skips separators/disabled)
* A to choose (CHOSEN result + outAct set)
* B or X to close (CLOSED result)
* Start ignored here (caller handles global Start if needed)
Rendering:
* Simple framed panel with dynamic header height, divider line,
then N rows of fixed height (m_rowH).
===============================================================================
*/
ContextMenu::ContextMenu(){
m_count=0; m_sel=0;
m_open=false; m_waitRelease=false;
m_x=0; m_y=0; m_w=320; m_rowH=28;
m_prevA=m_prevB=m_prevX=m_prevWhite=m_prevBlack=0;
m_prevButtons=0;
}
void ContextMenu::Clear(){ m_count=0; m_sel=0; }
// Add a selectable row
void ContextMenu::AddItem(const char* label, Action act, bool enabled){
if (m_count >= (int)(sizeof(m_items)/sizeof(m_items[0]))) return;
m_items[m_count].label = label;
m_items[m_count].act = act;
m_items[m_count].enabled = enabled;
m_items[m_count].separator = false;
++m_count;
}
// Add a non-selectable separator row (drawn as a thin line)
void ContextMenu::AddSeparator(){
if (m_count >= (int)(sizeof(m_items)/sizeof(m_items[0]))) return;
m_items[m_count].label = ""; // not used for separators
m_items[m_count].act = ACT_OPEN; // placeholder; ignored
m_items[m_count].enabled = false;
m_items[m_count].separator = true; // <-- key bit
++m_count;
}
// Row can be chosen iff enabled and not a separator
bool ContextMenu::IsSelectable(int idx) const{
if (idx < 0 || idx >= m_count) return false;
const Item& it = m_items[idx];
return (it.enabled && !it.separator);
}
// Scan forward/backward for the next selectable row; return -1 if none
int ContextMenu::FindNextSelectable(int start, int dir) const{
int i = start;
while (i >= 0 && i < m_count){
if (IsSelectable(i)) return i;
i += dir;
}
return -1;
}
// Position/size the menu and make it active.
// Also snaps the selection onto a valid (selectable) row and
// arms a small "wait for release" window so the A/X that opened
// the menu doesnt immediately trigger a choose/close here.
void ContextMenu::OpenAt(float x, float y, float width, float rowH){
m_x=x; m_y=y; m_w=width; m_rowH=rowH;
if (m_sel < 0) m_sel = 0;
if (m_sel >= m_count) m_sel = (m_count>0)?(m_count-1):0;
// If current selection is not selectable, try forward then backward.
if (!IsSelectable(m_sel)){
int fwd = FindNextSelectable(m_sel, +1);
int back = FindNextSelectable(m_sel, -1);
if (fwd >= 0) m_sel = fwd;
else if (back >= 0) m_sel = back;
else m_sel = 0; // no selectable items; harmless default
}
m_open=true;
m_waitRelease=true; // avoid immediate A/X carry-over
m_prevA=m_prevB=m_prevX=m_prevWhite=m_prevBlack=0;
m_prevButtons=0;
}
void ContextMenu::Close(){ m_open=false; }
// --- tiny ANSI->wide draw helpers (XDK fonts are wide) ----------------------
void ContextMenu::DrawAnsi(CXBFont& font, FLOAT x, FLOAT y, DWORD color, const char* text){
WCHAR wbuf[512]; MultiByteToWideChar(CP_ACP,0,text,-1,wbuf,512);
font.DrawText(x,y,color,wbuf,0,0.0f);
}
void ContextMenu::DrawRect(LPDIRECT3DDEVICE8 dev, float x,float y,float w,float h,D3DCOLOR c){
DrawSolidRect(dev, x, y, w, h, c);
}
// Render the menu panel + rows. Separators are drawn as thin centered lines.
// Disabled rows are dimmed and cannot be focused via navigation.
void ContextMenu::Draw(CXBFont& font, LPDIRECT3DDEVICE8 dev) const{
if (!m_open || m_count <= 0) return;
const FLOAT menuW = m_w;
const FLOAT rowH = m_rowH;
// --- measure header text height (ANSI -> wide, then GetTextExtent) ---
FLOAT hdrW = 0.0f, hdrH = 0.0f;
{
const char* title = "Select action";
WCHAR wbuf[128];
MultiByteToWideChar(CP_ACP, 0, title, -1, wbuf, 128);
font.GetTextExtent(wbuf, &hdrW, &hdrH);
}
// Layout constants (dynamic header height support)
const FLOAT headerTopPad = 8.0f;
const FLOAT headerBottomPad = 6.0f;
const FLOAT bottomPad = 12.0f;
const FLOAT x = m_x;
const FLOAT y = m_y;
const FLOAT lineY = y + headerTopPad + hdrH + headerBottomPad; // divider Y
const FLOAT listTop = lineY + 6.0f; // first row Y
const FLOAT menuH = (listTop - y) + (m_count * rowH) + bottomPad;
// Frame/background
DrawRect(dev, x - 6.0f, y - 6.0f, menuW + 12.0f, menuH + 12.0f, 0xA0101010);
DrawRect(dev, x, y, menuW, menuH, 0xE0222222);
// Header
DrawAnsi(font, x + 10.0f, y + headerTopPad, 0xFFFFFFFF, "Select action");
DrawRect(dev, x, lineY, menuW, 1.0f, 0x60FFFFFF);
// Rows
for (int i = 0; i < m_count; ++i){
const FLOAT rowY = listTop + i * rowH;
const Item& it = m_items[i];
if (it.separator){
// Non-selectable divider line centered within the row box
DrawRect(dev, x + 10.0f, rowY + rowH * 0.5f, menuW - 20.0f, 1.0f, 0x50FFFFFF);
continue;
}
// Selection highlight (only meaningful on selectable rows)
bool sel = (i == m_sel);
D3DCOLOR row = sel ? 0x60FFFF00 : 0x20202020;
DrawRect(dev, x + 6.0f, rowY - 2.0f, menuW - 12.0f, rowH, row);
// Text color: normal/selected/disabled
DWORD col = it.enabled ? (sel ? 0xFF202020 : 0xFFE0E0E0) : 0xFF7A7A7A;
// Measure label height for vertical centering
FLOAT tw = 0.0f, th = 0.0f;
{
WCHAR wbuf[256];
MultiByteToWideChar(CP_ACP, 0, it.label, -1, wbuf, 256);
font.GetTextExtent(wbuf, &tw, &th);
}
const FLOAT textY = rowY + (rowH - th) * 0.5f;
DrawAnsi(font, x + 16.0f, textY, col, it.label);
}
}
// Handle controller input for the menu.
// Returns CHOSEN with an Action when A is pressed on a selectable row,
// CLOSED when B or X is pressed, or NOOP if nothing to do this frame.
ContextMenu::Result ContextMenu::OnPad(const XBGAMEPAD& pad, Action& outAct){
outAct = ACT_OPEN; // harmless default
if (!m_open) return NOOP;
const DWORD btn = pad.wButtons;
unsigned char a = pad.bAnalogButtons[XINPUT_GAMEPAD_A];
unsigned char b = pad.bAnalogButtons[XINPUT_GAMEPAD_B];
unsigned char x = pad.bAnalogButtons[XINPUT_GAMEPAD_X];
// Debounce: absorb the input that opened the menu (prevents immediate choose/close)
if (m_waitRelease){
bool held = (a>30)||(b>30)||(x>30)||
(btn & XINPUT_GAMEPAD_START)||
(btn & XINPUT_GAMEPAD_DPAD_UP)||
(btn & XINPUT_GAMEPAD_DPAD_DOWN);
m_prevA=a; m_prevB=b; m_prevX=x; m_prevButtons=btn;
m_prevWhite = pad.bAnalogButtons[XINPUT_GAMEPAD_WHITE];
m_prevBlack = pad.bAnalogButtons[XINPUT_GAMEPAD_BLACK];
if (!held) m_waitRelease=false;
return NOOP;
}
// Navigation (edge + analog)
bool up = ((btn & XINPUT_GAMEPAD_DPAD_UP) && !(m_prevButtons & XINPUT_GAMEPAD_DPAD_UP)) || (pad.sThumbLY > 16000);
bool down = ((btn & XINPUT_GAMEPAD_DPAD_DOWN) && !(m_prevButtons & XINPUT_GAMEPAD_DPAD_DOWN)) || (pad.sThumbLY < -16000);
if (up){
int i = FindNextSelectable(m_sel - 1, -1);
if (i >= 0) m_sel = i;
}
if (down){
int i = FindNextSelectable(m_sel + 1, +1);
if (i >= 0) m_sel = i;
}
// Button edges
bool aTrig = (a > 30 && m_prevA <= 30);
bool bTrig = (b > 30 && m_prevB <= 30);
bool xTrig = (x > 30 && m_prevX <= 30);
// Save previous for next frame's edge detection
m_prevButtons = btn;
m_prevA = a; m_prevB = b; m_prevX = x;
m_prevWhite = pad.bAnalogButtons[XINPUT_GAMEPAD_WHITE];
m_prevBlack = pad.bAnalogButtons[XINPUT_GAMEPAD_BLACK];
// Choose / close behavior
if (aTrig){
const Item& it = m_items[m_sel];
if (IsSelectable(m_sel)){ outAct = it.act; return CHOSEN; }
return NOOP; // ignore A on non-selectable (e.g., separator)
}
if (bTrig || xTrig) return CLOSED;
return NOOP;
}