DVX_GUI/dvx/widgets/widgetSplitter.c

335 lines
9.1 KiB
C

// widgetSplitter.c — Splitter (draggable divider between two panes)
#include "widgetInternal.h"
// ============================================================
// Prototypes
// ============================================================
static WidgetT *spFirstChild(WidgetT *w);
static WidgetT *spSecondChild(WidgetT *w);
// ============================================================
// spFirstChild — get first visible child
// ============================================================
static WidgetT *spFirstChild(WidgetT *w) {
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
if (c->visible) {
return c;
}
}
return NULL;
}
// ============================================================
// spSecondChild — get second visible child
// ============================================================
static WidgetT *spSecondChild(WidgetT *w) {
int32_t n = 0;
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
if (c->visible) {
n++;
if (n == 2) {
return c;
}
}
}
return NULL;
}
// ============================================================
// widgetSplitterClampPos — clamp divider to child minimums
// ============================================================
void widgetSplitterClampPos(WidgetT *w, int32_t *pos) {
WidgetT *c1 = spFirstChild(w);
WidgetT *c2 = spSecondChild(w);
bool vert = w->as.splitter.vertical;
int32_t totalSize = vert ? w->w : w->h;
int32_t minFirst = c1 ? (vert ? c1->calcMinW : c1->calcMinH) : SPLITTER_MIN_PANE;
int32_t minSecond = c2 ? (vert ? c2->calcMinW : c2->calcMinH) : SPLITTER_MIN_PANE;
if (minFirst < SPLITTER_MIN_PANE) {
minFirst = SPLITTER_MIN_PANE;
}
if (minSecond < SPLITTER_MIN_PANE) {
minSecond = SPLITTER_MIN_PANE;
}
int32_t maxPos = totalSize - SPLITTER_BAR_W - minSecond;
if (maxPos < minFirst) {
maxPos = minFirst;
}
*pos = clampInt(*pos, minFirst, maxPos);
}
// ============================================================
// widgetSplitterCalcMinSize
// ============================================================
void widgetSplitterCalcMinSize(WidgetT *w, const BitmapFontT *font) {
// Recursively measure children
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
widgetCalcMinSizeTree(c, font);
}
WidgetT *c1 = spFirstChild(w);
WidgetT *c2 = spSecondChild(w);
int32_t m1w = c1 ? c1->calcMinW : 0;
int32_t m1h = c1 ? c1->calcMinH : 0;
int32_t m2w = c2 ? c2->calcMinW : 0;
int32_t m2h = c2 ? c2->calcMinH : 0;
if (w->as.splitter.vertical) {
w->calcMinW = m1w + m2w + SPLITTER_BAR_W;
w->calcMinH = DVX_MAX(m1h, m2h);
} else {
w->calcMinW = DVX_MAX(m1w, m2w);
w->calcMinH = m1h + m2h + SPLITTER_BAR_W;
}
}
// ============================================================
// widgetSplitterLayout
// ============================================================
void widgetSplitterLayout(WidgetT *w, const BitmapFontT *font) {
WidgetT *c1 = spFirstChild(w);
WidgetT *c2 = spSecondChild(w);
int32_t pos = w->as.splitter.dividerPos;
widgetSplitterClampPos(w, &pos);
w->as.splitter.dividerPos = pos;
if (w->as.splitter.vertical) {
// Left pane
if (c1) {
c1->x = w->x;
c1->y = w->y;
c1->w = pos;
c1->h = w->h;
widgetLayoutChildren(c1, font);
}
// Right pane
if (c2) {
c2->x = w->x + pos + SPLITTER_BAR_W;
c2->y = w->y;
c2->w = w->w - pos - SPLITTER_BAR_W;
c2->h = w->h;
if (c2->w < 0) {
c2->w = 0;
}
widgetLayoutChildren(c2, font);
}
} else {
// Top pane
if (c1) {
c1->x = w->x;
c1->y = w->y;
c1->w = w->w;
c1->h = pos;
widgetLayoutChildren(c1, font);
}
// Bottom pane
if (c2) {
c2->x = w->x;
c2->y = w->y + pos + SPLITTER_BAR_W;
c2->w = w->w;
c2->h = w->h - pos - SPLITTER_BAR_W;
if (c2->h < 0) {
c2->h = 0;
}
widgetLayoutChildren(c2, font);
}
}
}
// ============================================================
// widgetSplitterOnMouse
// ============================================================
void widgetSplitterOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
int32_t pos = hit->as.splitter.dividerPos;
// Check if click is on the divider bar
bool onDivider;
if (hit->as.splitter.vertical) {
int32_t barX = hit->x + pos;
onDivider = (vx >= barX && vx < barX + SPLITTER_BAR_W);
} else {
int32_t barY = hit->y + pos;
onDivider = (vy >= barY && vy < barY + SPLITTER_BAR_W);
}
if (onDivider) {
// Start dragging
sDragSplitter = hit;
if (hit->as.splitter.vertical) {
sDragSplitStart = vx - hit->x - pos;
} else {
sDragSplitStart = vy - hit->y - pos;
}
return;
}
// Forward click to child widgets
WidgetT *child = NULL;
for (WidgetT *c = hit->firstChild; c; c = c->nextSibling) {
WidgetT *ch = widgetHitTest(c, vx, vy);
if (ch) {
child = ch;
}
}
if (child && child->enabled && child->wclass && child->wclass->onMouse) {
if (sFocusedWidget && sFocusedWidget != child) {
sFocusedWidget->focused = false;
}
child->wclass->onMouse(child, root, vx, vy);
if (child->focused) {
sFocusedWidget = child;
}
}
wgtInvalidatePaint(hit);
}
// ============================================================
// widgetSplitterPaint
// ============================================================
void widgetSplitterPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
int32_t pos = w->as.splitter.dividerPos;
// Paint first child with clip rect
WidgetT *c1 = spFirstChild(w);
WidgetT *c2 = spSecondChild(w);
int32_t oldClipX = d->clipX;
int32_t oldClipY = d->clipY;
int32_t oldClipW = d->clipW;
int32_t oldClipH = d->clipH;
if (c1) {
if (w->as.splitter.vertical) {
setClipRect(d, w->x, w->y, pos, w->h);
} else {
setClipRect(d, w->x, w->y, w->w, pos);
}
widgetPaintOne(c1, d, ops, font, colors);
setClipRect(d, oldClipX, oldClipY, oldClipW, oldClipH);
}
if (c2) {
if (w->as.splitter.vertical) {
setClipRect(d, w->x + pos + SPLITTER_BAR_W, w->y, w->w - pos - SPLITTER_BAR_W, w->h);
} else {
setClipRect(d, w->x, w->y + pos + SPLITTER_BAR_W, w->w, w->h - pos - SPLITTER_BAR_W);
}
widgetPaintOne(c2, d, ops, font, colors);
setClipRect(d, oldClipX, oldClipY, oldClipW, oldClipH);
}
// Draw divider bar — raised bevel
BevelStyleT bevel = BEVEL_RAISED(colors, 1);
if (w->as.splitter.vertical) {
int32_t barX = w->x + pos;
drawBevel(d, ops, barX, w->y, SPLITTER_BAR_W, w->h, &bevel);
// Gripper — row of embossed 2x2 bumps centered vertically
int32_t gx = barX + 1;
int32_t midY = w->y + w->h / 2;
int32_t count = 5;
for (int32_t i = -count; i <= count; i++) {
int32_t dy = midY + i * 3;
drawHLine(d, ops, gx, dy, 2, colors->windowHighlight);
drawHLine(d, ops, gx + 1, dy + 1, 2, colors->windowShadow);
}
} else {
int32_t barY = w->y + pos;
drawBevel(d, ops, w->x, barY, w->w, SPLITTER_BAR_W, &bevel);
// Gripper — row of embossed 2x2 bumps centered horizontally
int32_t gy = barY + 1;
int32_t midX = w->x + w->w / 2;
int32_t count = 5;
for (int32_t i = -count; i <= count; i++) {
int32_t dx = midX + i * 3;
drawHLine(d, ops, dx, gy, 1, colors->windowHighlight);
drawHLine(d, ops, dx + 1, gy, 1, colors->windowShadow);
drawHLine(d, ops, dx, gy + 1, 1, colors->windowHighlight);
drawHLine(d, ops, dx + 1, gy + 1, 1, colors->windowShadow);
}
}
}
// ============================================================
// wgtSplitter
// ============================================================
WidgetT *wgtSplitter(WidgetT *parent, bool vertical) {
WidgetT *w = widgetAlloc(parent, WidgetSplitterE);
if (w) {
w->as.splitter.vertical = vertical;
w->as.splitter.dividerPos = 0;
w->weight = 100;
}
return w;
}
// ============================================================
// wgtSplitterGetPos
// ============================================================
int32_t wgtSplitterGetPos(const WidgetT *w) {
return w->as.splitter.dividerPos;
}
// ============================================================
// wgtSplitterSetPos
// ============================================================
void wgtSplitterSetPos(WidgetT *w, int32_t pos) {
w->as.splitter.dividerPos = pos;
}