You've already forked FileManager
mirror of
https://github.com/Team-Resurgent/FileManager.git
synced 2026-04-30 10:38:50 -07:00
368 lines
14 KiB
C++
368 lines
14 KiB
C++
#include "PaneRenderer.h"
|
|
|
|
#include "drawUtils.h"
|
|
#include "configuration.h"
|
|
|
|
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
|
|
|
|
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;
|
|
|
|
void PaneRenderer::PrimeSharedSizeColW(CXBFont& font, const Pane& p, const PaneStyle& st) {
|
|
const char* hdr = (p.mode == 0) ? "Free / Total" : "Size";
|
|
FLOAT maxW = GetAnsiW(font, hdr);
|
|
char buf[128];
|
|
|
|
if (p.mode == 0){ // mode == 0
|
|
int limit = ((int)p.items.size() > 32) ? 32 : (int)p.items.size();
|
|
|
|
for (int i=0;i<limit;++i){
|
|
const Item& it = p.items[i];
|
|
if (it.isDir && !it.isUpEntry){
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
else { // mode == 1
|
|
int limit = (p.items.size() > 200) ? 200 : p.items.size();
|
|
|
|
for (int i=0;i<limit;++i){
|
|
const Item& it = p.items[i];
|
|
if (!it.isDir && !it.isUpEntry){
|
|
FormatSize(it.size, buf, sizeof(buf));
|
|
maxW = max(GetAnsiW(font, buf), maxW);
|
|
}
|
|
}
|
|
}
|
|
|
|
maxW += 12.0f; // Why?
|
|
|
|
const FLOAT minW = max(90.0f, st.lineH * 3.5f);
|
|
const FLOAT maxWClamp = st.listW * 0.45f;
|
|
|
|
maxW = max(minW, maxW);
|
|
maxW = min(maxWClamp, maxW);
|
|
s_sharedSizeColW = max(s_sharedSizeColW, maxW);
|
|
}
|
|
|
|
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);
|
|
const FLOAT fitW_now = floorf(((maxW > 0.0f) ? maxW : 0.0f) + 0.5f);
|
|
|
|
FLOAT fullW = GetAnsiW(font, s);
|
|
|
|
MarqueeState& M = m_marq[paneIndex];
|
|
|
|
if (!isSelected){ // Not selected
|
|
char buf[512];
|
|
EllipsizeAnsiToFit(font, s, fitW_now + kMeasureFudgePx, buf, sizeof(buf));
|
|
DrawAnsiCentered(font, x, y, color, NULL, buf, NULL, h);
|
|
|
|
if (M.row == rowIndex) { M.row = -1; M.px = 0.0f; M.fitWLock = 0.0f; M.nextTick = 0; M.resetPause = 0; }
|
|
return;
|
|
}
|
|
|
|
// Selected and (near-)fits - draw and clear marquee
|
|
if (fullW <= fitW_now + kMeasureFudgePx + kNearFitSlackPx){
|
|
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; }
|
|
return;
|
|
}
|
|
|
|
// Selected and clipped - character-step marquee
|
|
const DWORD now = GetTickCount();
|
|
|
|
if (M.row != rowIndex){
|
|
M.row = rowIndex;
|
|
M.px = 0.0f; // start index
|
|
M.fitWLock = fitW_now; // lock visible width for stable end calculation
|
|
M.nextTick = now + kMarqInitPauseMs; // initial pause
|
|
M.resetPause= 0;
|
|
}
|
|
|
|
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
|
|
size_t hi = len, lo = 0;
|
|
while (lo < hi){
|
|
int mid = (lo + hi) / 2;
|
|
FLOAT tw = GetAnsiW(font, s + mid);
|
|
if (tw <= fitW + kMeasureFudgePx) hi = mid;
|
|
else lo = mid + 1;
|
|
}
|
|
const int lastStart = min(lo, len);
|
|
|
|
// Current start index
|
|
int startIdx = (int)M.px;
|
|
startIdx = max(0, min(startIdx, lastStart));
|
|
|
|
// Longest substring from s+startIdx that fits into fitW
|
|
const char* startPtr = s + startIdx;
|
|
const int remaining = len - startIdx;
|
|
int lo2 = 0, hi2 = remaining;
|
|
|
|
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);
|
|
DrawAnsiCentered(font, x, y, color, NULL, vis, NULL, h);
|
|
|
|
// Step/pause/reset
|
|
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 {
|
|
if (startIdx >= lastStart){
|
|
M.px = (FLOAT)lastStart; // hold
|
|
M.resetPause = now + kMarqEndPauseMs;
|
|
M.nextTick = M.resetPause;
|
|
}
|
|
else {
|
|
M.px = (FLOAT)(startIdx + kMarqStepChars);
|
|
M.nextTick = now + kMarqStepMs;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// LOOPING MARQUEE: header path
|
|
void PaneRenderer::DrawHeaderFittedOrMarquee(CXBFont& font, FLOAT x, FLOAT y, FLOAT h, FLOAT maxW, DWORD color, const char* text, int paneIndex) {
|
|
const char* s = text ? text : ""; // Safety
|
|
const int len = strlen(s);
|
|
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} };
|
|
if (_stricmp(s_prevHdr[paneIndex], s) != 0) {
|
|
_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.
|
|
FLOAT tw = GetAnsiW(font, s);
|
|
if (tw <= fitW_now + kMeasureFudgePx + kNearFitSlackPx){
|
|
DrawAnsiCentered(font, x, y, color, &DefaultColors, s, NULL, h);
|
|
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;
|
|
while (lo < hi) {
|
|
int mid = (lo + hi) / 2;
|
|
FLOAT tw = GetAnsiW(font, s + mid);
|
|
if (tw <= fitW + kMeasureFudgePx) hi = mid;
|
|
else lo = mid + 1;
|
|
}
|
|
|
|
const int lastStart = (lo > len) ? len : lo;
|
|
|
|
int startIdx = (int)M.px;
|
|
startIdx = max(0, min(startIdx, lastStart));
|
|
|
|
// Longest substring from s+startIdx that fits
|
|
const char* startPtr = s + startIdx;
|
|
const int remaining = len - startIdx;
|
|
int lo2 = 0, hi2 = remaining;
|
|
|
|
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);
|
|
DrawAnsiCentered(font, x, y, color, &DefaultColors, vis, NULL, h);
|
|
|
|
// Step/pause/reset
|
|
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);
|
|
M.nextTick = now + kMarqStepMs;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
// Header text
|
|
char hdr[600];
|
|
if (p.mode == 0) _snprintf(hdr, sizeof(hdr), "%s", "Detected Drives");
|
|
else _snprintf(hdr, sizeof(hdr), "%s", p.curPath);
|
|
hdr[sizeof(hdr) - 1] = 0;
|
|
|
|
// Center vertically
|
|
const FLOAT headerLeft = baseX + 6.0f;
|
|
const FLOAT headerMaxW = st.listW - 12.0f;
|
|
|
|
// Draw header (fit or marquee)
|
|
FLOAT tW = GetAnsiW(font, hdr);
|
|
const FLOAT fitHdrW = (headerMaxW > kRightGuardPx) ? (headerMaxW - kRightGuardPx) : 0.0f;
|
|
if (tW <= fitHdrW + kMeasureFudgePx + kNearFitSlackPx) {
|
|
const FLOAT cx = Snap(headerLeft + (fitHdrW - tW) * 0.5f);
|
|
DrawAnsiCentered(font, cx, st.hdrY, 0xFFFFFFFF, &DefaultColors, hdr, NULL, st.hdrH);
|
|
m_hdrMarq[paneIndex] = MarqueeState();
|
|
}
|
|
else DrawHeaderFittedOrMarquee(font, headerLeft, st.hdrY, st.hdrH, headerMaxW, 0xFFFFFFFF, hdr, paneIndex);
|
|
|
|
// size column metrics (shared)
|
|
const FLOAT sizeRight = baseX + st.listW - (st.scrollBarW + st.paddingX);
|
|
const FLOAT sizeColX = sizeRight - s_sharedSizeColW;
|
|
|
|
// 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);
|
|
|
|
const char* sizeHdr = (p.mode == 0) ? "Free / Total" : "Size";
|
|
|
|
DrawAnsiCentered(font, NameColX(baseX, st), colHdrY, HEADER_TEXT_COLOR, &DefaultColors, "Name", NULL, colHdrH);
|
|
DrawAnsiFromRight(font, sizeRight, colHdrY, HEADER_TEXT_COLOR, &DefaultColors, sizeHdr, colHdrH);
|
|
|
|
// underline + vertical divider
|
|
DrawSolidRect(dev, baseX, colHdrY + colHdrH, st.listW, 1.0f, 0x80444444);
|
|
DrawSolidRect(dev, sizeColX, colHdrY + 2.0f, 1.0f, colHdrH - 4.0f, 0x40444444);
|
|
|
|
// list background + nudge
|
|
const FLOAT listBgTop = colHdrY + colHdrH;
|
|
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;
|
|
|
|
// Fill background including the small gap introduced by the nudge
|
|
DrawSolidRect(dev, baseX, listBgTop, st.listW, kFirstRowNudge + listH, 0x30101010);
|
|
|
|
// alternating stripes (start at first row, not at underline)
|
|
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;
|
|
DrawSolidRect(dev, baseX, listTop + rowIndex*st.lineH, st.listW, st.lineH, stripe);
|
|
}
|
|
|
|
// selection highlight
|
|
if (!p.items.empty() && p.sel >= p.scroll && p.sel < end) {
|
|
int selRow = p.sel - p.scroll;
|
|
DrawSolidRect(dev, baseX, listTop + selRow*st.lineH, st.listW, st.lineH, active ? ACTIVE_HIGHLIGHTED_ROW_COLOR : HIGHLIGHTED_ROW_COLOR);
|
|
}
|
|
|
|
// rows
|
|
FLOAT y = listTop;
|
|
for (int i = p.scroll, r = 0; i < end; ++i, ++r) {
|
|
const Item& it = p.items[i];
|
|
DWORD nameCol = (i == p.sel) ? HIGHLIGHTED_TEXT_COLOR : 0xFFE0E0E0;
|
|
DWORD sizeCol = (i == p.sel) ? HIGHLIGHTED_TEXT_COLOR : 0xFFB0B0B0;
|
|
|
|
// icon gutter
|
|
const FLOAT gutterX = baseX + 4.0f;
|
|
const FLOAT gutterW = st.gutterW;
|
|
const FLOAT gutterH = st.lineH;
|
|
|
|
const char glyph[2] = { it.icon, 0 };
|
|
DrawAnsiCentered(font, gutterX, y, MARKED_ITEM_COLOR, (it.marked ? NULL : &DefaultColors), glyph, gutterW, gutterH);
|
|
|
|
// filename area (compute available width once)
|
|
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;
|
|
const FLOAT kNameSafePad = 2.0f;
|
|
|
|
const FLOAT nameRightEdge = Snap((baseX + st.listW) - rightPad - s_sharedSizeColW - kNameSafePad - kRightGuardPx);
|
|
const FLOAT nameLeftEdge = Snap(nameXRaw);
|
|
FLOAT nameMaxW = nameRightEdge - nameLeftEdge;
|
|
nameMaxW = max(nameMaxW, 0.0f);
|
|
|
|
const bool isSel = active && (i == p.sel);
|
|
DrawNameFittedOrMarquee(font, nameLeftEdge, y, gutterH, nameMaxW, nameCol, nameBuf, isSel, paneIndex, i - p.scroll);
|
|
|
|
// size column
|
|
char sz[96] = "";
|
|
if (p.mode == 0 && it.isDir && !it.isUpEntry) {
|
|
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));
|
|
_snprintf(sz, sizeof(sz), "%s / %s", f, t);
|
|
}
|
|
else if (!it.isDir && !it.isUpEntry) FormatSize(it.size, sz, sizeof(sz));
|
|
|
|
DrawAnsiFromRight(font, sizeRight, y, sizeCol, &DefaultColors, sz, gutterH);
|
|
y += st.lineH;
|
|
}
|
|
|
|
// ----- scrollbar -----
|
|
int total = p.items.size();
|
|
if (total > st.visibleRows) {
|
|
const FLOAT trackX = baseX + st.listW - st.scrollBarW;
|
|
const FLOAT trackY = listTop; // align with first row after nudge
|
|
const FLOAT trackH = st.visibleRows * st.lineH;
|
|
|
|
DrawSolidRect(dev, trackX, trackY, st.scrollBarW, trackH, 0x40282828);
|
|
|
|
FLOAT thumbH = (FLOAT)st.visibleRows/(FLOAT)total * trackH;
|
|
thumbH = max(thumbH, 10.0f);
|
|
|
|
FLOAT maxScroll = (FLOAT)(total - st.visibleRows);
|
|
FLOAT t = (maxScroll > 0.0f) ? ((FLOAT)p.scroll / maxScroll) : 0.0f;
|
|
FLOAT thumbY = trackY + t * (trackH - thumbH);
|
|
DrawSolidRect(dev, trackX, thumbY, st.scrollBarW, thumbH, 0x80C0C0C0);
|
|
}
|
|
}
|
|
|