446 lines
15 KiB
C++
446 lines
15 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 = move(mixer);
|
|
m_applicationController = move(appController);
|
|
|
|
m_interfaceScale = 1;
|
|
|
|
m_shiftHeld = false;
|
|
|
|
refreshKeybindings();
|
|
}
|
|
|
|
GuiContext::~GuiContext() {
|
|
s_singleton = nullptr;
|
|
}
|
|
|
|
void GuiContext::renderInit(RendererPtr renderer) {
|
|
m_renderer = 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) const {
|
|
auto getInterfacePosition = [this](Vec2I pos) {
|
|
return Vec2I(pos) / interfaceScale();
|
|
};
|
|
|
|
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 {};
|
|
}
|
|
|
|
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, int 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, int 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(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) {
|
|
auto assets = Root::singleton().assets();
|
|
auto config = Root::singleton().configuration();
|
|
auto audioInstance = make_shared<AudioInstance>(*assets->audio(audioAsset));
|
|
audioInstance->setVolume(volume);
|
|
audioInstance->setLoops(loops);
|
|
m_mixer->play(move(audioInstance));
|
|
}
|
|
|
|
String GuiContext::getClipboard() const {
|
|
return m_applicationController->getClipboard().value();
|
|
}
|
|
|
|
void GuiContext::setClipboard(String text) {
|
|
m_applicationController->setClipboard(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);
|
|
}
|
|
|
|
}
|