mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-08-03 03:23:35 -04:00
Two of the types FrameXML leans on hardest. A backdrop is the bordered panel nearly every window in the original interface is built from, and StatusBar is health, mana, cast bars and experience all at once — without them a panel comes out as a flat rectangle. The edge file is a strip of eight square tiles. That is measured, not assumed: UI-Tooltip-Border is 128x16 and UI-DialogBox-Border 256x32, both exactly eight wide. Edges are drawn before corners so a corner is never clipped by the run it meets, and the background sits inside the insets so it cannot show through the border over it. A frame carrying either now draws, beneath its own regions, because those sit a level above it. The shadowing problem that cost two rounds is now structurally impossible rather than something to keep noticing. Checking the C binding table against the Lua definitions caught SetBackdrop and both its colour setters about to be lost the same way EnableMouse was — so the bindings are simply re-applied after the bootstrap Lua runs. A no-op that answers and does nothing is much harder to spot than one that errors, and this was the fourth instance. Adding a StatusBar to the demo found an emitter bug: a nested frame's anchors were resolved against UIParent instead of the frame containing it, so anything inside a panel was positioned against the screen. FrameXML nests constantly. Inside a template the container is not known until replay, so the parent is asked for then. Still 228 of 228 real XML files parsing; four more widget tests and two more emitter tests.
482 lines
18 KiB
C++
482 lines
18 KiB
C++
#include <catch_amalgamated.hpp>
|
|
|
|
#include "ui/widget_tree.hpp"
|
|
|
|
#include <string>
|
|
|
|
using namespace wowee::ui;
|
|
|
|
// The anchor solver is what every frame's position comes out of, and it is the
|
|
// part of a widget system that is wrong in ways nothing reports: a frame lands
|
|
// somewhere plausible and only looks wrong against the art it was meant to sit
|
|
// on. Pin the rules directly.
|
|
//
|
|
// Coordinates are WoW's: origin bottom-left, y upward.
|
|
|
|
namespace {
|
|
constexpr float kScreenW = 1024.0f;
|
|
constexpr float kScreenH = 768.0f;
|
|
}
|
|
|
|
TEST_CASE("Anchor point names resolve to rect fractions", "[widget][anchor]") {
|
|
auto p = [](const char* n) { return resolveAnchorPoint(n); };
|
|
|
|
REQUIRE(p("BOTTOMLEFT").fx == Catch::Approx(0.0f));
|
|
REQUIRE(p("BOTTOMLEFT").fy == Catch::Approx(0.0f));
|
|
REQUIRE(p("TOPRIGHT").fx == Catch::Approx(1.0f));
|
|
REQUIRE(p("TOPRIGHT").fy == Catch::Approx(1.0f));
|
|
REQUIRE(p("CENTER").fx == Catch::Approx(0.5f));
|
|
REQUIRE(p("CENTER").fy == Catch::Approx(0.5f));
|
|
|
|
// The combined names carry both halves; TOP must not be read as "not
|
|
// bottom, therefore centred".
|
|
REQUIRE(p("TOP").fx == Catch::Approx(0.5f));
|
|
REQUIRE(p("TOP").fy == Catch::Approx(1.0f));
|
|
REQUIRE(p("LEFT").fx == Catch::Approx(0.0f));
|
|
REQUIRE(p("LEFT").fy == Catch::Approx(0.5f));
|
|
|
|
REQUIRE(p("topleft").fx == Catch::Approx(0.0f)); // case-insensitive
|
|
REQUIRE(p("NONSENSE").fx == Catch::Approx(0.5f)); // unknown falls to CENTER
|
|
}
|
|
|
|
TEST_CASE("UIParent fills the screen", "[widget][layout]") {
|
|
WidgetTree tree;
|
|
tree.layout(kScreenW, kScreenH);
|
|
const Widget* root = tree.get(tree.root());
|
|
REQUIRE(root != nullptr);
|
|
REQUIRE(root->left == Catch::Approx(0.0f));
|
|
REQUIRE(root->bottom == Catch::Approx(0.0f));
|
|
REQUIRE(root->rectW == Catch::Approx(kScreenW));
|
|
REQUIRE(root->rectH == Catch::Approx(kScreenH));
|
|
}
|
|
|
|
TEST_CASE("One anchor plus a size positions the frame", "[widget][layout]") {
|
|
WidgetTree tree;
|
|
const uint32_t f = tree.create(WidgetKind::Frame, 0, "F");
|
|
Widget* w = tree.get(f);
|
|
w->width = 100.0f;
|
|
w->height = 50.0f;
|
|
|
|
Anchor a;
|
|
a.point = "BOTTOMLEFT";
|
|
a.relativePoint = "BOTTOMLEFT";
|
|
a.x = 10.0f;
|
|
a.y = 20.0f;
|
|
tree.addPoint(f, a);
|
|
|
|
tree.layout(kScreenW, kScreenH);
|
|
REQUIRE(w->left == Catch::Approx(10.0f));
|
|
REQUIRE(w->bottom == Catch::Approx(20.0f));
|
|
REQUIRE(w->rectW == Catch::Approx(100.0f));
|
|
REQUIRE(w->rectH == Catch::Approx(50.0f));
|
|
}
|
|
|
|
TEST_CASE("Anchoring by CENTER offsets from the middle of the parent",
|
|
"[widget][layout]") {
|
|
WidgetTree tree;
|
|
const uint32_t f = tree.create(WidgetKind::Frame, 0, "F");
|
|
Widget* w = tree.get(f);
|
|
w->width = 200.0f;
|
|
w->height = 100.0f;
|
|
Anchor a; // defaults are CENTER to CENTER
|
|
tree.addPoint(f, a);
|
|
|
|
tree.layout(kScreenW, kScreenH);
|
|
// Its centre lands on the screen centre, so its corner is half its size away.
|
|
REQUIRE(w->left == Catch::Approx(kScreenW * 0.5f - 100.0f));
|
|
REQUIRE(w->bottom == Catch::Approx(kScreenH * 0.5f - 50.0f));
|
|
}
|
|
|
|
TEST_CASE("Two opposing anchors derive the size", "[widget][layout]") {
|
|
// This is what SetAllPoints relies on, and what most of FrameXML's
|
|
// backgrounds and borders use instead of ever stating a size.
|
|
WidgetTree tree;
|
|
const uint32_t f = tree.create(WidgetKind::Frame, 0, "F");
|
|
Widget* w = tree.get(f);
|
|
w->width = 1.0f; // deliberately wrong; the anchors must win
|
|
w->height = 1.0f;
|
|
|
|
Anchor tl; tl.point = "TOPLEFT"; tl.relativePoint = "TOPLEFT"; tl.x = 40.0f; tl.y = -30.0f;
|
|
Anchor br; br.point = "BOTTOMRIGHT"; br.relativePoint = "BOTTOMRIGHT"; br.x = -60.0f; br.y = 50.0f;
|
|
tree.addPoint(f, tl);
|
|
tree.addPoint(f, br);
|
|
|
|
tree.layout(kScreenW, kScreenH);
|
|
REQUIRE(w->left == Catch::Approx(40.0f));
|
|
REQUIRE(w->bottom == Catch::Approx(50.0f));
|
|
REQUIRE(w->rectW == Catch::Approx(kScreenW - 40.0f - 60.0f));
|
|
REQUIRE(w->rectH == Catch::Approx(kScreenH - 30.0f - 50.0f));
|
|
}
|
|
|
|
TEST_CASE("SetAllPoints matches the target exactly", "[widget][layout]") {
|
|
WidgetTree tree;
|
|
const uint32_t parent = tree.create(WidgetKind::Frame, 0, "P");
|
|
Widget* p = tree.get(parent);
|
|
p->width = 300.0f;
|
|
p->height = 200.0f;
|
|
Anchor pa; pa.point = "BOTTOMLEFT"; pa.relativePoint = "BOTTOMLEFT"; pa.x = 12.0f; pa.y = 34.0f;
|
|
tree.addPoint(parent, pa);
|
|
|
|
const uint32_t tex = tree.create(WidgetKind::Texture, parent, "");
|
|
tree.setAllPoints(tex, parent);
|
|
|
|
tree.layout(kScreenW, kScreenH);
|
|
const Widget* t = tree.get(tex);
|
|
REQUIRE(t->left == Catch::Approx(p->left));
|
|
REQUIRE(t->bottom == Catch::Approx(p->bottom));
|
|
REQUIRE(t->rectW == Catch::Approx(p->rectW));
|
|
REQUIRE(t->rectH == Catch::Approx(p->rectH));
|
|
}
|
|
|
|
TEST_CASE("A frame can anchor to a sibling, not just its parent", "[widget][layout]") {
|
|
WidgetTree tree;
|
|
const uint32_t a = tree.create(WidgetKind::Frame, 0, "A");
|
|
tree.get(a)->width = 50.0f;
|
|
tree.get(a)->height = 50.0f;
|
|
Anchor aa; aa.point = "BOTTOMLEFT"; aa.relativePoint = "BOTTOMLEFT"; aa.x = 100.0f; aa.y = 100.0f;
|
|
tree.addPoint(a, aa);
|
|
|
|
const uint32_t b = tree.create(WidgetKind::Frame, 0, "B");
|
|
tree.get(b)->width = 50.0f;
|
|
tree.get(b)->height = 50.0f;
|
|
Anchor ba;
|
|
ba.point = "LEFT";
|
|
ba.relativeTo = a;
|
|
ba.relativePoint = "RIGHT";
|
|
ba.x = 8.0f;
|
|
tree.addPoint(b, ba);
|
|
|
|
tree.layout(kScreenW, kScreenH);
|
|
// Sits just right of A, vertically centred on it.
|
|
REQUIRE(tree.get(b)->left == Catch::Approx(158.0f));
|
|
REQUIRE(tree.get(b)->bottom == Catch::Approx(100.0f));
|
|
}
|
|
|
|
TEST_CASE("Hiding a frame hides everything under it", "[widget][layout]") {
|
|
WidgetTree tree;
|
|
const uint32_t parent = tree.create(WidgetKind::Frame, 0, "P");
|
|
tree.get(parent)->width = 100.0f;
|
|
tree.get(parent)->height = 100.0f;
|
|
tree.addPoint(parent, Anchor{});
|
|
|
|
const uint32_t tex = tree.create(WidgetKind::Texture, parent, "");
|
|
tree.get(tex)->texturePath = "Interface\\Buttons\\Button-Backpack-Up.blp";
|
|
tree.setAllPoints(tex, parent);
|
|
|
|
tree.layout(kScreenW, kScreenH);
|
|
REQUIRE(tree.drawOrder().size() == 1);
|
|
|
|
tree.get(parent)->shown = false;
|
|
tree.layout(kScreenW, kScreenH);
|
|
REQUIRE(tree.drawOrder().empty());
|
|
}
|
|
|
|
TEST_CASE("Draw order runs strata, then level, then layer", "[widget][draworder]") {
|
|
WidgetTree tree;
|
|
auto addTexture = [&](uint32_t parent, DrawLayer layer, const char* name) {
|
|
const uint32_t t = tree.create(WidgetKind::Texture, parent, name);
|
|
Widget* w = tree.get(t);
|
|
w->texturePath = "x.blp";
|
|
w->layer = layer;
|
|
tree.setAllPoints(t, parent);
|
|
return t;
|
|
};
|
|
|
|
const uint32_t lowFrame = tree.create(WidgetKind::Frame, 0, "Low");
|
|
tree.get(lowFrame)->strata = FrameStrata::Low;
|
|
tree.get(lowFrame)->strataExplicit = true;
|
|
tree.get(lowFrame)->width = 10.0f;
|
|
tree.get(lowFrame)->height = 10.0f;
|
|
tree.addPoint(lowFrame, Anchor{});
|
|
|
|
const uint32_t highFrame = tree.create(WidgetKind::Frame, 0, "High");
|
|
tree.get(highFrame)->strata = FrameStrata::High;
|
|
tree.get(highFrame)->strataExplicit = true;
|
|
tree.get(highFrame)->width = 10.0f;
|
|
tree.get(highFrame)->height = 10.0f;
|
|
tree.addPoint(highFrame, Anchor{});
|
|
|
|
// Deliberately created in the wrong order: an OVERLAY in a low stratum must
|
|
// still fall behind a BACKGROUND in a high one.
|
|
const uint32_t highBackground = addTexture(highFrame, DrawLayer::Background, "highBg");
|
|
const uint32_t lowOverlay = addTexture(lowFrame, DrawLayer::Overlay, "lowOver");
|
|
const uint32_t lowBackground = addTexture(lowFrame, DrawLayer::Background, "lowBg");
|
|
|
|
tree.layout(kScreenW, kScreenH);
|
|
const auto& order = tree.drawOrder();
|
|
REQUIRE(order.size() == 3);
|
|
|
|
auto positionOf = [&](uint32_t id) {
|
|
for (size_t i = 0; i < order.size(); ++i) if (order[i]->id == id) return i;
|
|
return order.size();
|
|
};
|
|
REQUIRE(positionOf(lowBackground) < positionOf(lowOverlay));
|
|
REQUIRE(positionOf(lowOverlay) < positionOf(highBackground));
|
|
}
|
|
|
|
TEST_CASE("A child frame draws over its parent", "[widget][draworder]") {
|
|
WidgetTree tree;
|
|
const uint32_t parent = tree.create(WidgetKind::Frame, 0, "P");
|
|
tree.get(parent)->width = 100.0f;
|
|
tree.get(parent)->height = 100.0f;
|
|
tree.addPoint(parent, Anchor{});
|
|
const uint32_t parentArt = tree.create(WidgetKind::Texture, parent, "pa");
|
|
tree.get(parentArt)->texturePath = "p.blp";
|
|
tree.setAllPoints(parentArt, parent);
|
|
|
|
const uint32_t child = tree.create(WidgetKind::Frame, parent, "C");
|
|
tree.setAllPoints(child, parent);
|
|
const uint32_t childArt = tree.create(WidgetKind::Texture, child, "ca");
|
|
tree.get(childArt)->texturePath = "c.blp";
|
|
tree.setAllPoints(childArt, child);
|
|
|
|
tree.layout(kScreenW, kScreenH);
|
|
const auto& order = tree.drawOrder();
|
|
REQUIRE(order.size() == 2);
|
|
REQUIRE(order[0]->id == parentArt);
|
|
REQUIRE(order[1]->id == childArt);
|
|
}
|
|
|
|
TEST_CASE("Nothing to draw is not drawn", "[widget][draworder]") {
|
|
WidgetTree tree;
|
|
const uint32_t parent = tree.create(WidgetKind::Frame, 0, "P");
|
|
tree.get(parent)->width = 100.0f;
|
|
tree.get(parent)->height = 100.0f;
|
|
tree.addPoint(parent, Anchor{});
|
|
|
|
// A frame is a container and paints nothing itself.
|
|
tree.layout(kScreenW, kScreenH);
|
|
REQUIRE(tree.drawOrder().empty());
|
|
|
|
// A texture with no source, and a font string with no text, likewise.
|
|
const uint32_t empty = tree.create(WidgetKind::Texture, parent, "");
|
|
tree.setAllPoints(empty, parent);
|
|
const uint32_t blank = tree.create(WidgetKind::FontString, parent, "");
|
|
tree.setAllPoints(blank, parent);
|
|
tree.layout(kScreenW, kScreenH);
|
|
REQUIRE(tree.drawOrder().empty());
|
|
|
|
// A solid colour counts as a source even with no file behind it.
|
|
tree.get(empty)->solidColor = true;
|
|
tree.layout(kScreenW, kScreenH);
|
|
REQUIRE(tree.drawOrder().size() == 1);
|
|
}
|
|
|
|
TEST_CASE("A zero-sized region is skipped rather than drawn degenerate",
|
|
"[widget][draworder]") {
|
|
WidgetTree tree;
|
|
const uint32_t parent = tree.create(WidgetKind::Frame, 0, "P");
|
|
tree.get(parent)->width = 100.0f;
|
|
tree.get(parent)->height = 100.0f;
|
|
tree.addPoint(parent, Anchor{});
|
|
|
|
const uint32_t tex = tree.create(WidgetKind::Texture, parent, "");
|
|
tree.get(tex)->texturePath = "x.blp";
|
|
tree.addPoint(tex, Anchor{}); // anchored, but never given a size
|
|
|
|
tree.layout(kScreenW, kScreenH);
|
|
REQUIRE(tree.drawOrder().empty());
|
|
}
|
|
|
|
TEST_CASE("Two anchors at the same point do not blow the rect apart",
|
|
"[widget][layout]") {
|
|
// Anchoring a frame twice at the same point is redundant rather than a size
|
|
// constraint. Solving it as one would divide by a near-zero spread and throw
|
|
// the rect off the screen.
|
|
WidgetTree tree;
|
|
const uint32_t f = tree.create(WidgetKind::Frame, 0, "F");
|
|
tree.get(f)->width = 80.0f;
|
|
tree.get(f)->height = 40.0f;
|
|
|
|
Anchor a; a.point = "CENTER"; a.relativePoint = "CENTER";
|
|
Anchor b; b.point = "CENTER"; b.relativePoint = "CENTER"; b.x = 5.0f;
|
|
tree.addPoint(f, a);
|
|
tree.addPoint(f, b);
|
|
|
|
tree.layout(kScreenW, kScreenH);
|
|
REQUIRE(tree.get(f)->rectW == Catch::Approx(80.0f));
|
|
REQUIRE(tree.get(f)->rectH == Catch::Approx(40.0f));
|
|
REQUIRE(std::abs(tree.get(f)->left) < kScreenW);
|
|
}
|
|
|
|
// ── Hit testing ─────────────────────────────────────────────────────────────
|
|
|
|
namespace {
|
|
uint32_t makeButton(WidgetTree& tree, float x, float y, float w, float h,
|
|
FrameStrata strata = FrameStrata::Medium) {
|
|
const uint32_t id = tree.create(WidgetKind::Frame, 0, "");
|
|
Widget* f = tree.get(id);
|
|
f->width = w;
|
|
f->height = h;
|
|
f->mouseEnabled = true;
|
|
f->strata = strata;
|
|
f->strataExplicit = true;
|
|
Anchor a; a.point = "BOTTOMLEFT"; a.relativePoint = "BOTTOMLEFT"; a.x = x; a.y = y;
|
|
tree.addPoint(id, a);
|
|
return id;
|
|
}
|
|
}
|
|
|
|
TEST_CASE("A frame is only hit inside its rect", "[widget][hittest]") {
|
|
WidgetTree tree;
|
|
const uint32_t b = makeButton(tree, 100.0f, 100.0f, 50.0f, 40.0f);
|
|
tree.layout(kScreenW, kScreenH);
|
|
|
|
REQUIRE(tree.hitTest(125.0f, 120.0f) == b); // middle
|
|
REQUIRE(tree.hitTest(100.0f, 100.0f) == b); // corner counts
|
|
REQUIRE(tree.hitTest(99.0f, 120.0f) == 0); // just left
|
|
REQUIRE(tree.hitTest(125.0f, 141.0f) == 0); // just above
|
|
}
|
|
|
|
TEST_CASE("A frame without the mouse enabled is transparent to clicks",
|
|
"[widget][hittest]") {
|
|
// WoW's default, and the reason a plain Frame used as a container does not
|
|
// steal clicks from whatever is underneath it.
|
|
WidgetTree tree;
|
|
const uint32_t f = makeButton(tree, 0.0f, 0.0f, 100.0f, 100.0f);
|
|
tree.get(f)->mouseEnabled = false;
|
|
tree.layout(kScreenW, kScreenH);
|
|
REQUIRE(tree.hitTest(50.0f, 50.0f) == 0);
|
|
}
|
|
|
|
TEST_CASE("A hidden frame cannot be clicked", "[widget][hittest]") {
|
|
WidgetTree tree;
|
|
const uint32_t f = makeButton(tree, 0.0f, 0.0f, 100.0f, 100.0f);
|
|
tree.layout(kScreenW, kScreenH);
|
|
REQUIRE(tree.hitTest(50.0f, 50.0f) == f);
|
|
|
|
tree.get(f)->shown = false;
|
|
tree.layout(kScreenW, kScreenH);
|
|
REQUIRE(tree.hitTest(50.0f, 50.0f) == 0);
|
|
}
|
|
|
|
TEST_CASE("The frame drawn on top is the frame that gets the click",
|
|
"[widget][hittest]") {
|
|
// The whole point: what the player can see is what they hit. Overlapping
|
|
// frames must resolve the same way the draw order does, or a click lands on
|
|
// something buried.
|
|
WidgetTree tree;
|
|
const uint32_t low = makeButton(tree, 0.0f, 0.0f, 100.0f, 100.0f, FrameStrata::Low);
|
|
const uint32_t high = makeButton(tree, 0.0f, 0.0f, 100.0f, 100.0f, FrameStrata::High);
|
|
tree.layout(kScreenW, kScreenH);
|
|
REQUIRE(tree.hitTest(50.0f, 50.0f) == high);
|
|
|
|
// With strata equal, the later frame is on top and takes it.
|
|
tree.get(high)->strata = FrameStrata::Low;
|
|
tree.layout(kScreenW, kScreenH);
|
|
REQUIRE(tree.hitTest(50.0f, 50.0f) == high);
|
|
REQUIRE(low != high);
|
|
}
|
|
|
|
TEST_CASE("A child frame takes the click from its parent", "[widget][hittest]") {
|
|
WidgetTree tree;
|
|
const uint32_t parent = makeButton(tree, 0.0f, 0.0f, 200.0f, 200.0f);
|
|
const uint32_t child = tree.create(WidgetKind::Frame, parent, "");
|
|
tree.get(child)->width = 50.0f;
|
|
tree.get(child)->height = 50.0f;
|
|
tree.get(child)->mouseEnabled = true;
|
|
Anchor a; a.point = "BOTTOMLEFT"; a.relativePoint = "BOTTOMLEFT"; a.x = 10.0f; a.y = 10.0f;
|
|
tree.addPoint(child, a);
|
|
|
|
tree.layout(kScreenW, kScreenH);
|
|
REQUIRE(tree.hitTest(30.0f, 30.0f) == child); // over the child
|
|
REQUIRE(tree.hitTest(150.0f, 150.0f) == parent); // parent elsewhere
|
|
}
|
|
|
|
TEST_CASE("A zero-sized frame is never hit", "[widget][hittest]") {
|
|
WidgetTree tree;
|
|
const uint32_t f = tree.create(WidgetKind::Frame, 0, "");
|
|
tree.get(f)->mouseEnabled = true;
|
|
tree.addPoint(f, Anchor{}); // anchored but never sized
|
|
tree.layout(kScreenW, kScreenH);
|
|
REQUIRE(tree.hitTest(kScreenW * 0.5f, kScreenH * 0.5f) == 0);
|
|
}
|
|
|
|
// ── Backdrop and status bar geometry ────────────────────────────────────────
|
|
|
|
TEST_CASE("A frame with a backdrop draws; a bare frame does not",
|
|
"[widget][backdrop]") {
|
|
WidgetTree tree;
|
|
const uint32_t f = tree.create(WidgetKind::Frame, 0, "F");
|
|
tree.get(f)->width = 100.0f;
|
|
tree.get(f)->height = 60.0f;
|
|
tree.addPoint(f, Anchor{});
|
|
|
|
tree.layout(kScreenW, kScreenH);
|
|
REQUIRE(tree.drawOrder().empty()); // a container paints nothing
|
|
|
|
tree.get(f)->hasBackdrop = true;
|
|
tree.get(f)->bgFile = "Interface\\Tooltips\\UI-Tooltip-Background";
|
|
tree.layout(kScreenW, kScreenH);
|
|
REQUIRE(tree.drawOrder().size() == 1);
|
|
REQUIRE(tree.drawOrder()[0]->id == f);
|
|
}
|
|
|
|
TEST_CASE("A frame's backdrop draws beneath its own regions",
|
|
"[widget][backdrop][draworder]") {
|
|
// The backdrop is the panel; anything the frame owns belongs on top of it.
|
|
WidgetTree tree;
|
|
const uint32_t f = tree.create(WidgetKind::Frame, 0, "F");
|
|
tree.get(f)->width = 100.0f;
|
|
tree.get(f)->height = 60.0f;
|
|
tree.get(f)->hasBackdrop = true;
|
|
tree.addPoint(f, Anchor{});
|
|
|
|
const uint32_t art = tree.create(WidgetKind::Texture, f, "");
|
|
tree.get(art)->texturePath = "x.blp";
|
|
tree.setAllPoints(art, f);
|
|
|
|
tree.layout(kScreenW, kScreenH);
|
|
const auto& order = tree.drawOrder();
|
|
REQUIRE(order.size() == 2);
|
|
REQUIRE(order[0]->id == f);
|
|
REQUIRE(order[1]->id == art);
|
|
}
|
|
|
|
TEST_CASE("Status bar fill is clamped and survives a degenerate range",
|
|
"[widget][statusbar]") {
|
|
WidgetTree tree;
|
|
const uint32_t b = tree.create(WidgetKind::Frame, 0, "B");
|
|
Widget* w = tree.get(b);
|
|
w->isStatusBar = true;
|
|
w->barMin = 0.0f;
|
|
w->barMax = 100.0f;
|
|
|
|
w->barValue = 50.0f;
|
|
REQUIRE(w->barFraction() == Catch::Approx(0.5f));
|
|
w->barValue = 0.0f;
|
|
REQUIRE(w->barFraction() == Catch::Approx(0.0f));
|
|
w->barValue = 100.0f;
|
|
REQUIRE(w->barFraction() == Catch::Approx(1.0f));
|
|
|
|
// Out of range clamps rather than overflowing the bar.
|
|
w->barValue = 250.0f;
|
|
REQUIRE(w->barFraction() == Catch::Approx(1.0f));
|
|
w->barValue = -10.0f;
|
|
REQUIRE(w->barFraction() == Catch::Approx(0.0f));
|
|
|
|
// A bar whose range was never set, or set backwards, reads empty instead of
|
|
// dividing by nothing — health frames are created before their values are
|
|
// known and would otherwise flash full or NaN on the first frame.
|
|
w->barMin = 0.0f; w->barMax = 0.0f; w->barValue = 5.0f;
|
|
REQUIRE(w->barFraction() == Catch::Approx(0.0f));
|
|
w->barMin = 100.0f; w->barMax = 0.0f;
|
|
REQUIRE(w->barFraction() == Catch::Approx(0.0f));
|
|
}
|
|
|
|
TEST_CASE("A status bar with no texture and no backdrop is not drawn",
|
|
"[widget][statusbar]") {
|
|
WidgetTree tree;
|
|
const uint32_t b = tree.create(WidgetKind::Frame, 0, "B");
|
|
tree.get(b)->isStatusBar = true;
|
|
tree.get(b)->width = 80.0f;
|
|
tree.get(b)->height = 10.0f;
|
|
tree.addPoint(b, Anchor{});
|
|
tree.layout(kScreenW, kScreenH);
|
|
REQUIRE(tree.drawOrder().empty());
|
|
|
|
tree.get(b)->barTexture = "bar.blp";
|
|
tree.layout(kScreenW, kScreenH);
|
|
REQUIRE(tree.drawOrder().size() == 1);
|
|
}
|