Files

368 lines
14 KiB
C++
Raw Permalink Normal View History

2025-08-30 01:55:30 +10:00
#include "PaneRenderer.h"
2025-12-05 21:26:20 -08:00
2025-12-15 00:18:06 -08:00
#include "drawUtils.h"
#include "configuration.h"
2025-08-30 01:55:30 +10:00
2025-12-05 21:26:20 -08:00
static const FLOAT kRightGuardPx = 2.0f; // keep a sliver on the right
static const FLOAT kMeasureFudgePx = 2.0f; // measurement slack
static const FLOAT kNearFitSlackPx = 1.5f; // near-fit no-scroll slack
2025-09-02 14:26:19 +10:00
2025-12-05 21:26:20 -08:00
static const DWORD kMarqInitPauseMs = MARQUEE_INITIAL_PAUSE;
static const DWORD kMarqEndPauseMs = MARQUEE_END_PAUSE;
static const DWORD kMarqStepMs = MARQUEE_STEP;
static const int kMarqStepChars = MARQUEE_STEP_CHARS;
2025-12-05 21:26:20 -08:00
void PaneRenderer::PrimeSharedSizeColW(CXBFont& font, const Pane& p, const PaneStyle& st) {
2025-09-02 17:03:03 +10:00
const char* hdr = (p.mode == 0) ? "Free / Total" : "Size";
2025-12-05 21:26:20 -08:00
FLOAT maxW = GetAnsiW(font, hdr);
2025-09-02 17:03:03 +10:00
char buf[128];
2025-12-05 21:26:20 -08:00
if (p.mode == 0){ // mode == 0
int limit = ((int)p.items.size() > 32) ? 32 : (int)p.items.size();
2025-09-09 18:23:51 +10:00
for (int i=0;i<limit;++i){
2025-09-02 17:03:03 +10:00
const Item& it = p.items[i];
if (it.isDir && !it.isUpEntry){
2025-12-05 21:26:20 -08:00
ULONGLONG fb, tb;
GetDriveFreeTotal(it.name, fb, tb);
char f[64], t[64];
FormatSize(fb, f, sizeof(f));
FormatSize(tb, t, sizeof(t));
_snprintf(buf, sizeof(buf), "%s / %s", f, t);
buf[sizeof(buf) - 1] = 0;
maxW = max(GetAnsiW(font, buf), maxW);
2025-09-02 17:03:03 +10:00
}
}
2025-12-05 21:26:20 -08:00
}
else { // mode == 1
int limit = (p.items.size() > 200) ? 200 : p.items.size();
2025-09-09 18:23:51 +10:00
for (int i=0;i<limit;++i){
2025-09-02 17:03:03 +10:00
const Item& it = p.items[i];
if (!it.isDir && !it.isUpEntry){
FormatSize(it.size, buf, sizeof(buf));
2025-12-05 21:26:20 -08:00
maxW = max(GetAnsiW(font, buf), maxW);
2025-09-02 17:03:03 +10:00
}
2025-08-30 01:55:30 +10:00
}
}
2025-09-02 17:03:03 +10:00
2025-12-05 21:26:20 -08:00
maxW += 12.0f; // Why?
const FLOAT minW = max(90.0f, st.lineH * 3.5f);
2025-09-09 18:23:51 +10:00
const FLOAT maxWClamp = st.listW * 0.45f;
2025-12-05 21:26:20 -08:00
maxW = max(minW, maxW);
maxW = min(maxWClamp, maxW);
s_sharedSizeColW = max(s_sharedSizeColW, maxW);
2025-08-30 01:55:30 +10:00
}
2025-12-05 21:26:20 -08:00
void PaneRenderer::DrawNameFittedOrMarquee(CXBFont& font, FLOAT x, FLOAT y, FLOAT h, FLOAT maxW, DWORD color, const char* name, bool isSelected, int paneIndex, int rowIndex) {
const char* s = name ? name : ""; // Safety
const int len = strlen(s);
2025-09-09 18:23:51 +10:00
const FLOAT fitW_now = floorf(((maxW > 0.0f) ? maxW : 0.0f) + 0.5f);
2025-08-31 17:16:38 +10:00
2025-12-05 21:26:20 -08:00
FLOAT fullW = GetAnsiW(font, s);
2025-09-02 17:03:03 +10:00
2025-09-09 18:23:51 +10:00
MarqueeState& M = m_marq[paneIndex];
2025-12-05 21:26:20 -08:00
if (!isSelected){ // Not selected
2025-12-15 19:25:46 -08:00
char buf[512];
EllipsizeAnsiToFit(font, s, fitW_now + kMeasureFudgePx, buf, sizeof(buf));
DrawAnsiCentered(font, x, y, color, NULL, buf, NULL, h);
2025-12-05 21:26:20 -08:00
if (M.row == rowIndex) { M.row = -1; M.px = 0.0f; M.fitWLock = 0.0f; M.nextTick = 0; M.resetPause = 0; }
2025-09-02 17:03:03 +10:00
return;
}
2025-12-05 21:26:20 -08:00
// Selected and (near-)fits - draw and clear marquee
2025-09-09 18:23:51 +10:00
if (fullW <= fitW_now + kMeasureFudgePx + kNearFitSlackPx){
2025-12-05 21:26:20 -08:00
DrawAnsiCentered(font, x, y, color, NULL, s, NULL, h);
if (M.row == rowIndex) { M.row = -1; M.px = 0.0f; M.fitWLock = 0.0f; M.nextTick = 0; M.resetPause = 0; }
2025-09-09 18:23:51 +10:00
return;
2025-09-02 17:03:03 +10:00
}
2025-12-05 21:26:20 -08:00
// Selected and clipped - character-step marquee
2025-09-09 18:23:51 +10:00
const DWORD now = GetTickCount();
2025-09-02 17:03:03 +10:00
2025-09-09 18:23:51 +10:00
if (M.row != rowIndex){
M.row = rowIndex;
2025-12-05 21:26:20 -08:00
M.px = 0.0f; // start index
M.fitWLock = fitW_now; // lock visible width for stable end calculation
M.nextTick = now + kMarqInitPauseMs; // initial pause
2025-09-09 18:23:51 +10:00
M.resetPause= 0;
}
2025-12-05 21:26:20 -08:00
2025-09-09 18:23:51 +10:00
if (M.fitWLock <= 0.0f) M.fitWLock = fitW_now;
const FLOAT fitW = floorf(M.fitWLock + 0.5f);
// Earliest start index where the entire tail fits
2025-12-05 21:26:20 -08:00
size_t hi = len, lo = 0;
2025-09-09 18:23:51 +10:00
while (lo < hi){
int mid = (lo + hi) / 2;
2025-12-05 21:26:20 -08:00
FLOAT tw = GetAnsiW(font, s + mid);
if (tw <= fitW + kMeasureFudgePx) hi = mid;
else lo = mid + 1;
2025-09-09 18:23:51 +10:00
}
2025-12-05 21:26:20 -08:00
const int lastStart = min(lo, len);
2025-09-09 18:23:51 +10:00
// Current start index
int startIdx = (int)M.px;
2025-12-06 01:32:28 -08:00
startIdx = max(0, min(startIdx, lastStart));
2025-09-09 18:23:51 +10:00
// Longest substring from s+startIdx that fits into fitW
const char* startPtr = s + startIdx;
const int remaining = len - startIdx;
int lo2 = 0, hi2 = remaining;
2025-12-05 21:26:20 -08:00
2025-09-09 18:23:51 +10:00
while (lo2 < hi2){
int mid = (lo2 + hi2 + 1) / 2;
2025-12-05 21:26:20 -08:00
char tmp[512];
_snprintf(tmp, sizeof(tmp), "%.*s", mid, startPtr);
FLOAT tw = GetAnsiW(font, tmp);
if (tw <= fitW + kMeasureFudgePx) lo2 = mid;
else hi2 = mid - 1;
2025-09-09 18:23:51 +10:00
}
2025-12-05 21:26:20 -08:00
char vis[512];
_snprintf(vis, sizeof(vis), "%.*s", lo2, startPtr);
DrawAnsiCentered(font, x, y, color, NULL, vis, NULL, h);
2025-09-09 18:23:51 +10:00
// Step/pause/reset
2025-12-05 21:26:20 -08:00
if (now >= M.nextTick) {
if (M.resetPause) {
M.px = 0.0f; // hard reset to first char
M.resetPause = 0;
M.nextTick = now + kMarqInitPauseMs; // pause at start again
}
else {
2025-09-09 18:23:51 +10:00
if (startIdx >= lastStart){
2025-12-05 21:26:20 -08:00
M.px = (FLOAT)lastStart; // hold
M.resetPause = now + kMarqEndPauseMs;
M.nextTick = M.resetPause;
}
else {
M.px = (FLOAT)(startIdx + kMarqStepChars);
M.nextTick = now + kMarqStepMs;
2025-09-02 17:03:03 +10:00
}
}
}
}
2025-12-05 22:01:19 -08:00
// LOOPING MARQUEE: header path
2025-12-06 01:32:28 -08:00
void PaneRenderer::DrawHeaderFittedOrMarquee(CXBFont& font, FLOAT x, FLOAT y, FLOAT h, FLOAT maxW, DWORD color, const char* text, int paneIndex) {
2025-12-05 21:26:20 -08:00
const char* s = text ? text : ""; // Safety
const int len = strlen(s);
2025-09-09 18:23:51 +10:00
const FLOAT fitW_now = floorf(((maxW > kRightGuardPx) ? (maxW - kRightGuardPx) : 0.0f) + 0.5f);
// Reset header marquee if text changed
static char s_prevHdr[2][512] = { {0}, {0} };
2025-12-05 21:26:20 -08:00
if (_stricmp(s_prevHdr[paneIndex], s) != 0) {
2025-09-09 18:23:51 +10:00
_snprintf(s_prevHdr[paneIndex], sizeof(s_prevHdr[paneIndex]), "%s", s);
s_prevHdr[paneIndex][sizeof(s_prevHdr[paneIndex])-1] = 0;
m_hdrMarq[paneIndex] = MarqueeState();
m_hdrMarq[paneIndex].nextTick = GetTickCount() + kMarqInitPauseMs;
}
// Fits (with slack)? Draw and bail.
2025-12-05 21:26:20 -08:00
FLOAT tw = GetAnsiW(font, s);
if (tw <= fitW_now + kMeasureFudgePx + kNearFitSlackPx){
2025-12-06 01:32:28 -08:00
DrawAnsiCentered(font, x, y, color, &DefaultColors, s, NULL, h);
2025-09-09 18:23:51 +10:00
m_hdrMarq[paneIndex] = MarqueeState();
return;
}
// Character-step marquee
const DWORD now = GetTickCount();
MarqueeState& M = m_hdrMarq[paneIndex];
if (M.fitWLock <= 0.0f) M.fitWLock = fitW_now;
const FLOAT fitW = floorf(M.fitWLock + 0.5f);
// Earliest start where whole tail fits
int lo = 0, hi = len;
2025-12-05 21:26:20 -08:00
while (lo < hi) {
2025-09-09 18:23:51 +10:00
int mid = (lo + hi) / 2;
2025-12-05 21:26:20 -08:00
FLOAT tw = GetAnsiW(font, s + mid);
if (tw <= fitW + kMeasureFudgePx) hi = mid;
else lo = mid + 1;
2025-09-09 18:23:51 +10:00
}
2025-12-05 21:26:20 -08:00
2025-09-09 18:23:51 +10:00
const int lastStart = (lo > len) ? len : lo;
int startIdx = (int)M.px;
2025-12-06 01:32:28 -08:00
startIdx = max(0, min(startIdx, lastStart));
2025-09-09 18:23:51 +10:00
// Longest substring from s+startIdx that fits
const char* startPtr = s + startIdx;
2025-12-05 21:26:20 -08:00
const int remaining = len - startIdx;
2025-09-09 18:23:51 +10:00
int lo2 = 0, hi2 = remaining;
2025-12-05 21:26:20 -08:00
while (lo2 < hi2) {
int mid = (lo2 + hi2 + 1) / 2;
char tmp[512];
_snprintf(tmp, sizeof(tmp), "%.*s", mid, startPtr);
FLOAT tw = GetAnsiW(font, tmp);
if (tw <= fitW + kMeasureFudgePx) lo2 = mid;
else hi2 = mid - 1;
}
char vis[512];
_snprintf(vis, sizeof(vis), "%.*s", lo2, startPtr);
2025-12-06 01:32:28 -08:00
DrawAnsiCentered(font, x, y, color, &DefaultColors, vis, NULL, h);
2025-09-09 18:23:51 +10:00
// Step/pause/reset
2025-12-06 01:32:28 -08:00
if (now >= M.nextTick) {
if (M.resetPause) {
M.px = 0.0f;
M.resetPause = 0;
M.nextTick = now + kMarqInitPauseMs;
}
else {
if (startIdx >= lastStart) {
M.px = (FLOAT)lastStart;
M.resetPause = now + kMarqEndPauseMs;
M.nextTick = M.resetPause;
}
else {
M.px = (FLOAT)(startIdx + kMarqStepChars);
2025-12-05 21:26:20 -08:00
M.nextTick = now + kMarqStepMs;
2025-09-09 18:23:51 +10:00
}
}
}
}
2025-09-02 17:03:03 +10:00
2025-12-05 21:26:20 -08:00
void PaneRenderer::DrawPane(CXBFont& font, LPDIRECT3DDEVICE8 dev, FLOAT baseX, const Pane& p, bool active, const PaneStyle& st, int paneIndex) {
DrawSolidRect(dev, baseX, st.hdrY, st.hdrH ? st.hdrW : st.listW, st.hdrH, active ? 0xFF3A3A3A : 0x802A2A2A);
2025-09-09 18:23:51 +10:00
// Header text
char hdr[600];
if (p.mode == 0) _snprintf(hdr, sizeof(hdr), "%s", "Detected Drives");
2025-12-14 02:20:38 -08:00
else _snprintf(hdr, sizeof(hdr), "%s", p.curPath);
2025-12-05 21:26:20 -08:00
hdr[sizeof(hdr) - 1] = 0;
2025-09-06 21:54:55 +10:00
2025-09-09 18:23:51 +10:00
// Center vertically
const FLOAT headerLeft = baseX + 6.0f;
const FLOAT headerMaxW = st.listW - 12.0f;
2025-09-09 18:23:51 +10:00
// Draw header (fit or marquee)
2025-12-06 01:32:28 -08:00
FLOAT tW = GetAnsiW(font, hdr);
2025-09-09 18:23:51 +10:00
const FLOAT fitHdrW = (headerMaxW > kRightGuardPx) ? (headerMaxW - kRightGuardPx) : 0.0f;
if (tW <= fitHdrW + kMeasureFudgePx + kNearFitSlackPx) {
const FLOAT cx = Snap(headerLeft + (fitHdrW - tW) * 0.5f);
2025-12-06 01:32:28 -08:00
DrawAnsiCentered(font, cx, st.hdrY, 0xFFFFFFFF, &DefaultColors, hdr, NULL, st.hdrH);
2025-09-09 18:23:51 +10:00
m_hdrMarq[paneIndex] = MarqueeState();
2025-12-05 21:26:20 -08:00
}
2025-12-06 01:32:28 -08:00
else DrawHeaderFittedOrMarquee(font, headerLeft, st.hdrY, st.hdrH, headerMaxW, 0xFFFFFFFF, hdr, paneIndex);
2025-09-06 21:54:55 +10:00
2025-12-05 21:26:20 -08:00
// size column metrics (shared)
2025-09-09 18:23:51 +10:00
const FLOAT sizeRight = baseX + st.listW - (st.scrollBarW + st.paddingX);
2025-12-05 21:26:20 -08:00
const FLOAT sizeColX = sizeRight - s_sharedSizeColW;
2025-08-30 01:55:30 +10:00
2025-12-05 21:26:20 -08:00
// column headers
const FLOAT colHdrY = st.hdrY + st.hdrH + max(6.0f, st.lineH * 0.15f);
const FLOAT colHdrH = max(22.0f, st.lineH);
DrawSolidRect(dev, baseX, colHdrY, st.listW, colHdrH, 0x60333333);
2025-09-09 18:23:51 +10:00
const char* sizeHdr = (p.mode == 0) ? "Free / Total" : "Size";
2025-12-06 05:18:04 -08:00
DrawAnsiCentered(font, NameColX(baseX, st), colHdrY, HEADER_TEXT_COLOR, &DefaultColors, "Name", NULL, colHdrH);
DrawAnsiFromRight(font, sizeRight, colHdrY, HEADER_TEXT_COLOR, &DefaultColors, sizeHdr, colHdrH);
2025-09-02 14:26:19 +10:00
// underline + vertical divider
2025-12-05 21:26:20 -08:00
DrawSolidRect(dev, baseX, colHdrY + colHdrH, st.listW, 1.0f, 0x80444444);
DrawSolidRect(dev, sizeColX, colHdrY + 2.0f, 1.0f, colHdrH - 4.0f, 0x40444444);
2025-08-30 01:55:30 +10:00
2025-12-05 21:26:20 -08:00
// list background + nudge
2025-09-09 20:01:36 +10:00
const FLOAT listBgTop = colHdrY + colHdrH;
2025-12-05 21:26:20 -08:00
const FLOAT kFirstRowNudge = max(2.0f, st.lineH * 0.30f); // ~3px at 480p, scales up
const FLOAT listTop = Snap(listBgTop + kFirstRowNudge); // rows start here
const FLOAT listH = st.lineH * st.visibleRows;
2025-08-30 01:55:30 +10:00
2025-09-09 20:01:36 +10:00
// Fill background including the small gap introduced by the nudge
2025-12-05 21:26:20 -08:00
DrawSolidRect(dev, baseX, listBgTop, st.listW, kFirstRowNudge + listH, 0x30101010);
2025-09-09 20:01:36 +10:00
// alternating stripes (start at first row, not at underline)
2025-12-05 21:26:20 -08:00
int end = p.scroll + st.visibleRows;
if (end > p.items.size()) end = p.items.size();
int rowIndex = 0;
for (int i = p.scroll; i < end; ++i, ++rowIndex) {
D3DCOLOR stripe = (rowIndex & 1) ? 0x201E1E1E : 0x10000000;
2025-12-05 21:26:20 -08:00
DrawSolidRect(dev, baseX, listTop + rowIndex*st.lineH, st.listW, st.lineH, stripe);
2025-08-30 01:55:30 +10:00
}
2025-09-09 18:23:51 +10:00
2025-09-02 14:26:19 +10:00
// selection highlight
if (!p.items.empty() && p.sel >= p.scroll && p.sel < end) {
int selRow = p.sel - p.scroll;
2025-12-06 05:18:04 -08:00
DrawSolidRect(dev, baseX, listTop + selRow*st.lineH, st.listW, st.lineH, active ? ACTIVE_HIGHLIGHTED_ROW_COLOR : HIGHLIGHTED_ROW_COLOR);
2025-08-30 01:55:30 +10:00
}
2025-12-05 21:26:20 -08:00
// rows
2025-09-09 18:23:51 +10:00
FLOAT y = listTop;
for (int i = p.scroll, r = 0; i < end; ++i, ++r) {
const Item& it = p.items[i];
2025-12-06 05:18:04 -08:00
DWORD nameCol = (i == p.sel) ? HIGHLIGHTED_TEXT_COLOR : 0xFFE0E0E0;
DWORD sizeCol = (i == p.sel) ? HIGHLIGHTED_TEXT_COLOR : 0xFFB0B0B0;
2025-08-31 15:46:31 +10:00
2025-09-09 18:23:51 +10:00
// icon gutter
2025-12-05 21:26:20 -08:00
const FLOAT gutterX = baseX + 4.0f;
const FLOAT gutterW = st.gutterW;
const FLOAT gutterH = st.lineH;
2025-12-06 05:18:04 -08:00
const char glyph[2] = { it.icon, 0 };
DrawAnsiCentered(font, gutterX, y, MARKED_ITEM_COLOR, (it.marked ? NULL : &DefaultColors), glyph, gutterW, gutterH);
2025-08-30 01:55:30 +10:00
2025-09-09 18:23:51 +10:00
// filename area (compute available width once)
2025-12-05 21:26:20 -08:00
char nameBuf[300];
_snprintf(nameBuf, sizeof(nameBuf), "%s", it.name);
nameBuf[sizeof(nameBuf) - 1] = 0;
const FLOAT nameXRaw = NameColX(baseX, st);
const FLOAT rightPad = st.paddingX + st.scrollBarW;
2025-09-09 18:23:51 +10:00
const FLOAT kNameSafePad = 2.0f;
2025-12-05 21:26:20 -08:00
const FLOAT nameRightEdge = Snap((baseX + st.listW) - rightPad - s_sharedSizeColW - kNameSafePad - kRightGuardPx);
2025-09-09 18:23:51 +10:00
const FLOAT nameLeftEdge = Snap(nameXRaw);
FLOAT nameMaxW = nameRightEdge - nameLeftEdge;
2025-12-05 21:26:20 -08:00
nameMaxW = max(nameMaxW, 0.0f);
2025-08-31 17:16:38 +10:00
2025-09-02 17:03:03 +10:00
const bool isSel = active && (i == p.sel);
2025-12-05 21:26:20 -08:00
DrawNameFittedOrMarquee(font, nameLeftEdge, y, gutterH, nameMaxW, nameCol, nameBuf, isSel, paneIndex, i - p.scroll);
2025-08-30 01:55:30 +10:00
2025-09-09 18:23:51 +10:00
// size column
2025-09-02 17:03:03 +10:00
char sz[96] = "";
if (p.mode == 0 && it.isDir && !it.isUpEntry) {
2025-12-05 21:26:20 -08:00
ULONGLONG fb=0, tb=0;
GetDriveFreeTotal(it.name, fb, tb);
char f[32], t[32];
FormatSize(fb, f, sizeof(f));
FormatSize(tb, t, sizeof(t));
2025-09-02 17:03:03 +10:00
_snprintf(sz, sizeof(sz), "%s / %s", f, t);
2025-12-05 21:26:20 -08:00
}
else if (!it.isDir && !it.isUpEntry) FormatSize(it.size, sz, sizeof(sz));
2025-08-30 01:55:30 +10:00
2025-12-05 21:26:20 -08:00
DrawAnsiFromRight(font, sizeRight, y, sizeCol, &DefaultColors, sz, gutterH);
2025-08-30 01:55:30 +10:00
y += st.lineH;
}
2025-09-02 14:26:19 +10:00
// ----- scrollbar -----
2025-12-05 21:26:20 -08:00
int total = p.items.size();
if (total > st.visibleRows) {
2025-09-09 18:23:51 +10:00
const FLOAT trackX = baseX + st.listW - st.scrollBarW;
2025-12-05 21:26:20 -08:00
const FLOAT trackY = listTop; // align with first row after nudge
2025-09-09 18:23:51 +10:00
const FLOAT trackH = st.visibleRows * st.lineH;
2025-12-05 21:26:20 -08:00
DrawSolidRect(dev, trackX, trackY, st.scrollBarW, trackH, 0x40282828);
FLOAT thumbH = (FLOAT)st.visibleRows/(FLOAT)total * trackH;
thumbH = max(thumbH, 10.0f);
2025-09-02 14:26:19 +10:00
FLOAT maxScroll = (FLOAT)(total - st.visibleRows);
FLOAT t = (maxScroll > 0.0f) ? ((FLOAT)p.scroll / maxScroll) : 0.0f;
FLOAT thumbY = trackY + t * (trackH - thumbH);
2025-12-05 21:26:20 -08:00
DrawSolidRect(dev, trackX, thumbY, st.scrollBarW, thumbH, 0x80C0C0C0);
2025-08-30 01:55:30 +10:00
}
}
2025-09-09 20:01:36 +10:00