431a9c00a5
On Linux and macOS, using Clang to compile OpenStarbound produces about 400 MB worth of warnings during the build, making the compiler output unreadable and slowing the build down considerably. 99% of the warnings were unqualified uses of std::move and std::forward, which are now all properly qualified. Fixed a few other minor warnings about non-virtual destructors and some uses of std::move preventing copy elision on temporary objects. Most remaining warnings are now unused parameters.
451 lines
16 KiB
C++
451 lines
16 KiB
C++
#include "StarGuiContext.hpp"
|
|
#include "StarRoot.hpp"
|
|
#include "StarConfiguration.hpp"
|
|
#include "StarMixer.hpp"
|
|
#include "StarAssets.hpp"
|
|
#include "StarImageMetadataDatabase.hpp"
|
|
|
|
namespace Star {
|
|
|
|
GuiContext* GuiContext::s_singleton;
|
|
|
|
GuiContext* GuiContext::singletonPtr() {
|
|
return s_singleton;
|
|
}
|
|
|
|
GuiContext& GuiContext::singleton() {
|
|
if (!s_singleton)
|
|
throw GuiContextException("GuiContext::singleton() called with no GuiContext instance available");
|
|
else
|
|
return *s_singleton;
|
|
}
|
|
|
|
GuiContext::GuiContext(MixerPtr mixer, ApplicationControllerPtr appController) {
|
|
if (s_singleton)
|
|
throw GuiContextException("Singleton GuiContext has been constructed twice");
|
|
|
|
s_singleton = this;
|
|
|
|
m_mixer = std::move(mixer);
|
|
m_applicationController = std::move(appController);
|
|
|
|
m_interfaceScale = 1;
|
|
|
|
m_shiftHeld = false;
|
|
|
|
refreshKeybindings();
|
|
}
|
|
|
|
GuiContext::~GuiContext() {
|
|
s_singleton = nullptr;
|
|
}
|
|
|
|
void GuiContext::renderInit(RendererPtr renderer) {
|
|
m_renderer = std::move(renderer);
|
|
auto textureGroup = m_renderer->createTextureGroup();
|
|
m_textureCollection = make_shared<AssetTextureGroup>(textureGroup);
|
|
m_drawablePainter = make_shared<DrawablePainter>(m_renderer, m_textureCollection);
|
|
m_textPainter = make_shared<TextPainter>(m_renderer, textureGroup);
|
|
}
|
|
|
|
MixerPtr const& GuiContext::mixer() const {
|
|
return m_mixer;
|
|
}
|
|
|
|
ApplicationControllerPtr const& GuiContext::applicationController() const {
|
|
return m_applicationController;
|
|
}
|
|
|
|
RendererPtr const& GuiContext::renderer() const {
|
|
if (!m_renderer)
|
|
throw GuiContextException("GuiContext::renderer() called before renderInit");
|
|
return m_renderer;
|
|
}
|
|
|
|
AssetTextureGroupPtr const& GuiContext::assetTextureGroup() const {
|
|
if (!m_textureCollection)
|
|
throw GuiContextException("GuiContext::assetTextureGroup() called before renderInit");
|
|
return m_textureCollection;
|
|
}
|
|
|
|
TextPainterPtr const& GuiContext::textPainter() const {
|
|
if (!m_textPainter)
|
|
throw GuiContextException("GuiContext::textPainter() called before renderInit");
|
|
return m_textPainter;
|
|
}
|
|
|
|
unsigned GuiContext::windowWidth() const {
|
|
return renderer()->screenSize()[0];
|
|
}
|
|
|
|
unsigned GuiContext::windowHeight() const {
|
|
return renderer()->screenSize()[1];
|
|
}
|
|
|
|
Vec2U GuiContext::windowSize() const {
|
|
return renderer()->screenSize();
|
|
}
|
|
|
|
Vec2U GuiContext::windowInterfaceSize() const {
|
|
return Vec2U::ceil(Vec2F(windowSize()) / interfaceScale());
|
|
}
|
|
|
|
int GuiContext::interfaceScale() const {
|
|
return m_interfaceScale;
|
|
}
|
|
|
|
void GuiContext::setInterfaceScale(int interfaceScale) {
|
|
m_interfaceScale = interfaceScale;
|
|
}
|
|
|
|
Maybe<Vec2I> GuiContext::mousePosition(InputEvent const& event, int pixelRatio) const {
|
|
auto getInterfacePosition = [pixelRatio](Vec2I pos) {
|
|
return Vec2I(pos) / pixelRatio;
|
|
};
|
|
|
|
if (auto mouseMoveEvent = event.ptr<MouseMoveEvent>())
|
|
return getInterfacePosition(mouseMoveEvent->mousePosition);
|
|
else if (auto mouseDownEvent = event.ptr<MouseButtonDownEvent>())
|
|
return getInterfacePosition(mouseDownEvent->mousePosition);
|
|
else if (auto mouseUpEvent = event.ptr<MouseButtonUpEvent>())
|
|
return getInterfacePosition(mouseUpEvent->mousePosition);
|
|
else if (auto mouseWheelEvent = event.ptr<MouseWheelEvent>())
|
|
return getInterfacePosition(mouseWheelEvent->mousePosition);
|
|
else
|
|
return {};
|
|
}
|
|
|
|
Maybe<Vec2I> GuiContext::mousePosition(InputEvent const& event) const {
|
|
return mousePosition(event, interfaceScale());
|
|
}
|
|
|
|
Set<InterfaceAction> GuiContext::actions(InputEvent const& event) const {
|
|
return m_keyBindings.actions(event);
|
|
}
|
|
|
|
Set<InterfaceAction> GuiContext::actionsForKey(Key key) const {
|
|
return m_keyBindings.actionsForKey(key);
|
|
}
|
|
|
|
void GuiContext::refreshKeybindings() {
|
|
m_keyBindings = KeyBindings(Root::singleton().configuration()->get("bindings"));
|
|
}
|
|
|
|
void GuiContext::setInterfaceScissorRect(RectI const& scissor) {
|
|
renderer()->setScissorRect(RectI(scissor).scaled(interfaceScale()));
|
|
}
|
|
|
|
void GuiContext::resetInterfaceScissorRect() {
|
|
renderer()->setScissorRect({});
|
|
}
|
|
|
|
Vec2U GuiContext::textureSize(AssetPath const& texName) {
|
|
return assetTextureGroup()->loadTexture(texName)->size();
|
|
}
|
|
|
|
void GuiContext::drawQuad(RectF const& screenCoords, Vec4B const& color) {
|
|
renderer()->immediatePrimitives().emplace_back(std::in_place_type_t<RenderQuad>(), screenCoords, color, 0.0f);
|
|
}
|
|
|
|
void GuiContext::drawQuad(AssetPath const& texName, RectF const& screenCoords, Vec4B const& color) {
|
|
renderer()->immediatePrimitives().emplace_back(std::in_place_type_t<RenderQuad>(), assetTextureGroup()->loadTexture(texName), screenCoords, color, 0.0f);
|
|
}
|
|
|
|
void GuiContext::drawQuad(AssetPath const& texName, Vec2F const& screenPos, float pixelRatio, Vec4B const& color) {
|
|
renderer()->immediatePrimitives().emplace_back(std::in_place_type_t<RenderQuad>(), assetTextureGroup()->loadTexture(texName), screenPos, pixelRatio, color, 0.0f);
|
|
}
|
|
|
|
void GuiContext::drawQuad(AssetPath const& texName, RectF const& texCoords, RectF const& screenCoords, Vec4B const& color) {
|
|
renderer()->immediatePrimitives().emplace_back(std::in_place_type_t<RenderQuad>(), assetTextureGroup()->loadTexture(texName),
|
|
screenCoords.min(), texCoords.min(),
|
|
Vec2F(screenCoords.xMax(), screenCoords.yMin()), Vec2F(texCoords.xMax(), texCoords.yMin()),
|
|
screenCoords.max(), texCoords.max(),
|
|
Vec2F(screenCoords.xMin(), screenCoords.yMax()), Vec2F(texCoords.xMin(), texCoords.yMax()),
|
|
color, 0.0f);
|
|
}
|
|
|
|
void GuiContext::drawDrawable(Drawable drawable, Vec2F const& screenPos, float pixelRatio, Vec4B const& color) {
|
|
if (drawable.isLine())
|
|
drawable.linePart().width *= pixelRatio;
|
|
|
|
drawable.scale(pixelRatio);
|
|
drawable.translate(screenPos);
|
|
drawable.color *= Color::rgba(color);
|
|
m_drawablePainter->drawDrawable(drawable);
|
|
}
|
|
|
|
void GuiContext::drawLine(Vec2F const& begin, Vec2F const end, Vec4B const& color, float lineWidth) {
|
|
Vec2F left = vnorm(Vec2F(end) - Vec2F(begin)).rot90() * lineWidth / 2.0f;
|
|
renderer()->immediatePrimitives().emplace_back(std::in_place_type_t<RenderQuad>(),
|
|
begin + left,
|
|
begin - left,
|
|
end - left,
|
|
end + left,
|
|
color, 0.0f);
|
|
}
|
|
|
|
void GuiContext::drawPolyLines(PolyF const& poly, Vec4B const& color, float lineWidth) {
|
|
for (size_t i = 0; i < poly.sides(); ++i)
|
|
drawLine(poly.vertex(i), poly.vertex(i + 1), color, lineWidth);
|
|
}
|
|
|
|
void GuiContext::drawTriangles(List<tuple<Vec2F, Vec2F, Vec2F>> const& triangles, Vec4B const& color) {
|
|
for (auto& poly : triangles) {
|
|
renderer()->immediatePrimitives().emplace_back(std::in_place_type_t<RenderTriangle>(),
|
|
get<0>(poly), get<1>(poly), get<2>(poly), color, 0.0f);
|
|
}
|
|
}
|
|
|
|
void GuiContext::drawInterfaceDrawable(Drawable drawable, Vec2F const& screenPos, Vec4B const& color) {
|
|
drawDrawable(std::move(drawable), screenPos * interfaceScale(), (float)interfaceScale(), color);
|
|
}
|
|
|
|
void GuiContext::drawInterfaceLine(Vec2F const& begin, Vec2F const end, Vec4B const& color, float lineWidth) {
|
|
drawLine(begin * interfaceScale(), end * interfaceScale(), color, lineWidth * interfaceScale());
|
|
}
|
|
|
|
void GuiContext::drawInterfacePolyLines(PolyF poly, Vec4B const& color, float lineWidth) {
|
|
poly.scale(interfaceScale());
|
|
drawPolyLines(poly, color, lineWidth * interfaceScale());
|
|
}
|
|
|
|
void GuiContext::drawInterfaceQuad(RectF const& screenCoords, Vec4B const& color) {
|
|
drawQuad(screenCoords.scaled(interfaceScale()), color);
|
|
}
|
|
|
|
void GuiContext::drawInterfaceQuad(AssetPath const& texName, Vec2F const& screenCoords, Vec4B const& color) {
|
|
drawQuad(texName, screenCoords * interfaceScale(), interfaceScale(), color);
|
|
}
|
|
|
|
void GuiContext::drawInterfaceQuad(AssetPath const& texName, Vec2F const& screenCoords, float scale, Vec4B const& color) {
|
|
drawQuad(texName, screenCoords * interfaceScale(), interfaceScale() * scale, color);
|
|
}
|
|
|
|
void GuiContext::drawInterfaceQuad(AssetPath const& texName, RectF const& texCoords, RectF const& screenCoords, Vec4B const& color) {
|
|
drawQuad(texName, texCoords, screenCoords.scaled(interfaceScale()), color);
|
|
}
|
|
|
|
void GuiContext::drawInterfaceTriangles(List<tuple<Vec2F, Vec2F, Vec2F>> const& triangles, Vec4B const& color) {
|
|
drawTriangles(triangles.transformed([this](tuple<Vec2F, Vec2F, Vec2F> const& poly) {
|
|
return tuple<Vec2F, Vec2F, Vec2F>(get<0>(poly) * interfaceScale(), get<1>(poly) * interfaceScale(), get<2>(poly) * interfaceScale());
|
|
}), color);
|
|
}
|
|
|
|
void GuiContext::drawImageStretchSet(ImageStretchSet const& imageSet, RectF const& screenPos, GuiDirection direction, Vec4B const& color) {
|
|
int innerSize;
|
|
int innerOffset = 0;
|
|
RectF begin, end, inner;
|
|
if (direction == GuiDirection::Horizontal) {
|
|
innerSize = screenPos.width();
|
|
if (imageSet.begin.size())
|
|
innerOffset = textureSize(imageSet.begin)[0];
|
|
|
|
if (imageSet.end.size())
|
|
innerSize = std::max(0, innerSize - innerOffset - Vec2I(textureSize(imageSet.end))[0]);
|
|
else
|
|
innerSize = std::max(0, innerSize - innerOffset);
|
|
|
|
if (imageSet.begin.size())
|
|
begin = RectF::withSize(screenPos.min(), Vec2F(textureSize(imageSet.begin)[0], screenPos.height()));
|
|
else
|
|
begin = RectF::withSize(screenPos.min(), Vec2F(0, screenPos.height()));
|
|
|
|
inner = RectF::withSize(screenPos.min() + Vec2F(innerOffset, 0), Vec2F(innerSize, screenPos.height()));
|
|
if (imageSet.end.size())
|
|
end = RectF::withSize(screenPos.min() + Vec2F(innerOffset + innerSize, 0), Vec2F(textureSize(imageSet.end)[0], screenPos.height()));
|
|
else
|
|
end = RectF::withSize(screenPos.min(), Vec2F(0, screenPos.height()));
|
|
|
|
} else {
|
|
innerSize = screenPos.height();
|
|
if (imageSet.begin.size())
|
|
innerOffset = textureSize(imageSet.begin)[1];
|
|
|
|
if (imageSet.end.size())
|
|
innerSize = std::max(0, innerSize - innerOffset - Vec2I(textureSize(imageSet.end))[1]);
|
|
else
|
|
innerSize = std::max(0, innerSize - innerOffset);
|
|
|
|
if (imageSet.begin.size())
|
|
begin = RectF::withSize(screenPos.min(), Vec2F(screenPos.width(), textureSize(imageSet.begin)[1]));
|
|
else
|
|
begin = RectF::withSize(screenPos.min(), Vec2F(screenPos.width(), 0));
|
|
|
|
inner = RectF::withSize(screenPos.min() + Vec2F(0, innerOffset), Vec2F(screenPos.width(), innerSize));
|
|
if (imageSet.end.size()) {
|
|
end = RectF::withSize(screenPos.min() + Vec2F(0, innerOffset + innerSize), Vec2F(screenPos.width(), textureSize(imageSet.end)[1]));
|
|
} else {
|
|
end = RectF::withSize(screenPos.min(), Vec2F(screenPos.width(), 0));
|
|
}
|
|
}
|
|
|
|
if (imageSet.begin.size())
|
|
drawInterfaceQuad(imageSet.begin, RectF(Vec2F(), Vec2F(textureSize(imageSet.begin))), begin, color);
|
|
|
|
if (imageSet.type == ImageStretchSet::ImageStretchType::Stretch) {
|
|
drawInterfaceQuad(imageSet.inner, RectF(Vec2F(), Vec2F(textureSize(imageSet.inner))), inner, color);
|
|
|
|
} else {
|
|
int position = 0;
|
|
auto texSize = Vec2F(textureSize(imageSet.inner));
|
|
if (direction == GuiDirection::Horizontal) {
|
|
starAssert(texSize[0] > 0);
|
|
while (position < inner.width()) {
|
|
RectF partialImage = RectF::withSize(Vec2F(), Vec2F(std::min(inner.width() - position, texSize[0]), texSize[1]));
|
|
drawInterfaceQuad(imageSet.inner, partialImage, RectF::withSize(inner.min() + Vec2F(position, 0), partialImage.size()), color);
|
|
position += partialImage.size()[0];
|
|
}
|
|
} else {
|
|
starAssert(texSize[1] > 0);
|
|
while (position < inner.height()) {
|
|
RectF partialImage = RectF::withSize(
|
|
Vec2F(0, max(0.0f, texSize[1] - (inner.height() - position))),
|
|
Vec2F(texSize[0], std::min(inner.height() - position, texSize[1])));
|
|
drawInterfaceQuad(imageSet.inner, partialImage, RectF::withSize(inner.min() + Vec2F(0, position), partialImage.size()), color);
|
|
position += partialImage.size()[1];
|
|
}
|
|
}
|
|
}
|
|
|
|
if (imageSet.end.size())
|
|
drawInterfaceQuad(imageSet.end, RectF(Vec2F(), Vec2F(textureSize(imageSet.end))), end, color);
|
|
}
|
|
|
|
bool GuiContext::trySetCursor(Drawable const& drawable, Vec2I const& offset, int pixelRatio) {
|
|
if (!drawable.isImage())
|
|
return false;
|
|
|
|
auto assets = Root::singleton().assets();
|
|
auto& imagePath = drawable.imagePart().image;
|
|
return applicationController()->setCursorImage(AssetPath::join(imagePath), assets->image(imagePath), pixelRatio, offset);
|
|
}
|
|
|
|
RectF GuiContext::renderText(String const& s, TextPositioning const& position) {
|
|
return textPainter()->renderText(s, position);
|
|
}
|
|
|
|
RectF GuiContext::renderInterfaceText(String const& s, TextPositioning const& position) {
|
|
auto res = renderText(s, {
|
|
position.pos * interfaceScale(),
|
|
position.hAnchor,
|
|
position.vAnchor,
|
|
position.wrapWidth.apply(bind(std::multiplies<int>(), _1, interfaceScale())),
|
|
position.charLimit
|
|
});
|
|
return RectF(res).scaled(1.0f / interfaceScale());
|
|
}
|
|
|
|
RectF GuiContext::determineTextSize(String const& s, TextPositioning const& positioning) {
|
|
return textPainter()->determineTextSize(s, positioning);
|
|
}
|
|
|
|
RectF GuiContext::determineInterfaceTextSize(String const& s, TextPositioning const& positioning) {
|
|
auto res = determineTextSize(s, {
|
|
positioning.pos * interfaceScale(),
|
|
positioning.hAnchor,
|
|
positioning.vAnchor,
|
|
positioning.wrapWidth.apply(bind(std::multiplies<int>(), _1, interfaceScale()))
|
|
});
|
|
return RectF(res).scaled(1.0f / interfaceScale());
|
|
}
|
|
|
|
void GuiContext::setFontSize(unsigned size) {
|
|
setFontSize(size, interfaceScale());
|
|
}
|
|
|
|
void GuiContext::setFontSize(unsigned size, int pixelRatio) {
|
|
textPainter()->setFontSize(size * pixelRatio);
|
|
}
|
|
|
|
void GuiContext::setFontColor(Vec4B const& color) {
|
|
textPainter()->setFontColor(color);
|
|
}
|
|
|
|
void GuiContext::setFontMode(FontMode mode) {
|
|
textPainter()->setMode(mode);
|
|
}
|
|
|
|
void GuiContext::setFontProcessingDirectives(String const& directives) {
|
|
textPainter()->setProcessingDirectives(directives);
|
|
}
|
|
|
|
void GuiContext::setFont(String const& font) {
|
|
textPainter()->setFont(font);
|
|
}
|
|
|
|
void GuiContext::setDefaultFont() {
|
|
textPainter()->setFont("");
|
|
}
|
|
|
|
void GuiContext::setLineSpacing(float lineSpacing) {
|
|
textPainter()->setLineSpacing(lineSpacing);
|
|
}
|
|
|
|
void GuiContext::setDefaultLineSpacing() {
|
|
textPainter()->setLineSpacing(DefaultLineSpacing);
|
|
}
|
|
|
|
int GuiContext::stringWidth(String const& s) {
|
|
return textPainter()->stringWidth(s);
|
|
}
|
|
|
|
//TODO: Make this use StringView
|
|
int GuiContext::stringInterfaceWidth(String const& s) {
|
|
if (interfaceScale()) {
|
|
// font size is already adjusted UP by interfaceScale, so we have to adjust
|
|
// it back down
|
|
return stringWidth(s) / interfaceScale();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
StringList GuiContext::wrapText(String const& s, Maybe<unsigned> wrapWidth) {
|
|
return textPainter()->wrapText(s, wrapWidth);
|
|
}
|
|
|
|
StringList GuiContext::wrapInterfaceText(String const& s, Maybe<unsigned> wrapWidth) {
|
|
if (wrapWidth)
|
|
*wrapWidth *= interfaceScale();
|
|
return wrapText(s, wrapWidth);
|
|
}
|
|
|
|
bool GuiContext::shiftHeld() const {
|
|
return m_shiftHeld;
|
|
}
|
|
|
|
void GuiContext::setShiftHeld(bool held) {
|
|
m_shiftHeld = held;
|
|
}
|
|
|
|
void GuiContext::playAudio(AudioInstancePtr audioInstance) {
|
|
m_mixer->play(audioInstance);
|
|
}
|
|
|
|
void GuiContext::playAudio(String const& audioAsset, int loops, float volume, float pitch) {
|
|
auto assets = Root::singleton().assets();
|
|
auto config = Root::singleton().configuration();
|
|
auto audioInstance = make_shared<AudioInstance>(*assets->audio(audioAsset));
|
|
audioInstance->setVolume(volume);
|
|
audioInstance->setPitchMultiplier(pitch);
|
|
audioInstance->setLoops(loops);
|
|
m_mixer->play(std::move(audioInstance));
|
|
}
|
|
|
|
String GuiContext::getClipboard() const {
|
|
return m_applicationController->getClipboard().value();
|
|
}
|
|
|
|
void GuiContext::setClipboard(String text) {
|
|
m_applicationController->setClipboard(std::move(text));
|
|
}
|
|
|
|
void GuiContext::cleanup() {
|
|
int64_t textureTimeout = Root::singleton().assets()->json("/rendering.config:textureTimeout").toInt();
|
|
if (m_textureCollection)
|
|
m_textureCollection->cleanup(textureTimeout);
|
|
if (m_textPainter)
|
|
m_textPainter->cleanup(textureTimeout);
|
|
}
|
|
|
|
}
|