4b0bc220e4
Context-specific (like per-world) timescales can also be added later
475 lines
8.9 KiB
C++
475 lines
8.9 KiB
C++
#include "StarWidget.hpp"
|
|
#include "StarPane.hpp"
|
|
|
|
#include "StarLabelWidget.hpp"
|
|
#include "StarFlowLayout.hpp"
|
|
|
|
namespace Star {
|
|
|
|
Widget::Widget() {
|
|
m_parent = nullptr;
|
|
m_context = GuiContext::singletonPtr();
|
|
m_visible = true;
|
|
m_focus = false;
|
|
m_doScissor = true;
|
|
m_container = false;
|
|
m_mouseTransparent = false;
|
|
}
|
|
|
|
Widget::~Widget() {
|
|
removeAllChildren();
|
|
}
|
|
|
|
void Widget::update(float dt) {
|
|
for (auto& widget : m_members)
|
|
widget->update(dt);
|
|
}
|
|
|
|
GuiContext* Widget::context() const {
|
|
return m_context;
|
|
}
|
|
|
|
void Widget::render(RectI const& region) {
|
|
if (!m_visible)
|
|
return;
|
|
|
|
if (!setupDrawRegion(region))
|
|
return;
|
|
|
|
renderImpl();
|
|
drawChildren();
|
|
}
|
|
|
|
void Widget::renderImpl() {}
|
|
|
|
void Widget::drawChildren() {
|
|
for (auto child : m_members)
|
|
child->render(m_drawingArea);
|
|
}
|
|
|
|
Vec2I Widget::position() const {
|
|
return m_position + m_drawingOffset;
|
|
}
|
|
|
|
Vec2I Widget::relativePosition() const {
|
|
return m_position;
|
|
}
|
|
|
|
bool Widget::setupDrawRegion(RectI const& region) {
|
|
RectI scissorRect;
|
|
if (m_doScissor) {
|
|
scissorRect = getScissorRect();
|
|
} else {
|
|
scissorRect = noScissor();
|
|
}
|
|
m_drawingArea = scissorRect.limited(region);
|
|
if (m_drawingArea.isEmpty())
|
|
return false;
|
|
|
|
m_context->setInterfaceScissorRect(m_drawingArea);
|
|
return true;
|
|
}
|
|
|
|
Vec2I Widget::drawingOffset() const {
|
|
return m_drawingOffset;
|
|
}
|
|
|
|
void Widget::setDrawingOffset(Vec2I const& offset) {
|
|
m_drawingOffset = offset;
|
|
}
|
|
|
|
RectI Widget::getScissorRect() const {
|
|
return screenBoundRect();
|
|
}
|
|
|
|
RectI Widget::noScissor() const {
|
|
return RectI::inf();
|
|
}
|
|
|
|
void Widget::disableScissoring() {
|
|
m_doScissor = false;
|
|
}
|
|
|
|
void Widget::enableScissoring() {
|
|
m_doScissor = true;
|
|
}
|
|
|
|
void Widget::setPosition(Vec2I const& position) {
|
|
m_position = position;
|
|
}
|
|
|
|
Vec2I Widget::size() const {
|
|
return m_size;
|
|
}
|
|
|
|
void Widget::setSize(Vec2I const& size) {
|
|
m_size = size;
|
|
}
|
|
|
|
RectI Widget::relativeBoundRect() const {
|
|
return RectI::withSize(relativePosition(), size());
|
|
}
|
|
|
|
RectI Widget::screenBoundRect() const {
|
|
return relativeBoundRect().translated(screenPosition() - relativePosition());
|
|
}
|
|
|
|
void Widget::determineSizeFromChildren() {
|
|
Vec2I max;
|
|
|
|
for (auto& child : m_members) {
|
|
Vec2I childMax = child->position() + child->size();
|
|
max = max.piecewiseMax(childMax);
|
|
}
|
|
|
|
setSize(max);
|
|
}
|
|
|
|
void Widget::markAsContainer() {
|
|
m_container = true;
|
|
}
|
|
|
|
KeyboardCaptureMode Widget::keyboardCaptured() const {
|
|
if (active()) {
|
|
for (auto const& member : m_members) {
|
|
auto mode = member->keyboardCaptured();
|
|
if (mode != KeyboardCaptureMode::None)
|
|
return mode;
|
|
}
|
|
}
|
|
return KeyboardCaptureMode::None;
|
|
}
|
|
|
|
void Widget::setData(Json const& data) {
|
|
m_data = data;
|
|
}
|
|
|
|
Json const& Widget::data() {
|
|
return m_data;
|
|
}
|
|
|
|
Vec2I Widget::screenPosition() const {
|
|
if (m_parent) {
|
|
return m_parent->screenPosition() + position();
|
|
} else {
|
|
return position();
|
|
}
|
|
}
|
|
|
|
bool Widget::inMember(Vec2I const& position) const {
|
|
if (!m_visible)
|
|
return false;
|
|
|
|
if (m_mouseTransparent)
|
|
return false;
|
|
|
|
if (!m_drawingArea.isNull() && !m_drawingArea.contains(Vec2I::floor(position)))
|
|
return false;
|
|
|
|
if (m_container) {
|
|
for (auto child : m_members)
|
|
if (child->inMember(position))
|
|
return true;
|
|
} else {
|
|
return screenBoundRect().contains(position);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Widget::sendEvent(InputEvent const& event) {
|
|
if (!m_visible)
|
|
return false;
|
|
|
|
for (auto child : reverseIterate(m_members)) {
|
|
if (child->sendEvent(event))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void Widget::mouseOver() {}
|
|
|
|
void Widget::mouseOut() {}
|
|
|
|
void Widget::mouseReturnStillDown() {}
|
|
|
|
void Widget::setMouseTransparent(bool transparent) {
|
|
m_mouseTransparent = transparent;
|
|
}
|
|
|
|
bool Widget::mouseTransparent() {
|
|
return m_mouseTransparent;
|
|
}
|
|
|
|
Widget* Widget::parent() const {
|
|
return m_parent;
|
|
}
|
|
|
|
void Widget::setParent(Widget* parent) {
|
|
m_parent = parent;
|
|
}
|
|
|
|
void Widget::show() {
|
|
m_visible = true;
|
|
}
|
|
|
|
void Widget::hide() {
|
|
m_visible = false;
|
|
}
|
|
|
|
void Widget::toggleVisibility() {
|
|
m_visible = !m_visible;
|
|
}
|
|
|
|
void Widget::setVisibility(bool visibility) {
|
|
if (visibility)
|
|
show();
|
|
else
|
|
hide();
|
|
}
|
|
|
|
bool Widget::active() const {
|
|
return m_visible;
|
|
}
|
|
|
|
bool Widget::interactive() const {
|
|
return true;
|
|
}
|
|
|
|
bool Widget::hasFocus() const {
|
|
return m_focus;
|
|
}
|
|
|
|
void Widget::focus() {
|
|
m_focus = true;
|
|
if (auto w = window())
|
|
w->setFocus(this);
|
|
}
|
|
|
|
void Widget::blur() {
|
|
m_focus = false;
|
|
if (window())
|
|
window()->removeFocus(this);
|
|
}
|
|
|
|
unsigned Widget::windowHeight() const {
|
|
return context()->windowHeight();
|
|
}
|
|
|
|
unsigned Widget::windowWidth() const {
|
|
return context()->windowWidth();
|
|
}
|
|
|
|
Vec2I Widget::windowSize() const {
|
|
return Vec2I(context()->windowSize());
|
|
}
|
|
|
|
Pane* Widget::window() {
|
|
if (m_parent)
|
|
return m_parent->window();
|
|
return nullptr;
|
|
}
|
|
|
|
Pane const* Widget::window() const {
|
|
if (m_parent)
|
|
return m_parent->window();
|
|
return nullptr;
|
|
}
|
|
|
|
void Widget::addChild(String const& name, WidgetPtr member) {
|
|
member->setName(name);
|
|
m_members.push_back(member);
|
|
m_memberHash[member->name()] = member;
|
|
member->setParent(this);
|
|
}
|
|
|
|
void Widget::addChildAt(String const& name, WidgetPtr member, size_t at) {
|
|
if (at > m_members.size())
|
|
throw GuiException("Attempted to insert item after the end of the list.");
|
|
|
|
m_members.insert(m_members.begin() + at, member);
|
|
m_memberHash[name] = member;
|
|
member->setName(name);
|
|
member->setParent(this);
|
|
}
|
|
|
|
bool Widget::removeChild(Widget* member) {
|
|
m_memberHash.erase(member->name());
|
|
for (auto child = m_members.begin(); child != m_members.end(); ++child) {
|
|
if (child->get() == member) {
|
|
(*child)->setParent(nullptr);
|
|
m_members.erase(child);
|
|
return true;
|
|
} else {
|
|
if ((*child)->removeChild(member))
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Widget::removeChild(String const& name) {
|
|
m_memberHash.erase(name);
|
|
for (auto child = m_members.begin(); child != m_members.end(); ++child) {
|
|
auto ptr = *child;
|
|
if (ptr->name() == name) {
|
|
m_members.erase(child);
|
|
ptr->setParent(nullptr);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Widget::removeChildAt(size_t at) {
|
|
if (at >= m_members.size())
|
|
return false;
|
|
|
|
m_memberHash.erase(m_members.at(at)->name());
|
|
|
|
m_members.at(at)->setParent(nullptr);
|
|
m_members.erase(m_members.begin() + at);
|
|
return true;
|
|
}
|
|
|
|
void Widget::removeAllChildren() {
|
|
for (auto child : m_members)
|
|
child->setParent(nullptr);
|
|
|
|
m_members.clear();
|
|
m_memberHash.clear();
|
|
}
|
|
|
|
bool Widget::containsChild(String const& name) {
|
|
if (fetchChild(name))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
WidgetPtr Widget::fetchChild(String const& name) {
|
|
WidgetPtr res;
|
|
if (name.contains(".")) {
|
|
StringList nameList = name.split(".", 1);
|
|
if (auto child = m_memberHash.value(nameList[0], {}))
|
|
return child->fetchChild(nameList[1]);
|
|
} else {
|
|
if (auto child = m_memberHash.value(name, {}))
|
|
return child;
|
|
}
|
|
return {};
|
|
}
|
|
|
|
WidgetPtr Widget::findChild(String const& name) {
|
|
if (auto found = fetchChild(name))
|
|
return found;
|
|
for (auto const& child : m_members) {
|
|
if (auto found = child->findChild(name))
|
|
return found;
|
|
}
|
|
return {};
|
|
}
|
|
|
|
WidgetPtr Widget::childPtr(Widget const* child) const {
|
|
for (auto const& m : m_members) {
|
|
if (m.get() == child)
|
|
return m;
|
|
if (auto c = m->childPtr(child))
|
|
return c;
|
|
}
|
|
return {};
|
|
}
|
|
|
|
WidgetPtr Widget::getChildAt(Vec2I const& pos) {
|
|
for (auto child : reverseIterate(m_members)) {
|
|
if (child->inMember(pos)) {
|
|
auto res = child->getChildAt(pos);
|
|
if (res) {
|
|
return res;
|
|
}
|
|
return child;
|
|
}
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
size_t Widget::numChildren() const {
|
|
return m_members.size();
|
|
}
|
|
|
|
WidgetPtr Widget::getChildNum(size_t num) const {
|
|
return m_members.at(num);
|
|
}
|
|
|
|
String const& Widget::name() const {
|
|
return m_name;
|
|
}
|
|
|
|
void Widget::setName(String const& name) {
|
|
m_name = name;
|
|
}
|
|
|
|
String Widget::fullName() const {
|
|
if (m_parent) {
|
|
return m_parent->fullName() + "." + name();
|
|
}
|
|
|
|
return name();
|
|
}
|
|
|
|
String Widget::toStringImpl(int indentLevel) const {
|
|
auto leader = String(" ") * indentLevel;
|
|
String childrenString;
|
|
for (auto child : m_members) {
|
|
childrenString.append(child->toStringImpl(indentLevel + 4));
|
|
}
|
|
String output = strf(R"OUTPUT({}{} : {
|
|
{} address : %p,
|
|
{} visible : {},
|
|
{} position : {},
|
|
{} size : {},
|
|
{} children : {
|
|
{}
|
|
{} }
|
|
{}}
|
|
)OUTPUT",
|
|
leader,
|
|
m_name,
|
|
leader,
|
|
(void*)this,
|
|
leader,
|
|
m_visible ? "true" : "false",
|
|
leader,
|
|
m_position,
|
|
leader,
|
|
m_size,
|
|
leader,
|
|
childrenString,
|
|
leader,
|
|
leader);
|
|
|
|
return output;
|
|
}
|
|
|
|
bool Widget::setLabel(String const& name, String const& value) {
|
|
if (containsChild(name)) {
|
|
auto child = fetchChild(name);
|
|
if (auto label = as<LabelWidget>(child)) {
|
|
label->setText(value);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
std::ostream& operator<<(std::ostream& os, Widget const& widget) {
|
|
os << widget.toStringImpl(0);
|
|
return os;
|
|
}
|
|
|
|
}
|