You've already forked FileManager
mirror of
https://github.com/Team-Resurgent/FileManager.git
synced 2026-04-30 10:38:50 -07:00
b07c4be9cb
Fixed keyboard highlight spacing Fixed button sensitivity while in context menus. Fixed scroll sensitivity when navigating files and folders. Fixed go to root. If already in a folder will take you to the root of the drive, If root of drive will take you to drive list.
1332 lines
46 KiB
C++
1332 lines
46 KiB
C++
//------------------------------------------------------------------------------
|
||
// 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 <xtl.h>
|
||
#include <wchar.h>
|
||
#include <vector>
|
||
#include <algorithm>
|
||
#include <stdarg.h>
|
||
|
||
#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 <algorithm> 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<cap){ s[n]='\\'; s[n+1]=0; } }
|
||
static inline void ParentPath(char* path){
|
||
size_t n=strlen(path);
|
||
if (n <= 3) { path[0]=0; return; }
|
||
while (n && path[n-1]=='\\') { path[--n]=0; }
|
||
char* p = strrchr(path,'\\');
|
||
if (!p) { path[0]=0; return; }
|
||
if (p == path+2) *(p+1)=0; else *p=0;
|
||
}
|
||
static inline int ci_cmp(const char* a,const char* b){ return _stricmp(a,b); }
|
||
|
||
// -------------------- letter mapping --------------------
|
||
static BOOL MapLetterToDevice(const char* letter,const char* devicePath){
|
||
char dosBuf[16]={0}; MakeDosString(dosBuf,sizeof(dosBuf),letter);
|
||
STRING sDos; BuildString(sDos,dosBuf); IoDeleteSymbolicLink(&sDos);
|
||
STRING sDev; BuildString(sDev,devicePath);
|
||
if(IoCreateSymbolicLink(&sDos,&sDev)!=0) return FALSE; // STATUS_SUCCESS==0
|
||
char root[8]={0}; _snprintf(root,sizeof(root),"%s\\",letter);
|
||
if(GetFileAttributesA(root)==INVALID_FILE_ATTRIBUTES){ IoDeleteSymbolicLink(&sDos); return FALSE; }
|
||
return TRUE;
|
||
}
|
||
static void MapStandardDrives_Io(){
|
||
MapLetterToDevice("D:","\\Device\\Cdrom0");
|
||
MapLetterToDevice("C:","\\Device\\Harddisk0\\Partition2");
|
||
MapLetterToDevice("E:","\\Device\\Harddisk0\\Partition1");
|
||
MapLetterToDevice("X:","\\Device\\Harddisk0\\Partition3");
|
||
MapLetterToDevice("Y:","\\Device\\Harddisk0\\Partition4");
|
||
MapLetterToDevice("Z:","\\Device\\Harddisk0\\Partition5");
|
||
MapLetterToDevice("F:","\\Device\\Harddisk0\\Partition6");
|
||
MapLetterToDevice("G:","\\Device\\Harddisk0\\Partition7");
|
||
}
|
||
|
||
// -------------------- listing --------------------
|
||
struct Item { char name[256]; bool isDir; ULONGLONG size; bool isUpEntry; };
|
||
|
||
static void BuildDriveItems(std::vector<Item>& out){
|
||
out.clear(); for(int j=0;j<g_presentCount;++j){ int i=g_presentIdx[j]; Item it; ZeroMemory(&it,sizeof(it));
|
||
strncpy(it.name,kRoots[i],255); it.name[255]=0; it.isDir=true; it.size=0; it.isUpEntry=false; out.push_back(it); }
|
||
}
|
||
static void RescanDrives(){ g_presentCount=0; for(int i=0;i<kNumRoots && g_presentCount<(int)(sizeof(g_presentIdx)/sizeof(g_presentIdx[0])); ++i){ DWORD a=GetFileAttributesA(kRoots[i]); if(a!=INVALID_FILE_ATTRIBUTES) g_presentIdx[g_presentCount++]=i; } }
|
||
static bool ItemLess(const Item& a,const Item& b){ if(a.isDir!=b.isDir) return a.isDir>b.isDir; return ci_cmp(a.name,b.name)<0; }
|
||
static bool ListDirectory(const char* path,std::vector<Item>& 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<Item> 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;i<limit;++i){
|
||
const Item& it = p.items[i];
|
||
if(!it.isDir && !it.isUpEntry){
|
||
FormatSize(it.size, buf, sizeof(buf));
|
||
FLOAT w = MeasureTextW(buf); if(w>maxW) 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; col<cols; ++col){
|
||
// raw cell edges
|
||
FLOAT x0, x1;
|
||
if (row < 4){
|
||
x0 = Snap(x + padX + col * colW12);
|
||
x1 = Snap(x + padX + (col + 1) * colW12);
|
||
}else{
|
||
FLOAT colW = contentW / (FLOAT)cols;
|
||
x0 = Snap(x + padX + col * colW);
|
||
x1 = Snap(x + padX + (col + 1) * colW);
|
||
}
|
||
|
||
// make highlight symmetric: half gap left + half right
|
||
const FLOAT drawX = x0 + gapX * 0.5f;
|
||
const FLOAT drawW = (x1 - x0) - gapX;
|
||
const bool sel = (row==m_renSelRow && col==m_renSelCol);
|
||
|
||
DrawRect(drawX, rowY, drawW, cellH, sel ? 0x60FFFF00 : 0x30202020);
|
||
|
||
// label text (centered)
|
||
if (row < 4){
|
||
char s[2]; s[0] = KbCharAt(row, col); s[1] = 0;
|
||
FLOAT tw, th; MeasureTextWH(s, tw, th);
|
||
const FLOAT tx = Snap(drawX + (drawW - tw) * 0.5f);
|
||
const FLOAT ty = Snap(rowY + (cellH - th) * 0.5f);
|
||
DrawAnsi(m_font, tx, ty, 0xFFE0E0E0, s);
|
||
}else{
|
||
const char* cap =
|
||
(col==0) ? "Backspace" :
|
||
(col==1) ? "Space" :
|
||
(col==2) ? "Aa" :
|
||
(col==3) ? "OK" : "Cancel";
|
||
FLOAT tw, th; MeasureTextWH(cap, tw, th);
|
||
const FLOAT tx = Snap(drawX + (drawW - tw) * 0.5f);
|
||
const FLOAT ty = Snap(rowY + (cellH - th) * 0.5f);
|
||
DrawAnsi(m_font, tx, ty, 0xFFE0E0E0, cap);
|
||
}
|
||
}
|
||
}
|
||
|
||
DrawAnsi(m_font, x+12, y+panelH-20, 0xFFBBBBBB,
|
||
"A: Select B: Cancel Start: OK Y/Aa: Case White/Black: Move cursor");
|
||
}
|
||
|
||
void ExecuteAction(Action act){
|
||
Pane& src = m_pane[m_active];
|
||
Pane& dst = m_pane[1 - m_active];
|
||
|
||
const Item* sel = NULL;
|
||
if (!src.items.empty()) sel = &src.items[src.sel];
|
||
|
||
char srcFull[512]="";
|
||
if (sel && src.mode==1 && !sel->isUpEntry) 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;i<m_menuCount;++i){
|
||
bool sel = (i == m_menuSel);
|
||
D3DCOLOR row = sel ? 0x60FFFF00 : 0x20202020;
|
||
DrawRect(x+6.0f, iy-2.0f, menuW-12.0f, rowH, row);
|
||
|
||
const MenuItem& mi = m_menu[i];
|
||
DWORD col = mi.enabled? (sel?0xFF202020:0xFFE0E0E0) : 0xFF7A7A7A;
|
||
DrawAnsi(m_font, x+16.0f, iy, col, mi.label);
|
||
iy += rowH;
|
||
}
|
||
}
|
||
|
||
HRESULT Initialize(){
|
||
if (FAILED(m_font.Create("D:\\Media\\Font.xpr", 0))) {
|
||
m_font.Create("D:\\Media\\CourierNew.xpr", 0);
|
||
}
|
||
XBInput_CreateGamepads(); MapStandardDrives_Io(); RescanDrives();
|
||
BuildDriveItems(m_pane[0].items); BuildDriveItems(m_pane[1].items);
|
||
|
||
// compute resolution-aware layout from viewport
|
||
D3DVIEWPORT8 vp; m_pd3dDevice->GetViewport(&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<end;++i,++rowIndex){
|
||
D3DCOLOR stripe=(rowIndex&1)?0x201E1E1E:0x10000000;
|
||
DrawRect(baseX, kListY + rowIndex*kLineH - 2.0f, kListW-8.0f, kLineH, stripe);
|
||
}
|
||
if(!p.items.empty() && p.sel>=p.scroll && p.sel<end){
|
||
int selRow=p.sel-p.scroll;
|
||
DrawRect(baseX, kListY + selRow*kLineH - 2.0f, kListW-8.0f, kLineH, active?0x60FFFF00:0x30FFFF00);
|
||
}
|
||
|
||
// rows
|
||
FLOAT y=kListY;
|
||
for(int i=p.scroll, r=0; i<end; ++i,++r){
|
||
const Item& it=p.items[i];
|
||
DWORD nameCol=(i==p.sel)?0xFFFFFF00:0xFFE0E0E0;
|
||
DWORD sizeCol=(i==p.sel)?0xFFFFFF00:0xFFB0B0B0;
|
||
D3DCOLOR ico = it.isUpEntry ? 0xFFAAAAAA : (it.isDir ? 0xFF5EA4FF : 0xFF89D07E);
|
||
DrawRect(baseX+2.0f, y+6.0f, kGutterW-8.0f, kLineH-12.0f, ico);
|
||
const char* glyph = it.isUpEntry ? ".." : (it.isDir?"+":"-");
|
||
DrawAnsi(m_font, baseX+4.0f, y+4.0f, 0xFFFFFFFF, glyph);
|
||
|
||
char nameBuf[300]; _snprintf(nameBuf,sizeof(nameBuf),"%s",it.name); nameBuf[sizeof(nameBuf)-1]=0;
|
||
DrawAnsi(m_font, NameColX(baseX), y, nameCol, nameBuf);
|
||
|
||
char sz[64]=""; if(!it.isDir && !it.isUpEntry){ FormatSize(it.size, sz, sizeof(sz)); }
|
||
DrawRightAligned(sz, sizeRight, y, sizeCol);
|
||
|
||
y += kLineH;
|
||
}
|
||
|
||
// scrollbar
|
||
if((int)p.items.size()>m_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(); }
|