143 lines
4.6 KiB
C++
143 lines
4.6 KiB
C++
// action_bar — pure implementation. See action_bar.h.
|
|
|
|
#include "core/ui/action_bar.h"
|
|
|
|
#include <cstddef>
|
|
|
|
namespace reasampler::ui {
|
|
|
|
namespace {
|
|
|
|
int totalButtons(const std::vector<ClusterSpec>& clusters) {
|
|
int n = 0;
|
|
for (const ClusterSpec& c : clusters)
|
|
if (c.count > 0) n += c.count;
|
|
return n;
|
|
}
|
|
|
|
void fillTextRects(ActionBarSlot& s, const ActionBarSpec& /*spec*/) {
|
|
const int hpad = 4;
|
|
const int innerX = s.x + hpad;
|
|
const int innerW = s.width - 2 * hpad;
|
|
if (innerW <= 0) return;
|
|
s.labelX = innerX; s.labelY = s.y; s.labelW = innerW; s.labelH = s.height;
|
|
}
|
|
|
|
// The one placement routine; computeBarSlots and hitTestActionBar both drive it so draw and
|
|
// hit-test can't drift apart.
|
|
std::vector<ActionBarSlot> tile(const ActionBarRect& bar,
|
|
const std::vector<ClusterSpec>& clusters,
|
|
const ActionBarSpec& spec, int visible) {
|
|
std::vector<ActionBarSlot> slots;
|
|
if (visible <= 0) return slots;
|
|
slots.reserve(static_cast<std::size_t>(visible));
|
|
|
|
const int top = bar.y + spec.verticalInset;
|
|
const int btnH = bar.height - 2 * spec.verticalInset;
|
|
if (btnH <= 0) return slots;
|
|
|
|
int cursorX = bar.x + spec.sidePad;
|
|
int flatIndex = 0;
|
|
int placed = 0;
|
|
bool firstClusterEmitted = false;
|
|
|
|
for (const ClusterSpec& c : clusters) {
|
|
if (c.count <= 0) continue;
|
|
if (placed >= visible) break;
|
|
|
|
if (firstClusterEmitted) cursorX += spec.clusterGap;
|
|
firstClusterEmitted = true;
|
|
|
|
for (int i = 0; i < c.count; ++i, ++flatIndex) {
|
|
if (placed >= visible) return slots;
|
|
if (i > 0) cursorX += spec.buttonGap;
|
|
|
|
ActionBarSlot s;
|
|
s.index = flatIndex;
|
|
s.cluster = c.cluster;
|
|
s.x = cursorX;
|
|
s.y = top;
|
|
s.width = spec.buttonWidth;
|
|
s.height = btnH;
|
|
fillTextRects(s, spec);
|
|
slots.push_back(s);
|
|
|
|
cursorX += spec.buttonWidth;
|
|
++placed;
|
|
}
|
|
}
|
|
return slots;
|
|
}
|
|
|
|
// Mirrors tile()'s advance math so fit and layout agree.
|
|
int rightEdgeFor(const ActionBarRect& bar, const std::vector<ClusterSpec>& clusters,
|
|
const ActionBarSpec& spec, int visible) {
|
|
if (visible <= 0) return bar.x + spec.sidePad;
|
|
int cursorX = bar.x + spec.sidePad;
|
|
int placed = 0;
|
|
bool firstClusterEmitted = false;
|
|
for (const ClusterSpec& c : clusters) {
|
|
if (c.count <= 0) continue;
|
|
if (placed >= visible) break;
|
|
if (firstClusterEmitted) cursorX += spec.clusterGap;
|
|
firstClusterEmitted = true;
|
|
for (int i = 0; i < c.count; ++i) {
|
|
if (placed >= visible) return cursorX;
|
|
if (i > 0) cursorX += spec.buttonGap;
|
|
cursorX += spec.buttonWidth;
|
|
++placed;
|
|
if (placed >= visible) return cursorX;
|
|
}
|
|
}
|
|
return cursorX;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
BarFit computeBarFit(const ActionBarRect& bar, const std::vector<ClusterSpec>& clusters,
|
|
const ActionBarSpec& spec) {
|
|
BarFit fit;
|
|
const int total = totalButtons(clusters);
|
|
if (total <= 0 || bar.width <= 0 || bar.height <= 0 || spec.buttonWidth <= 0) {
|
|
fit.hiddenCount = total > 0 ? total : 0;
|
|
return fit;
|
|
}
|
|
|
|
const int usableRight = bar.x + bar.width - spec.sidePad;
|
|
int visible = 0;
|
|
for (int cand = 1; cand <= total; ++cand) {
|
|
if (rightEdgeFor(bar, clusters, spec, cand) <= usableRight)
|
|
visible = cand;
|
|
else
|
|
break;
|
|
}
|
|
fit.visibleCount = visible;
|
|
fit.hiddenCount = total - visible;
|
|
return fit;
|
|
}
|
|
|
|
std::vector<ActionBarSlot> computeBarSlots(const ActionBarRect& bar,
|
|
const std::vector<ClusterSpec>& clusters,
|
|
const ActionBarSpec& spec) {
|
|
if (bar.width <= 0 || bar.height <= 0 || spec.buttonWidth <= 0) return {};
|
|
const BarFit fit = computeBarFit(bar, clusters, spec);
|
|
return tile(bar, clusters, spec, fit.visibleCount);
|
|
}
|
|
|
|
int hitTestActionBar(int px, int py, const ActionBarRect& bar,
|
|
const std::vector<ClusterSpec>& clusters, const ActionBarSpec& spec) {
|
|
if (bar.height <= 0 || bar.width <= 0) return -1;
|
|
if (px < bar.x || px >= bar.x + bar.width ||
|
|
py < bar.y || py >= bar.y + bar.height)
|
|
return -1;
|
|
|
|
const std::vector<ActionBarSlot> slots = computeBarSlots(bar, clusters, spec);
|
|
for (const ActionBarSlot& s : slots) {
|
|
if (px >= s.x && px < s.x + s.width && py >= s.y && py < s.y + s.height)
|
|
return s.index;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
} // namespace reasampler::ui
|