//------------------------------------------------------------------------------ // OG Xbox File Browser (Original Xbox XDK) — VS2003 friendly // Dual-pane GUI with auto layout, context menu, basic file ops, and rename OSD //------------------------------------------------------------------------------ #include "stdafx.h" // Remove if you disabled PCH #include #include #include #include #include #include "XBApp.h" #include "XBFont.h" #include "XBInput.h" extern "C" { typedef struct _STRING { USHORT Length; USHORT MaximumLength; PCHAR Buffer; } STRING, *PSTRING; LONG __stdcall IoCreateSymbolicLink(PSTRING SymbolicLinkName, PSTRING DeviceName); LONG __stdcall IoDeleteSymbolicLink(PSTRING SymbolicLinkName); } //#pragma comment(lib, "xboxkrnl.lib") #ifndef INVALID_FILE_ATTRIBUTES #define INVALID_FILE_ATTRIBUTES 0xFFFFFFFF #endif // --- Simple on-screen keyboard layout (fixed grid, VS2003-safe) --- static const char s_kb_r0[] = "ABCDEFGHIJKL"; // 12 static const char s_kb_r1[] = "MNOPQRSTUVWX"; // 12 static const char s_kb_r2[] = "YZ0123456789"; // 12 static const char s_kb_r3[] = "-_.()[]{}+&"; // 12 (punctuation row) static const int s_kb_cols[5] = {12,12,12,12,5}; // last row: Back, Space, Aa, OK, Cancel // small helpers (avoid max macro surprises in VS2003) static inline FLOAT MaxF(FLOAT a, FLOAT b){ return (a>b)?a:b; } static inline int MaxI(int a, int b){ return (a>b)?a:b; } static inline FLOAT Snap(FLOAT v){ return (FLOAT)((int)(v + 0.5f)); } // integer pixel snap static const char* kRoots[] = { "C:\\", "D:\\", "E:\\", "F:\\", "G:\\", "X:\\", "Y:\\", "Z:\\" }; static const int kNumRoots = sizeof(kRoots)/sizeof(kRoots[0]); static int g_presentIdx[16]; static int g_presentCount = 0; // -------------------- text / size / path helpers -------------------- static inline void 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); } static inline void FormatSize(ULONGLONG sz, char* out, size_t cap){ const char* unit="B"; double v=(double)sz; if(sz>=(1ULL<<30)){ v/=(double)(1ULL<<30); unit="GB"; } else if(sz>=(1ULL<<20)){ v/=(double)(1ULL<<20); unit="MB"; } else if(sz>=(1ULL<<10)){ v/=(double)(1ULL<<10); unit="KB"; } _snprintf(out,(int)cap,(unit[0]=='B')?"%.0f %s":"%.1f %s",v,unit); out[cap-1]=0; } static inline void GetDriveFreeTotal(const char* anyPathInDrive, ULONGLONG& freeBytes, ULONGLONG& totalBytes){ freeBytes=0; totalBytes=0; char root[8]; _snprintf(root,sizeof(root),"%c:\\",anyPathInDrive[0]); root[sizeof(root)-1]=0; ULARGE_INTEGER a,t,f; a.QuadPart=0; t.QuadPart=0; f.QuadPart=0; if(GetDiskFreeSpaceExA(root,&a,&t,&f)){ freeBytes=f.QuadPart; totalBytes=t.QuadPart; } } static inline void BuildString(STRING& s,const char* z){ USHORT L=(USHORT)strlen(z); s.Length=L; s.MaximumLength=L+1; s.Buffer=(PCHAR)z; } static inline void MakeDosString(char* out,size_t cap,const char* letter){ _snprintf(out,(int)cap,"\\??\\%s",letter); out[cap-1]=0; } static inline void JoinPath(char* dst,size_t cap,const char* base,const char* name){ size_t bl=strlen(base); if(bl&&base[bl-1]=='\\') _snprintf(dst,(int)cap,"%s%s",base,name); else _snprintf(dst,(int)cap,"%s\\%s",base,name); dst[cap-1]=0; } static inline void EnsureTrailingSlash(char* s,size_t cap){ size_t n=strlen(s); if(n&&s[n-1]!='\\'&&n+1& out){ out.clear(); for(int j=0;jb.isDir; return ci_cmp(a.name,b.name)<0; } static bool ListDirectory(const char* path,std::vector& out){ out.clear(); if(strlen(path)>3){ Item up; ZeroMemory(&up,sizeof(up)); strncpy(up.name,"..",3); up.isDir=true; up.size=0; up.isUpEntry=true; out.push_back(up); } char base[512]; _snprintf(base,sizeof(base),"%s",path); base[sizeof(base)-1]=0; EnsureTrailingSlash(base,sizeof(base)); char mask[512]; _snprintf(mask,sizeof(mask),"%s*",base); mask[sizeof(mask)-1]=0; WIN32_FIND_DATAA fd; ZeroMemory(&fd,sizeof(fd)); HANDLE h=FindFirstFileA(mask,&fd); if(h==INVALID_HANDLE_VALUE) return false; do{ const char* n=fd.cFileName; if(!strcmp(n,".")||!strcmp(n,"..")) continue; Item it; ZeroMemory(&it,sizeof(it)); strncpy(it.name,n,255); it.name[255]=0; it.isDir=(fd.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY)!=0; it.size=(((ULONGLONG)fd.nFileSizeHigh)<<32)|fd.nFileSizeLow; it.isUpEntry=false; out.push_back(it); }while(FindNextFileA(h,&fd)); FindClose(h); size_t start=(strlen(path)>3)?1:0; if(out.size()>start+1) std::sort(out.begin()+(int)start,out.end(),ItemLess); return true; } static bool IsDriveRoot(const char* p){ return p && strlen(p)==3 && p[1]==':' && p[2]=='\\'; } // -------------------- App (dual-pane + menu) -------------------- class FileBrowserApp : public CXBApplication { public: // pane state struct Pane { std::vector items; char curPath[512]; int mode; // 0=drives, 1=dir int sel; int scroll; Pane(){ curPath[0]=0; mode=0; sel=0; scroll=0; } }; CXBFont m_font; int m_visible; // rows unsigned char m_prevA, m_prevB, m_prevX, m_prevY; DWORD m_prevButtons; // last frame's digital buttons unsigned char m_prevWhite, m_prevBlack; // last frame's white/black Pane m_pane[2]; int m_active; // 0=left, 1=right bool m_navUDHeld; // up/down currently held? int m_navUDDir; // -1 up, +1 down DWORD m_navUDNext; // next time (ms) another step is allowed // TL vertex for filled rects struct TLVERT { float x,y,z,rhw; D3DCOLOR color; }; enum { FVF_TLVERT = D3DFVF_XYZRHW | D3DFVF_DIFFUSE }; // Layout (left pane base + right pane offset) — computed at runtime static FLOAT kListX_L, kListY, kListW, kLineH; static FLOAT kHdrX_L, kHdrY, kHdrW, kHdrH; static FLOAT kGutterW, kPaddingX, kScrollBarW; static FLOAT kPaneGap; // space between panes // --- Context menu/state --- enum Action { ACT_OPEN, ACT_COPY, ACT_MOVE, ACT_DELETE, ACT_RENAME, ACT_MKDIR, ACT_CALCSIZE, ACT_GOROOT, ACT_SWITCHMEDIA }; struct MenuItem { const char* label; Action act; bool enabled; }; bool m_menuOpen; int m_menuSel; MenuItem m_menu[9]; int m_menuCount; enum { MODE_BROWSE, MODE_MENU, MODE_RENAME } m_mode; // footer status char m_status[256]; DWORD m_statusUntilMs; // --- Rename UI state --- bool m_renActive; char m_renParent[512]; char m_renOld[256]; char m_renBuf[256]; int m_renCursor; // insertion cursor in m_renBuf int m_renSelRow; // keyboard selection int m_renSelCol; bool m_kbLower; // case mode FileBrowserApp(){ m_visible=13; m_prevA=0; m_prevB=0; m_prevX=0; m_prevY=0; m_active=0; m_prevButtons = 0; m_prevWhite = m_prevBlack = 0; m_navUDHeld = false; m_navUDDir = 0; m_navUDNext = 0; ZeroMemory(&m_d3dpp,sizeof(m_d3dpp)); m_d3dpp.BackBufferWidth=1280; m_d3dpp.BackBufferHeight=720; m_d3dpp.BackBufferFormat=D3DFMT_X8R8G8B8; m_d3dpp.SwapEffect=D3DSWAPEFFECT_DISCARD; m_d3dpp.FullScreen_RefreshRateInHz=60; m_d3dpp.EnableAutoDepthStencil=TRUE; m_d3dpp.AutoDepthStencilFormat=D3DFMT_D24S8; m_d3dpp.Flags = D3DPRESENTFLAG_PROGRESSIVE | D3DPRESENTFLAG_WIDESCREEN; m_menuOpen=false; m_menuSel=0; m_menuCount=0; m_mode=MODE_BROWSE; m_status[0]=0; m_statusUntilMs=0; m_renActive=false; m_renParent[0]=0; m_renOld[0]=0; m_renBuf[0]=0; m_renCursor=0; m_renSelRow=0; m_renSelCol=0; m_kbLower=false; } // --- draw prims void DrawRect(float x,float y,float w,float h,D3DCOLOR c){ TLVERT v[4]; v[0].x=x; v[0].y=y; v[1].x=x+w; v[1].y=y; v[2].x=x; v[2].y=y+h; v[3].x=x+w; v[3].y=y+h; for(int i=0;i<4;i++){ v[i].z=0.0f; v[i].rhw=1.0f; v[i].color=c; } m_pd3dDevice->SetTexture(0,NULL); m_pd3dDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_DISABLE); m_pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_DISABLE); m_pd3dDevice->SetVertexShader(FVF_TLVERT); m_pd3dDevice->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP,2,v,sizeof(TLVERT)); } void DrawHLine(float x,float y,float w,D3DCOLOR c){ DrawRect(x,y,w,1.0f,c); } void DrawVLine(float x,float y,float h,D3DCOLOR c){ DrawRect(x,y,1.0f,h,c); } // --- layout helpers/columns static FLOAT NameColX(FLOAT baseX){ return baseX + kGutterW + kPaddingX; } static FLOAT HdrX (FLOAT baseX){ return baseX - 15.0f; } // --- text measurement + right-align FLOAT MeasureTextW(const char* s){ WCHAR wbuf[256]; MultiByteToWideChar(CP_ACP, 0, s, -1, wbuf, 256); FLOAT w=0.0f, h=0.0f; m_font.GetTextExtent(wbuf, &w, &h); return w; } // Returns both width & height for centering void MeasureTextWH(const char* s, FLOAT& outW, FLOAT& outH){ WCHAR wbuf[256]; MultiByteToWideChar(CP_ACP, 0, s, -1, wbuf, 256); m_font.GetTextExtent(wbuf, &outW, &outH); } void DrawRightAligned(const char* s, FLOAT rightX, FLOAT y, DWORD color){ FLOAT w = MeasureTextW(s); DrawAnsi(m_font, rightX - w, y, color, s); } // --- size column measurement (dynamic per pane) FLOAT ComputeSizeColW(const Pane& p){ FLOAT maxW = MeasureTextW("Size"); int limit = (int)p.items.size(); if(limit>200) limit=200; char buf[64]; for(int i=0;imaxW) maxW=w; } } maxW += 16.0f; // padding const FLOAT minW = MaxF(90.0f, kLineH * 4.0f); const FLOAT maxWClamp = kListW * 0.40f; if(maxW < minW) maxW = minW; if(maxW > maxWClamp) maxW = maxWClamp; return maxW; } // --- status void SetStatus(const char* fmt, ...){ va_list ap; va_start(ap, fmt); _vsnprintf(m_status, sizeof(m_status), fmt, ap); va_end(ap); m_status[sizeof(m_status)-1]=0; m_statusUntilMs = GetTickCount() + 3000; // 3s } void SetStatusLastErr(const char* prefix){ DWORD e = GetLastError(); char msg[64]; _snprintf(msg, sizeof(msg), "%s (err=%lu)", prefix, (unsigned long)e); msg[sizeof(msg)-1]=0; SetStatus("%s", msg); } // Return true if we can create & delete a temp file in 'dir' bool CanWriteHereA(const char* dir){ char test[512]; JoinPath(test, sizeof(test), dir, ".__xwtest$__"); test[sizeof(test)-1]=0; HANDLE h = CreateFileA(test, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, NULL); if (h == INVALID_HANDLE_VALUE) return false; CloseHandle(h); DeleteFileA(test); return true; } // normalize dir for things like "E:" -> "E:\" void NormalizeDirA(char* s){ size_t n = strlen(s); if (n==2 && s[1]==':'){ s[2]='\\'; s[3]=0; return; } EnsureTrailingSlash(s, 512); } // FATX-ish name rules: disallow control chars and obvious bad ones bool IsBadFatxChar(char c){ if ((unsigned char)c < 32) return true; const char* bad = "\\/:*?\"<>|+,;=[]"; return (strchr(bad, c) != NULL); } void SanitizeFatxNameInPlace(char* s){ for (char* p=s; *p; ++p) if (IsBadFatxChar(*p)) *p = '_'; int n = (int)strlen(s); while (n>0 && (s[n-1]==' ' || s[n-1]=='.')) s[--n]=0; if (n > 42) { s[42]=0; n=42; } if (n==0 || (strcmp(s,".")==0) || (strcmp(s,"..")==0)) strcpy(s, "NewName"); } // pick char at (row,col) for rows 0-3 of keyboard (respects case) char KbCharAt(int row, int col){ char ch = 0; if (row==0) ch = s_kb_r0[col]; else if (row==1) ch = s_kb_r1[col]; else if (row==2) ch = s_kb_r2[col]; else if (row==3) ch = s_kb_r3[col]; if (m_kbLower && (ch>='A' && ch<='Z')) ch = (char)(ch + ('a' - 'A')); return ch; } void RefreshPane(Pane& p){ if (p.mode==1){ int prevSel = p.sel; int prevScroll= p.scroll; ListDirectory(p.curPath, p.items); if (prevSel >= (int)p.items.size()) prevSel = (int)p.items.size()-1; if (prevSel < 0) prevSel = 0; p.sel = prevSel; int maxScroll = MaxI(0, (int)p.items.size() - m_visible); if (prevScroll > maxScroll) prevScroll = maxScroll; if (prevScroll < 0) prevScroll = 0; p.scroll = prevScroll; } else { // drives list BuildDriveItems(p.items); if (p.sel >= (int)p.items.size()) p.sel = (int)p.items.size()-1; if (p.sel < 0) p.sel = 0; p.scroll = 0; } } bool ResolveDestDir(char* outDst, size_t cap){ Pane& dst = m_pane[1 - m_active]; outDst[0] = 0; if (dst.mode == 1) { _snprintf(outDst, (int)cap, "%s", dst.curPath); outDst[cap-1]=0; NormalizeDirA(outDst); return true; } if (!dst.items.empty()){ const Item& di = dst.items[dst.sel]; if (di.isDir && !di.isUpEntry){ _snprintf(outDst, (int)cap, "%s", di.name); // e.g. "E:\" outDst[cap-1]=0; NormalizeDirA(outDst); return true; } } return false; } // --- file ops helpers bool EnsureDirA(const char* path){ DWORD a = GetFileAttributesA(path); if (a != INVALID_FILE_ATTRIBUTES && (a & FILE_ATTRIBUTE_DIRECTORY)) return true; return CreateDirectoryA(path, NULL) ? true : false; } bool CopyFileSimpleA(const char* s, const char* d){ return CopyFileA(s, d, FALSE) ? true : false; } bool DeleteRecursiveA(const char* path){ DWORD a = GetFileAttributesA(path); if (a == INVALID_FILE_ATTRIBUTES) return false; if (a & FILE_ATTRIBUTE_DIRECTORY){ char mask[512]; JoinPath(mask, sizeof(mask), path, "*"); WIN32_FIND_DATAA fd; HANDLE h=FindFirstFileA(mask,&fd); if (h != INVALID_HANDLE_VALUE){ do{ if (!strcmp(fd.cFileName,".") || !strcmp(fd.cFileName,"..")) continue; char sub[512]; JoinPath(sub, sizeof(sub), path, fd.cFileName); DeleteRecursiveA(sub); }while(FindNextFileA(h,&fd)); FindClose(h); } return RemoveDirectoryA(path) ? true : false; }else{ return DeleteFileA(path) ? true : false; } } bool CopyRecursiveA(const char* srcPath, const char* dstDir){ DWORD a = GetFileAttributesA(srcPath); if (a == INVALID_FILE_ATTRIBUTES) return false; const char* base = strrchr(srcPath, '\\'); base = base ? base+1 : srcPath; char dstPath[512]; JoinPath(dstPath, sizeof(dstPath), dstDir, base); if (a & FILE_ATTRIBUTE_DIRECTORY){ if (!EnsureDirA(dstPath)) return false; char mask[512]; JoinPath(mask, sizeof(mask), srcPath, "*"); WIN32_FIND_DATAA fd; HANDLE h=FindFirstFileA(mask, &fd); if (h != INVALID_HANDLE_VALUE){ do{ if (!strcmp(fd.cFileName,".") || !strcmp(fd.cFileName,"..")) continue; char subSrc[512]; JoinPath(subSrc, sizeof(subSrc), srcPath, fd.cFileName); CopyRecursiveA(subSrc, dstPath); }while(FindNextFileA(h,&fd)); FindClose(h); } return true; }else{ return CopyFileSimpleA(srcPath, dstPath); } } ULONGLONG DirSizeRecursiveA(const char* path){ ULONGLONG sum = 0; DWORD a = GetFileAttributesA(path); if (a == INVALID_FILE_ATTRIBUTES) return 0; if (a & FILE_ATTRIBUTE_DIRECTORY){ char mask[512]; JoinPath(mask, sizeof(mask), path, "*"); WIN32_FIND_DATAA fd; HANDLE h = FindFirstFileA(mask, &fd); if (h != INVALID_HANDLE_VALUE){ do{ if (!strcmp(fd.cFileName,".") || !strcmp(fd.cFileName,"..")) continue; if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY){ char sub[512]; JoinPath(sub, sizeof(sub), path, fd.cFileName); sum += DirSizeRecursiveA(sub); } else { sum += (((ULONGLONG)fd.nFileSizeHigh)<<32) | fd.nFileSizeLow; } } while (FindNextFileA(h, &fd)); FindClose(h); } } else { WIN32_FILE_ATTRIBUTE_DATA fad; if (GetFileAttributesExA(path, GetFileExInfoStandard, &fad)){ sum += (((ULONGLONG)fad.nFileSizeHigh)<<32) | fad.nFileSizeLow; } } return sum; } void AddMenuItem(const char* label, Action act, bool enabled){ m_menu[m_menuCount].label = label; m_menu[m_menuCount].act = act; m_menu[m_menuCount].enabled = enabled; ++m_menuCount; } bool DirExistsA(const char* path){ DWORD a = GetFileAttributesA(path); return (a != INVALID_FILE_ATTRIBUTES) && (a & FILE_ATTRIBUTE_DIRECTORY); } void SelectItemInPane(Pane& p, const char* name){ if (!name || p.items.empty()) return; for (int i=0;i<(int)p.items.size();++i){ if (_stricmp(p.items[i].name, name) == 0){ p.sel = i; if (p.sel < p.scroll) p.scroll = p.sel; if (p.sel >= p.scroll + m_visible) p.scroll = p.sel - (m_visible - 1); return; } } } // --- context menu void BuildContextMenu(){ Pane& p = m_pane[m_active]; bool inDir = (p.mode == 1); bool hasSel = !p.items.empty(); m_menuCount = 0; if (inDir) AddMenuItem("Open", ACT_OPEN, hasSel); AddMenuItem("Copy", ACT_COPY, hasSel); AddMenuItem("Move", ACT_MOVE, hasSel); AddMenuItem("Delete", ACT_DELETE, hasSel); AddMenuItem("Rename", ACT_RENAME, hasSel); AddMenuItem("Make new folder", ACT_MKDIR, inDir); AddMenuItem("Calculate size", ACT_CALCSIZE, hasSel); AddMenuItem("Go to root", ACT_GOROOT, inDir); AddMenuItem("Switch pane", ACT_SWITCHMEDIA,true); if (m_menuSel >= m_menuCount) m_menuSel = m_menuCount-1; if (m_menuSel < 0) m_menuSel = 0; } void OpenMenu(){ BuildContextMenu(); m_menuOpen=true; m_mode=MODE_MENU; } // >>> CHANGED: only switch to BROWSE if we were actually in MENU void CloseMenu(){ m_menuOpen = false; if (m_mode == MODE_MENU) m_mode = MODE_BROWSE; } // --- Rename UI lifecycle void BeginRename(const char* parentDir, const char* oldName){ // ensure any context menu is hidden, but do NOT stomp the new mode m_menuOpen = false; m_renActive = true; _snprintf(m_renParent, sizeof(m_renParent), "%s", parentDir); m_renParent[sizeof(m_renParent)-1]=0; _snprintf(m_renOld, sizeof(m_renOld), "%s", oldName); m_renOld[sizeof(m_renOld)-1]=0; _snprintf(m_renBuf, sizeof(m_renBuf), "%s", oldName); m_renBuf[sizeof(m_renBuf)-1]=0; m_renCursor = (int)strlen(m_renBuf); m_renSelRow = 0; m_renSelCol = 0; m_mode = MODE_RENAME; } void CancelRename(){ m_renActive = false; m_mode = MODE_BROWSE; } void AcceptRename(){ SanitizeFatxNameInPlace(m_renBuf); if (_stricmp(m_renBuf, m_renOld)==0){ SetStatus("No change"); CancelRename(); return; } char oldPath[512]; JoinPath(oldPath, sizeof(oldPath), m_renParent, m_renOld); char newPath[512]; JoinPath(newPath, sizeof(newPath), m_renParent, m_renBuf); if (MoveFileA(oldPath, newPath)){ SetStatus("Renamed to %s", m_renBuf); RefreshPane(m_pane[0]); RefreshPane(m_pane[1]); Pane& ap = m_pane[m_active]; if (ap.mode==1 && _stricmp(ap.curPath, m_renParent)==0){ SelectItemInPane(ap, m_renBuf); } }else{ SetStatusLastErr("Rename failed"); RefreshPane(m_pane[0]); RefreshPane(m_pane[1]); } CancelRename(); } void OnPad_Rename(const XBGAMEPAD& pad) { const DWORD btn = pad.wButtons; // Edge-detected digital buttons + allow quick analog nudge 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); bool left = ((btn & XINPUT_GAMEPAD_DPAD_LEFT) && !(m_prevButtons & XINPUT_GAMEPAD_DPAD_LEFT)) || (pad.sThumbLX < -16000); bool right = ((btn & XINPUT_GAMEPAD_DPAD_RIGHT) && !(m_prevButtons & XINPUT_GAMEPAD_DPAD_RIGHT)) || (pad.sThumbLX > 16000); if (up) { if (m_renSelRow > 0) m_renSelRow--; Sleep(120); } if (down) { if (m_renSelRow < 4) m_renSelRow++; Sleep(120); } int cols = s_kb_cols[m_renSelRow]; if (m_renSelCol >= cols) m_renSelCol = cols - 1; if (left) { if (m_renSelCol > 0) m_renSelCol--; Sleep(120); } if (right) { if (m_renSelCol < cols - 1) m_renSelCol++; Sleep(120); } // Edge-detect face buttons unsigned char a = pad.bAnalogButtons[XINPUT_GAMEPAD_A]; unsigned char b = pad.bAnalogButtons[XINPUT_GAMEPAD_B]; unsigned char y = pad.bAnalogButtons[XINPUT_GAMEPAD_Y]; unsigned char w = pad.bAnalogButtons[XINPUT_GAMEPAD_WHITE]; unsigned char k = pad.bAnalogButtons[XINPUT_GAMEPAD_BLACK]; bool aTrig = (a > 30 && m_prevA <= 30); bool bTrig = (b > 30 && m_prevB <= 30); bool yTrig = (y > 30 && m_prevY <= 30); bool startTrig = ((btn & XINPUT_GAMEPAD_START) && !(m_prevButtons & XINPUT_GAMEPAD_START)); // Toggle case with Y if (yTrig){ m_kbLower = !m_kbLower; Sleep(120); } if (aTrig){ if (m_renSelRow <= 3){ // Insert selected char at cursor int len = (int)strlen(m_renBuf); const int cap = (int)sizeof(m_renBuf) - 1; if (len < cap){ char ch = KbCharAt(m_renSelRow, m_renSelCol); for (int i=len; i>=m_renCursor; --i) m_renBuf[i+1] = m_renBuf[i]; m_renBuf[m_renCursor++] = ch; } }else{ // Bottom-row actions: 0=Backspace 1=Space 2=Aa 3=OK 4=Cancel if (m_renSelCol == 0){ // Backspace if (m_renCursor > 0){ int len = (int)strlen(m_renBuf); for (int i=m_renCursor-1; i<=len; ++i) m_renBuf[i] = m_renBuf[i+1]; m_renCursor--; } }else if (m_renSelCol == 1){ // Space int len = (int)strlen(m_renBuf); const int cap = (int)sizeof(m_renBuf) - 1; if (len < cap){ for (int i=len; i>=m_renCursor; --i) m_renBuf[i+1] = m_renBuf[i]; m_renBuf[m_renCursor++] = ' '; } }else if (m_renSelCol == 2){ // Aa toggle m_kbLower = !m_kbLower; }else if (m_renSelCol == 3){ // OK AcceptRename(); m_prevA=a; m_prevB=b; m_prevY=y; m_prevButtons=btn; m_prevWhite=w; m_prevBlack=k; return; }else if (m_renSelCol == 4){ // Cancel CancelRename(); m_prevA=a; m_prevB=b; m_prevY=y; m_prevButtons=btn; m_prevWhite=w; m_prevBlack=k; return; } } Sleep(140); } if (startTrig){ AcceptRename(); m_prevA=a; m_prevB=b; m_prevY=y; m_prevButtons=btn; m_prevWhite=w; m_prevBlack=k; return; } if (bTrig){ CancelRename(); m_prevA=a; m_prevB=b; m_prevY=y; m_prevButtons=btn; m_prevWhite=w; m_prevBlack=k; return; } // Move caret with White/Black (edge-detected) bool wTrig = (w > 30 && m_prevWhite <= 30); bool kTrig = (k > 30 && m_prevBlack <= 30); if (wTrig){ if (m_renCursor > 0) m_renCursor--; } if (kTrig){ int len=(int)strlen(m_renBuf); if (m_renCursor < len) m_renCursor++; } // Clamp caret { int len=(int)strlen(m_renBuf); if (m_renCursor < 0) m_renCursor = 0; if (m_renCursor > len) m_renCursor = len; } // Save previous for edge detection next frame m_prevA = a; m_prevB = b; m_prevY = y; m_prevButtons = btn; m_prevWhite = w; m_prevBlack = k; } void OnPad_Menu(const XBGAMEPAD& pad) { const DWORD btn = pad.wButtons; // Edge-detected dpad (like keyboard) + allow quick analog nudge 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) { if (m_menuSel > 0) --m_menuSel; } if (down) { if (m_menuSel < m_menuCount-1) ++m_menuSel; } // Edge-detect face buttons unsigned char a = pad.bAnalogButtons[XINPUT_GAMEPAD_A]; unsigned char b = pad.bAnalogButtons[XINPUT_GAMEPAD_B]; unsigned char x = pad.bAnalogButtons[XINPUT_GAMEPAD_X]; bool aTrig = (a > 30 && m_prevA <= 30); bool bTrig = (b > 30 && m_prevB <= 30); bool xTrig = (x > 30 && m_prevX <= 30); if (aTrig) { const MenuItem& mi = m_menu[m_menuSel]; if (mi.enabled) ExecuteAction(mi.act); // may close menu / change mode } else if (bTrig || xTrig) { CloseMenu(); } // Save previous states for next frame 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]; } void DrawRename(){ if (!m_renActive) return; D3DVIEWPORT8 vp; m_pd3dDevice->GetViewport(&vp); const FLOAT panelW = MaxF(520.0f, vp.Width * 0.55f); const FLOAT panelH = MaxF(280.0f, vp.Height*0.45f); const FLOAT x = Snap((vp.Width - panelW)*0.5f); const FLOAT y = Snap((vp.Height - panelH)*0.5f); DrawRect(x-8, y-8, panelW+16, panelH+16, 0xA0101010); DrawRect(x, y, panelW, panelH, 0xE0222222); DrawAnsi(m_font, x+12, y+8, 0xFFFFFFFF, "Rename"); DrawHLine(x, y+28, panelW, 0x60FFFFFF); char hdr[640]; _snprintf(hdr, sizeof(hdr), "In: %s", m_renParent); hdr[sizeof(hdr)-1]=0; DrawAnsi(m_font, x+12, y+34, 0xFFCCCCCC, hdr); const FLOAT boxY = Snap(y + 60.0f); const FLOAT boxH = 30.0f; DrawRect(x+12, boxY, panelW-24, boxH, 0xFF0E0E0E); // text DrawAnsi(m_font, x+18, boxY+4, 0xFFFFFF00, m_renBuf); // caret at cursor char tmp = m_renBuf[m_renCursor]; m_renBuf[m_renCursor] = 0; FLOAT caretX = Snap(x+18 + MeasureTextW(m_renBuf)); m_renBuf[m_renCursor] = tmp; DrawRect(caretX, boxY+4, 2.0f, boxH-8.0f, 0x90FFFF00); // keyboard grid (integer-snap to avoid drift) const FLOAT padX = 12.0f; const FLOAT contentW = panelW - 2.0f*padX; const FLOAT cellH = kLineH + 6.0f; const FLOAT gridTop = Snap(boxY + boxH + 16.0f); // visual gaps between keys const FLOAT gapX = 6.0f; // horizontal gap between keys const FLOAT gapY = 4.0f; // vertical gap const FLOAT colW12 = contentW / 12.0f; for (int row=0; row<5; ++row){ int cols = s_kb_cols[row]; FLOAT rowY = Snap(gridTop + row*(cellH + gapY)); for (int col=0; colisUpEntry) JoinPath(srcFull, sizeof(srcFull), src.curPath, sel->name); switch (act){ case ACT_OPEN: if (sel){ if (sel->isUpEntry) { UpOne(src); } else if (sel->isDir) { EnterSelection(src); } } CloseMenu(); break; case ACT_COPY: { if (!sel || sel->isUpEntry) { SetStatus("Nothing to copy"); CloseMenu(); break; } char dstDir[512]; if (!ResolveDestDir(dstDir, sizeof(dstDir))) { SetStatus("Pick a destination (open a folder or select a drive)"); CloseMenu(); break; } if ((dstDir[0]=='D' || dstDir[0]=='d') && dstDir[1]==':') { SetStatus("Cannot copy to D:\\ (read-only)"); CloseMenu(); break; } NormalizeDirA(dstDir); if (!CanWriteHereA(dstDir)){ SetStatusLastErr("Dest not writable"); CloseMenu(); break; } if (CopyRecursiveA(srcFull, dstDir)) { Pane& dstp = m_pane[1 - m_active]; if (dstp.mode==1 && _stricmp(dstp.curPath, dstDir)==0) ListDirectory(dstp.curPath, dstp.items); SetStatus("Copied to %s", dstDir); } else { SetStatusLastErr("Copy failed"); } RefreshPane(m_pane[0]); RefreshPane(m_pane[1]); CloseMenu(); break; } case ACT_MOVE: { if (!sel || sel->isUpEntry) { SetStatus("Nothing to move"); CloseMenu(); break; } char dstDir[512]; if (!ResolveDestDir(dstDir, sizeof(dstDir))) { SetStatus("Pick a destination (open a folder or select a drive)"); CloseMenu(); break; } if ((dstDir[0]=='D' || dstDir[0]=='d') && dstDir[1]==':') { SetStatus("Cannot move to D:\\ (read-only)"); CloseMenu(); break; } NormalizeDirA(dstDir); if (!CanWriteHereA(dstDir)){ SetStatusLastErr("Dest not writable"); CloseMenu(); break; } if (CopyRecursiveA(srcFull, dstDir)) { if (!DeleteRecursiveA(srcFull)) { SetStatusLastErr("Move: delete source failed"); } else { Pane& srcp = m_pane[m_active]; Pane& dstp = m_pane[1 - m_active]; if (dstp.mode==1 && _stricmp(dstp.curPath, dstDir)==0) ListDirectory(dstp.curPath, dstp.items); if (srcp.mode==1) { ListDirectory(srcp.curPath, srcp.items); if (srcp.sel >= (int)srcp.items.size()) srcp.sel = (int)srcp.items.size()-1; } SetStatus("Moved to %s", dstDir); } } else { SetStatusLastErr("Move: copy failed"); } RefreshPane(m_pane[0]); RefreshPane(m_pane[1]); CloseMenu(); break; } case ACT_DELETE: if (sel){ if (DeleteRecursiveA(srcFull)){ ListDirectory(src.curPath, src.items); if (src.sel >= (int)src.items.size()) src.sel = (int)src.items.size()-1; SetStatus("Deleted"); } else SetStatus("Delete failed"); } RefreshPane(m_pane[0]); RefreshPane(m_pane[1]); CloseMenu(); break; case ACT_RENAME: { if (sel && src.mode==1 && !sel->isUpEntry){ BeginRename(src.curPath, sel->name); // BeginRename will hide menu & set MODE_RENAME CloseMenu(); // safe (won't change mode unless we were in MENU) } else { SetStatus("Open a folder and select an item"); CloseMenu(); } break; } case ACT_MKDIR: { char baseDir[512] = {0}; if (src.mode == 1) { _snprintf(baseDir, sizeof(baseDir), "%s", src.curPath); } else if (!src.items.empty()) { const Item& di = src.items[src.sel]; if (di.isDir && !di.isUpEntry) { _snprintf(baseDir, sizeof(baseDir), "%s", di.name); // e.g. "E:\" } } baseDir[sizeof(baseDir)-1] = 0; if (!baseDir[0]) { SetStatus("Open a folder or select a drive first"); CloseMenu(); break; } if ((baseDir[0]=='D' || baseDir[0]=='d') && baseDir[1]==':') { SetStatus("Cannot create on D:\\ (read-only)"); CloseMenu(); break; } NormalizeDirA(baseDir); if (!CanWriteHereA(baseDir)) { SetStatusLastErr("Dest not writable"); CloseMenu(); break; } char nameBuf[64]; char target[512]; int idx = 0; for (;;){ if (idx == 0) _snprintf(nameBuf, sizeof(nameBuf), "NewFolder"); else _snprintf(nameBuf, sizeof(nameBuf), "NewFolder%d", idx); nameBuf[sizeof(nameBuf)-1]=0; JoinPath(target, sizeof(target), baseDir, nameBuf); if (!DirExistsA(target)) { if (CreateDirectoryA(target, NULL)) { SetStatus("Created %s", nameBuf); } else { SetStatusLastErr("Create folder failed"); } break; } if (++idx > 999){ SetStatus("Create folder failed (names exhausted)"); break; } } RefreshPane(m_pane[0]); RefreshPane(m_pane[1]); if (src.mode == 1 && _stricmp(src.curPath, baseDir) == 0) { SelectItemInPane(src, nameBuf); } CloseMenu(); break; } case ACT_CALCSIZE: if (sel){ ULONGLONG bytes = DirSizeRecursiveA(srcFull); char tmp[64]; FormatSize(bytes, tmp, sizeof(tmp)); SetStatus("%s", tmp); } CloseMenu(); break; case ACT_GOROOT: { if (src.mode == 1){ if (!IsDriveRoot(src.curPath)){ // go to current drive’s root char root[4] = { src.curPath[0], ':', '\\', 0 }; strncpy(src.curPath, root, sizeof(src.curPath)-1); src.curPath[3] = 0; src.sel = 0; src.scroll = 0; ListDirectory(src.curPath, src.items); } else { // already at root -> show all drives src.mode = 0; src.curPath[0] = 0; src.sel = 0; src.scroll = 0; BuildDriveItems(src.items); } } else { // if we were on the drives list already, just ensure it’s built BuildDriveItems(src.items); } CloseMenu(); break; } case ACT_SWITCHMEDIA: m_active = 1 - m_active; CloseMenu(); break; } } void DrawMenu(){ if (!m_menuOpen) return; const FLOAT menuW = 340.0f; const FLOAT rowH = kLineH + 6.0f; const FLOAT menuH = 12.0f + m_menuCount*rowH + 12.0f; const FLOAT paneX = (m_active==0) ? kListX_L : (kListX_L + kListW + kPaneGap); const FLOAT x = paneX + (kListW - menuW)*0.5f; const FLOAT y = kListY + 20.0f; // backdrop DrawRect(x-6.0f, y-6.0f, menuW+12.0f, menuH+12.0f, 0xA0101010); DrawRect(x, y, menuW, menuH, 0xE0222222); // title DrawAnsi(m_font, x+10.0f, y+6.0f, 0xFFFFFFFF, "Select action"); DrawHLine(x, y+26.0f, menuW, 0x60FFFFFF); // items FLOAT iy = y + 30.0f; for (int i=0;iGetViewport(&vp); const FLOAT sideMargin = MaxF(24.0f, vp.Width * 0.04f); const FLOAT gap = MaxF(24.0f, vp.Width * 0.035f); kPaneGap = gap; kListX_L = sideMargin; const FLOAT totalUsable = (FLOAT)vp.Width - (sideMargin * 2.0f) - gap; kListW = MaxF(260.0f, (totalUsable / 2.0f) - 10.0f); kHdrW = kListW + 30.0f; kHdrY = MaxF(12.0f, vp.Height * 0.03f); kHdrH = MaxF(22.0f, vp.Height * 0.04f); kListY = MaxF(60.0f, kHdrY + kHdrH + 34.0f); // room under "Name/Size" kLineH = MaxF(22.0f, vp.Height * 0.036f); // compute rows by viewport (leave footer space) FLOAT bottomY = (FLOAT)vp.Height - MaxF(48.0f, vp.Height * 0.09f); FLOAT usableH = bottomY - kListY; if (usableH < 0) usableH = 0; m_visible = (int)(usableH / kLineH); if (m_visible < 6) m_visible=6; if (m_visible>30) m_visible=30; return S_OK; } void EnsureListing(Pane& p){ if (p.mode==0) BuildDriveItems(p.items); else ListDirectory(p.curPath, p.items); if (p.sel >= (int)p.items.size()) p.sel = (int)p.items.size()-1; if (p.sel < 0) p.sel = 0; if (p.scroll > p.sel) p.scroll = p.sel; int maxScroll = MaxI(0, (int)p.items.size() - m_visible); if (p.scroll > maxScroll) p.scroll = maxScroll; } void EnterSelection(Pane& p){ if (p.items.empty()) return; const Item& it = p.items[p.sel]; if (p.mode==0){ // into drive strncpy(p.curPath,it.name,sizeof(p.curPath)-1); p.curPath[sizeof(p.curPath)-1]=0; p.mode=1; p.sel=0; p.scroll=0; ListDirectory(p.curPath,p.items); return; } if (it.isUpEntry){ if (strlen(p.curPath)<=3){ p.mode=0; p.sel=0; p.scroll=0; BuildDriveItems(p.items); p.curPath[0]=0; } else { ParentPath(p.curPath); p.sel=0; p.scroll=0; ListDirectory(p.curPath,p.items); } return; } if (it.isDir){ char next[512]; JoinPath(next,sizeof(next),p.curPath,it.name); strncpy(p.curPath,next,sizeof(p.curPath)-1); p.curPath[sizeof(p.curPath)-1]=0; p.sel=0; p.scroll=0; ListDirectory(p.curPath,p.items); return; } // files: no-op in this build } void UpOne(Pane& p){ if (p.mode==0) return; if (strlen(p.curPath)<=3){ p.mode=0; p.sel=0; p.scroll=0; BuildDriveItems(p.items); p.curPath[0]=0; return; } ParentPath(p.curPath); p.sel=0; p.scroll=0; ListDirectory(p.curPath,p.items); } void OnPad(const XBGAMEPAD& pad){ // Rename modal takes over if (m_mode == MODE_RENAME){ OnPad_Rename(pad); return; } // Context menu modal if (m_mode == MODE_MENU){ OnPad_Menu(pad); return; } // Browse mode OnPad_Browse(pad); } void OnPad_Browse(const XBGAMEPAD& pad) { const DWORD btn = pad.wButtons; Pane& p = m_pane[m_active]; // -------- NAVIGATION (auto-repeat with initial delay) -------- const DWORD now = GetTickCount(); const int kDead = 16000; // analog deadzone const DWORD kInit = 230; // ms before first repeat const DWORD kRep = 120; // ms between repeats int ud = 0; if ((btn & XINPUT_GAMEPAD_DPAD_UP) || pad.sThumbLY > kDead) ud = -1; if ((btn & XINPUT_GAMEPAD_DPAD_DOWN) || pad.sThumbLY < -kDead) ud = +1; if (ud == 0){ m_navUDHeld = false; m_navUDDir = 0; }else{ if (!m_navUDHeld || m_navUDDir != ud){ // first step immediately if (ud < 0){ if (p.sel > 0){ --p.sel; if (p.sel < p.scroll) p.scroll = p.sel; } }else{ int maxSel = (int)p.items.size() - 1; if (p.sel < maxSel){ ++p.sel; if (p.sel >= p.scroll + m_visible) p.scroll = p.sel - (m_visible - 1); } } m_navUDHeld = true; m_navUDDir = ud; m_navUDNext = now + kInit; }else if (now >= m_navUDNext){ // held long enough -> repeat if (ud < 0){ if (p.sel > 0){ --p.sel; if (p.sel < p.scroll) p.scroll = p.sel; } }else{ int maxSel = (int)p.items.size() - 1; if (p.sel < maxSel){ ++p.sel; if (p.sel >= p.scroll + m_visible) p.scroll = p.sel - (m_visible - 1); } } m_navUDNext = now + kRep; } } // Pane switch (edge-detected, no repeat) bool leftTrig = (btn & XINPUT_GAMEPAD_DPAD_LEFT) && !(m_prevButtons & XINPUT_GAMEPAD_DPAD_LEFT); bool rightTrig = (btn & XINPUT_GAMEPAD_DPAD_RIGHT) && !(m_prevButtons & XINPUT_GAMEPAD_DPAD_RIGHT); if (leftTrig) m_active = 0; if (rightTrig) m_active = 1; // -------- Other buttons (edge-detected) -------- unsigned char a = pad.bAnalogButtons[XINPUT_GAMEPAD_A]; unsigned char b = pad.bAnalogButtons[XINPUT_GAMEPAD_B]; unsigned char x = pad.bAnalogButtons[XINPUT_GAMEPAD_X]; unsigned char w = pad.bAnalogButtons[XINPUT_GAMEPAD_WHITE]; // page down unsigned char k = pad.bAnalogButtons[XINPUT_GAMEPAD_BLACK]; // page up bool aTrig = (a > 30 && m_prevA <= 30); bool bTrig = (b > 30 && m_prevB <= 30); bool xTrig = (x > 30 && m_prevX <= 30); bool wTrig = (w > 30 && m_prevWhite <= 30); bool kTrig = (k > 30 && m_prevBlack <= 30); bool startTrig = ((btn & XINPUT_GAMEPAD_START) && !(m_prevButtons & XINPUT_GAMEPAD_START)); if (xTrig) { OpenMenu(); goto store_prev; } // open once if (aTrig) EnterSelection(p); if (bTrig) UpOne(p); if (kTrig){ // BLACK: page up p.sel -= m_visible; if (p.sel < 0) p.sel = 0; if (p.sel < p.scroll) p.scroll = p.sel; } if (wTrig){ // WHITE: page down int maxSel = (int)p.items.size()-1; p.sel += m_visible; if (p.sel > maxSel) p.sel = maxSel; if (p.sel >= p.scroll + m_visible) p.scroll = p.sel - (m_visible - 1); } if (startTrig){ MapStandardDrives_Io(); RescanDrives(); EnsureListing(m_pane[0]); EnsureListing(m_pane[1]); } store_prev: // remember states for edge detection next frame m_prevButtons = btn; m_prevA = a; m_prevB = b; m_prevX = x; m_prevWhite = w; m_prevBlack = k; } HRESULT Render(){ m_pd3dDevice->Clear(0,NULL,D3DCLEAR_TARGET,0x20202020,1.0f,0); m_pd3dDevice->BeginScene(); m_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE); m_pd3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); m_pd3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); // left + right panes DrawPane(kListX_L, m_pane[0], m_active==0); DrawPane(kListX_L + kListW + kPaneGap, m_pane[1], m_active==1); // footer D3DVIEWPORT8 vp2; m_pd3dDevice->GetViewport(&vp2); const FLOAT footerY = (FLOAT)vp2.Height - MaxF(48.0f, vp2.Height * 0.09f); DrawRect(HdrX(kListX_L), footerY, kHdrW*2 + kPaneGap + 30.0f, 28.0f, 0x802A2A2A); if (m_pane[m_active].mode==0){ DrawAnsi(m_font, HdrX(kListX_L)+5.0f, footerY+4.0f, 0xFFCCCCCC, "D-Pad: Move | Left/Right: Switch pane | A: Enter | X: Menu | Start: Map+Rescan | Black/White: Page"); } else { ULONGLONG fb=0, tb=0; GetDriveFreeTotal(m_pane[m_active].curPath, fb, tb); char fstr[64], tstr[64], bar[420]; FormatSize(fb, fstr, sizeof(fstr)); FormatSize(tb, tstr, sizeof(tstr)); _snprintf(bar,sizeof(bar), "Active: %s | B: Up | Free: %s / Total: %s | Left/Right: Switch pane | X: Menu | Black/White: Page", m_active==0?"Left":"Right", fstr, tstr); bar[sizeof(bar)-1]=0; DrawAnsi(m_font, HdrX(kListX_L)+5.0f, footerY+4.0f, 0xFFCCCCCC, bar); } // transient status (over footer, if any) DWORD now = GetTickCount(); if (now < m_statusUntilMs && m_status[0]){ DrawAnsi(m_font, HdrX(kListX_L)+5.0f, footerY-18.0f, 0xFFBBDDEE, m_status); } // modal menu DrawMenu(); // rename overlay (modal) DrawRename(); m_pd3dDevice->EndScene(); m_pd3dDevice->Present(NULL,NULL,NULL,NULL); return S_OK; } // input pump HRESULT FrameMove(){ XBInput_GetInput(); OnPad(g_Gamepads[0]); return S_OK; } // pane draw (placed after other methods for readability) void DrawPane(FLOAT baseX, Pane& p, bool active){ // header FLOAT hx = HdrX(baseX); DrawRect(hx, kHdrY, kHdrW, kHdrH, active ? 0xFF3A3A3A : 0x802A2A2A); if (p.mode==0) DrawAnsi(m_font, hx+5.0f, kHdrY+6.0f, 0xFFFFFFFF, "Detected Drives"); else { char hdr[600]; _snprintf(hdr,sizeof(hdr),"%s", p.curPath); hdr[sizeof(hdr)-1]=0; DrawAnsi(m_font, hx+5.0f, kHdrY+6.0f, 0xFFFFFFFF, hdr); } // compute Size column width/X for this pane const FLOAT sizeColW = ComputeSizeColW(p); const FLOAT sizeColX = baseX + kListW - (kScrollBarW + kPaddingX + sizeColW); const FLOAT sizeRight = sizeColX + sizeColW; // column header DrawRect(baseX-10.0f, kHdrY+kHdrH+6.0f, kListW+20.0f, 22.0f, 0x60333333); DrawAnsi(m_font, NameColX(baseX), kHdrY+kHdrH+10.0f, 0xFFDDDDDD, "Name"); DrawRightAligned("Size", sizeRight, kHdrY+kHdrH+10.0f, 0xFFDDDDDD); DrawHLine(baseX-10.0f, kHdrY+kHdrH+28.0f, kListW+20.0f, 0x80444444); DrawVLine(sizeColX - 8.0f, kHdrY+kHdrH+7.0f, 22.0f, 0x40444444); // list bg DrawRect(baseX-10.0f, kListY-6.0f, kListW+20.0f, kLineH*m_visible+12.0f, 0x30101010); // stripes + selection int end=p.scroll+m_visible; if(end>(int)p.items.size()) end=(int)p.items.size(); int rowIndex=0; for(int i=p.scroll;i=p.scroll && p.selm_visible){ FLOAT trackX = baseX + kListW - kScrollBarW - 4.0f; FLOAT trackY = kListY - 2.0f; FLOAT trackH = m_visible * kLineH; DrawRect(trackX, trackY, kScrollBarW, trackH, 0x40282828); int total=(int)p.items.size(); FLOAT thumbH=(FLOAT)m_visible/(FLOAT)total*trackH; if(thumbH<10.0f) thumbH=10.0f; FLOAT maxScroll=(FLOAT)(total-m_visible); FLOAT t=(maxScroll>0.0f)?((FLOAT)p.scroll/maxScroll):0.0f; FLOAT thumbY=trackY + t*(trackH-thumbH); DrawRect(trackX, thumbY, kScrollBarW, thumbH, 0x80C0C0C0); } } }; // ---- static layout defs (defaults; overwritten in Initialize) ---- FLOAT FileBrowserApp::kListX_L = 50.0f; FLOAT FileBrowserApp::kListY = 100.0f; FLOAT FileBrowserApp::kListW = 540.0f; FLOAT FileBrowserApp::kLineH = 26.0f; FLOAT FileBrowserApp::kHdrX_L = 35.0f; FLOAT FileBrowserApp::kHdrY = 22.0f; FLOAT FileBrowserApp::kHdrW = 570.0f; FLOAT FileBrowserApp::kHdrH = 28.0f; FLOAT FileBrowserApp::kGutterW = 18.0f; FLOAT FileBrowserApp::kPaddingX = 6.0f; FLOAT FileBrowserApp::kScrollBarW = 3.0f; FLOAT FileBrowserApp::kPaneGap = 60.0f; // OG XDK prefers main() entry int __cdecl main(){ FileBrowserApp app; app.Create(); return app.Run(); }