void Application::drawUI() { if (context->dirtyUI) { context->setCursor(nullptr); if (uiFrameBuffer->width() != context->screenSize.x || uiFrameBuffer->height() != context->screenSize.y) { uiFrameBuffer->initialize(context->screenSize.x, context->screenSize.y); } uiFrameBuffer->begin(); glViewport(0, 0, uiFrameBuffer->width(),uiFrameBuffer->height()); NVGcontext* nvg = context->nvgContext; nvgBeginFrame(nvg, uiFrameBuffer->width(), uiFrameBuffer->height(),1.0f);//(float) context->pixelRatio nvgScissor(nvg, 0, 0, (float)uiFrameBuffer->width(), (float)uiFrameBuffer->height()); rootRegion.draw(context.get()); nvgScissor(nvg, 0, 0, (float)uiFrameBuffer->width(), (float)uiFrameBuffer->height()); Region* onTop = context->getOnTopRegion(); if (onTop != nullptr) { if (onTop->isVisible()) onTop->draw(context.get()); } const Cursor* cursor = context->getCursor(); if (!cursor) { cursor = &Cursor::Normal; } nvgEndFrame(nvg); uiFrameBuffer->end(); context->dirtyUI = false; } imageShader->draw(uiFrameBuffer->getTexture(), pixel2(0, 0),pixel2(context->viewSize)); }
box2px TreeItem::update(AlloyContext* context, const pixel2& offset) { NVGcontext* nvg = context->nvgContext; nvgTextAlign(nvg, NVG_ALIGN_LEFT | NVG_ALIGN_TOP); nvgFontSize(nvg, fontSize); nvgFontFaceId(nvg, context->getFontHandle(FontType::Bold)); spaceWidth = fontSize + PADDING * 2; float textWidth = nvgTextBounds(nvg, 0, 0, name.c_str(), nullptr, nullptr); nvgFontFaceId(nvg, context->getFontHandle(FontType::Icon)); float iconWidth = (iconCodeString.length() == 0) ? 0 : nvgTextBounds(nvg, 0, 0, iconCodeString.c_str(), nullptr, nullptr) + PADDING * 2; float th = (name.length() > 0) ? fontSize + PADDING * 2 : 0; selectionBounds = box2px(offset, pixel2(textWidth + iconWidth + spaceWidth + PADDING, th)); bounds = selectionBounds; if (isExpanded()) { pixel2 pt = offset + pixel2((name.length() > 0) ? spaceWidth : 0, th); for (TreeItemPtr& item : children) { box2px cdims = item->update(context, pt); bounds.dimensions = aly::max(bounds.max(), cdims.max()) - aly::min(bounds.min(), cdims.min()); pt += pixel2(0.0f, cdims.dimensions.y); } } return bounds; }
void Application::onCursorPos(double xpos, double ypos) { context->hasFocus = true; context->cursorPosition = pixel2((pixel)( xpos), (pixel) (ypos)); InputEvent& e = inputEvent; e.type = InputType::Cursor; e.cursor = pixel2((pixel) (xpos), (pixel) (ypos)); fireEvent(e); }
void Application::onWindowFocus(int focused) { if (focused) { context->hasFocus = true; InputEvent& e = inputEvent; e.type = InputType::Cursor; e.cursor = context->cursorPosition; fireEvent(e); } else { context->mouseOverRegion = nullptr; context->mouseDownRegion = nullptr; context->cursorPosition = pixel2(-1, -1); context->cursorDownPosition = pixel2(-1, -1); context->hasFocus = false; } }
void ExpandTree::pack(const pixel2& pos, const pixel2& dims, const double2& dpmm, double pixelRatio, bool clamp) { update(AlloyApplicationContext().get()); drawRegion->dimensions = CoordPX( aly::max(getBounds().dimensions, root.getBounds().dimensions + pixel2(Composite::scrollBarSize))); Composite::pack(pos, dims, dpmm, pixelRatio, clamp); }
void Application::onScroll(double xoffset, double yoffset) { InputEvent& e = inputEvent; e.cursor = context->cursorPosition; e.type = InputType::Scroll; e.scroll = pixel2((pixel) xoffset, (pixel) yoffset); GLFWwindow* window = context->window; e.mods = 0; if (glfwGetKey(window, GLFW_KEY_LEFT_SHIFT) | glfwGetKey(window, GLFW_KEY_RIGHT_SHIFT)) e.mods |= GLFW_MOD_SHIFT; if (glfwGetKey(window, GLFW_KEY_LEFT_CONTROL) | glfwGetKey(window, GLFW_KEY_RIGHT_CONTROL)) e.mods |= GLFW_MOD_CONTROL; if (glfwGetKey(window, GLFW_KEY_LEFT_ALT) | glfwGetKey(window, GLFW_KEY_RIGHT_ALT)) e.mods |= GLFW_MOD_ALT; if (glfwGetKey(window, GLFW_KEY_LEFT_SUPER) | glfwGetKey(window, GLFW_KEY_RIGHT_SUPER)) e.mods |= GLFW_MOD_SUPER; fireEvent(e); }
namespace aly { std::shared_ptr<AlloyContext> AlloyContext::defaultContext; const Cursor Cursor::Normal(0xf245, 24.0f); const Cursor Cursor::Hand(0xf25a, 24.0f, NVG_ALIGN_TOP | NVG_ALIGN_LEFT, FontType::Icon,0.0f,pixel2(-8.0f,0.0f)); const Cursor Cursor::Grab(0xf255, 24.0f, NVG_ALIGN_MIDDLE | NVG_ALIGN_CENTER, FontType::Icon); const Cursor Cursor::Horizontal(0xf07e, 24.0f, NVG_ALIGN_MIDDLE | NVG_ALIGN_CENTER); const Cursor Cursor::Vertical(0xf07d, 24.0f, NVG_ALIGN_MIDDLE | NVG_ALIGN_CENTER); const Cursor Cursor::Position(0xf047, 24.0f, NVG_ALIGN_MIDDLE | NVG_ALIGN_CENTER); const Cursor Cursor::TextInsert(0xf246, 24.0f, NVG_ALIGN_MIDDLE | NVG_ALIGN_CENTER); const Cursor Cursor::SlantDown(0xf07d, 24.0f, NVG_ALIGN_MIDDLE | NVG_ALIGN_CENTER, FontType::Icon, -ALY_PI_4); const Cursor Cursor::SlantUp(0xf07d, 24.0f, NVG_ALIGN_MIDDLE | NVG_ALIGN_CENTER, FontType::Icon, ALY_PI_4); const Cursor Cursor::CrossHairs(0xf05b, 24.0f,NVG_ALIGN_MIDDLE|NVG_ALIGN_CENTER, FontType::Icon,0.0f,pixel2(-0.25f,-0.25f)); void Cursor::draw(AlloyContext* context) const { pixel2 cursor = context->cursorPosition; if (fontSize > 0.0f && context->hasFocus && cursor.x >= 0 && cursor.y >= 0 && cursor.x < context->getScreenWidth() && cursor.y < context->getScreenHeight()) { NVGcontext* nvg = context->nvgContext; nvgTextAlign(nvg, align); nvgSave(nvg); nvgFontFaceId(nvg, context->getFontHandle(fontType)); nvgFontSize(nvg, fontSize); nvgFillColor(nvg, Color(255, 255, 255)); nvgTranslate(nvg, cursor.x+nudge.x, cursor.y+nudge.y); nvgRotate(nvg, angle); const float shift = 1.0f; const char* txt = codeString.c_str(); nvgFillColor(nvg, Color(0, 0, 0)); nvgText(nvg, +shift, 0, txt, nullptr); nvgText(nvg, -shift, 0, txt, nullptr); nvgText(nvg, 0, +shift, txt, nullptr); nvgText(nvg, 0, -shift, txt, nullptr); nvgFillColor(nvg, Color(255, 255, 255)); nvgText(nvg, 0, 0, txt, nullptr); nvgRestore(nvg); } } Font::Font(const std::string& name, const std::string& file, AlloyContext* context) : nvg(context->nvgContext), handle(0), name(name), file(file) { handle = nvgCreateFont(nvg, name.c_str(), file.c_str()); } int Font::getCursorPosition(const std::string & text, float fontSize, int xCoord) const { std::vector<NVGglyphPosition> positions(text.size()); nvgFontSize(nvg, fontSize); nvgFontFaceId(nvg, handle); nvgTextAlign(nvg, NVG_ALIGN_LEFT | NVG_ALIGN_TOP); positions.resize( nvgTextGlyphPositions(nvg, 0, 0, text.data(), text.data() + text.size(), positions.data(), (int) positions.size())); for (size_t i = 0; i < positions.size(); ++i) { if (xCoord < positions[i].maxx) { return static_cast<int>(i); } } return static_cast<int>(positions.size()); } AwesomeGlyph::AwesomeGlyph(int codePoint, AlloyContext* context, const FontStyle& style, pixel height) : Glyph(CodePointToUTF8(codePoint), GlyphType::Awesome, 0, height), codePoint( codePoint), style(style) { NVGcontext* nvg = context->nvgContext; nvgFontSize(nvg, height); nvgFontFaceId(nvg, context->getFontHandle(FontType::Icon)); width = nvgTextBounds(nvg, 0, 0, name.c_str(), nullptr, nullptr); } void AwesomeGlyph::draw(const box2px& bounds, const Color& fgColor, const Color& bgColor, AlloyContext* context) { NVGcontext* nvg = context->nvgContext; nvgFontFaceId(nvg, context->getFontHandle(FontType::Icon)); nvgFontSize(nvg, height); nvgTextAlign(nvg, NVG_ALIGN_MIDDLE | NVG_ALIGN_CENTER); drawText(nvg, bounds.position + HALF_PIX(bounds.dimensions), name, style, fgColor, bgColor, nullptr); } ImageGlyph::ImageGlyph(const std::string& file, AlloyContext* context, bool mipmap) : Glyph(GetFileNameWithoutExtension(file), GlyphType::Image, 0, 0), file( file) { handle = nvgCreateImage(context->nvgContext, file.c_str(), (mipmap) ? NVG_IMAGE_GENERATE_MIPMAPS : 0); int w, h; nvgImageSize(context->nvgContext, handle, &w, &h); width = (pixel) w; height = (pixel) h; } void ImageGlyph::set(const ImageRGBA& rgba, AlloyContext* context) { nvgUpdateImage(context->nvgContext, handle, rgba.ptr()); } ImageGlyph::~ImageGlyph() { AlloyContext* context = AlloyDefaultContext().get(); if (context) nvgDeleteImage(context->nvgContext, handle); } ImageGlyph::ImageGlyph(const ImageRGBA& rgba, AlloyContext* context, bool mipmap) : Glyph("image_rgba", GlyphType::Image, 0, 0) { handle = nvgCreateImageRGBA(context->nvgContext, rgba.width, rgba.height, (mipmap) ? NVG_IMAGE_GENERATE_MIPMAPS : 0, rgba.ptr()); width = (pixel) rgba.width; height = (pixel) rgba.height; } void ImageGlyph::draw(const box2px& bounds, const Color& fgColor, const Color& bgColor, AlloyContext* context) { NVGcontext* nvg = context->nvgContext; NVGpaint imgPaint = nvgImagePattern(nvg, bounds.position.x, bounds.position.y, bounds.dimensions.x, bounds.dimensions.y, 0.f, handle, 1.0f); nvgBeginPath(nvg); nvgFillColor(nvg, Color(COLOR_WHITE)); nvgRect(nvg, bounds.position.x, bounds.position.y, bounds.dimensions.x, bounds.dimensions.y); nvgFillPaint(nvg, imgPaint); nvgFill(nvg); if (fgColor.a > 0) { nvgBeginPath(nvg); nvgRect(nvg, bounds.position.x, bounds.position.y, bounds.dimensions.x, bounds.dimensions.y); nvgFillColor(nvg, Color(fgColor)); nvgFill(nvg); } } CheckerboardGlyph::CheckerboardGlyph(int width, int height, int horizTiles, int vertTiles, AlloyContext* context, bool mipmap) : Glyph("image_rgba", GlyphType::Image, (pixel) width, (pixel) height) { ImageRGBA img(width, height); int cellWidth = width / horizTiles; int cellHeight = height / vertTiles; for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { bool vt = (i / cellWidth) % 2 == 0; bool ht = (j / cellHeight) % 2 == 0; img(i, j) = ((vt && !ht) || (!vt && ht)) ? RGBA(0, 0, 0, 0) : RGBA(255, 255, 255, 255); } } handle = nvgCreateImageRGBA(context->nvgContext, width, height, (mipmap) ? NVG_IMAGE_GENERATE_MIPMAPS : 0, img.ptr()); } void CheckerboardGlyph::draw(const box2px& bounds, const Color& fgColor, const Color& bgColor, AlloyContext* context) { NVGcontext* nvg = context->nvgContext; NVGpaint imgPaint = nvgImagePattern(nvg, bounds.position.x, bounds.position.y, bounds.dimensions.x, bounds.dimensions.y, 0.f, handle, 1.0f); if (bgColor.a > 0) { nvgBeginPath(nvg); nvgRect(nvg, bounds.position.x, bounds.position.y, bounds.dimensions.x, bounds.dimensions.y); nvgFillColor(nvg, Color(bgColor)); nvgFill(nvg); } nvgBeginPath(nvg); nvgRect(nvg, bounds.position.x, bounds.position.y, bounds.dimensions.x, bounds.dimensions.y); nvgFillPaint(nvg, imgPaint); nvgFill(nvg); if (fgColor.a > 0) { nvgBeginPath(nvg); nvgRect(nvg, bounds.position.x, bounds.position.y, bounds.dimensions.x, bounds.dimensions.y); nvgFillColor(nvg, Color(fgColor)); nvgFill(nvg); } } void AlloyContext::addAssetDirectory(const std::string& dir) { std::string dirCopy = dir; if (ALY_PATH_SEPARATOR[0] != '/') { for (char& c : dirCopy) { if (c == '/') { c = ALY_PATH_SEPARATOR[0]; } } } else if (ALY_PATH_SEPARATOR[0] != '\\') { for (char& c : dirCopy) { if (c == '\\') { c = ALY_PATH_SEPARATOR[0]; } } } assetDirectories.push_back(dirCopy); } std::shared_ptr<Font>& AlloyContext::loadFont(FontType type, const std::string& name, const std::string& file) { int idx = static_cast<int>(type); if (idx >= (int)fonts.size()) { fonts.resize(idx + 1); } fonts[idx] = std::shared_ptr<Font>(new Font(name, getFullPath(file), this)); return fonts[static_cast<int>(type)]; } std::shared_ptr<Font>& AlloyContext::loadFont(int idx, const std::string& name, const std::string& file) { if (idx >= (int)fonts.size()) { fonts.resize(idx + 1); } fonts[idx] = std::shared_ptr<Font>(new Font(name, getFullPath(file), this)); return fonts[idx]; } std::shared_ptr<Font>& AlloyContext::loadFont(const std::string& name, const std::string& file) { fonts.push_back( std::shared_ptr<Font>(new Font(name, getFullPath(file), this))); return fonts.back(); } std::string AlloyContext::getFullPath(const std::string& partialFile) { std::string fileName = partialFile; if (ALY_PATH_SEPARATOR[0] != '/') { for (char& c : fileName) { if (c == '/') { c = ALY_PATH_SEPARATOR[0]; } } } else if (ALY_PATH_SEPARATOR[0] != '\\') { for (char& c : fileName) { if (c == '\\') { c = ALY_PATH_SEPARATOR[0]; } } } for (std::string& dir : assetDirectories) { std::string fullPath = RemoveTrailingSlash(dir) + ALY_PATH_SEPARATOR+ fileName; if (FileExists(fullPath)) { return fullPath; } } std::string executableDir = GetExecutableDirectory(); for (std::string& dir : assetDirectories) { std::string fullPath = RemoveTrailingSlash(executableDir) + ALY_PATH_SEPARATOR+ RemoveTrailingSlash(dir) + ALY_PATH_SEPARATOR + fileName; if (FileExists(fullPath)) { return fullPath; } } std::cout << "Could not find \"" << fileName << "\"\nThis is where I looked:" << std::endl; for (std::string& dir : assetDirectories) { std::string fullPath = RemoveTrailingSlash(dir) + ALY_PATH_SEPARATOR+ fileName; std::cout << "\"" << fullPath << "\"" << std::endl; fullPath = executableDir + ALY_PATH_SEPARATOR + RemoveTrailingSlash(dir) + ALY_PATH_SEPARATOR + fileName; std::cout << "\"" << fullPath << "\"" << std::endl; } throw std::runtime_error( MakeString() << "Could not find \"" << fileName << "\""); return std::string(""); } void AlloyContext::setDragObject(Region* region) { mouseDownRegion = region; cursorDownPosition = cursorPosition - mouseDownRegion->getBoundsPosition(); } bool AlloyContext::isOnTop(Region* region) const { return (onTopRegion != nullptr && (onTopRegion == region || region->hasParent(onTopRegion))); } pixel2 AlloyContext::getCursorDownPosition() const { return cursorDownPosition + ((mouseDownRegion != nullptr) ? mouseDownRegion->getBoundsPosition() : pixel2(0.0f)); } bool AlloyContext::fireListeners(const InputEvent& event) { firingListeners=true; for (auto iter = listeners.rbegin(); iter != listeners.rend(); iter++) { if(firingListeners){ EventHandler* handler = *iter; if (handler->onEventHandler(this, event))return true; } else { return false; } } return false; } void AlloyContext::addListener(EventHandler* region) { firingListeners=false; listeners.push_back(region); } void AlloyContext::removeListener(const EventHandler* region) { firingListeners=false; for (auto iter = listeners.begin(); iter != listeners.end(); iter++) { if (region == *iter) { listeners.erase(iter); break; } } } EventHandler::~EventHandler() { Application::removeListener(this); } void AlloyContext::setOnTopRegion(Region* region) { if (region == nullptr) throw std::runtime_error( "On top region cannot be null. use removeOnTopRegion() instead."); if (onTopRegion != nullptr) { if (onTopRegion->onRemoveFromOnTop) onTopRegion->onRemoveFromOnTop(); } onTopRegion = region; region->setVisible(true); } void AlloyContext::removeOnTopRegion(Region* region) { if (region == nullptr) throw std::runtime_error("Remove on top region cannot be null."); if (region == onTopRegion) { if (onTopRegion->onRemoveFromOnTop) onTopRegion->onRemoveFromOnTop(); onTopRegion = nullptr; } } void AlloyContext::setOffScreenVisible(bool vis) { if (vis) { glfwShowWindow(offscreenWindow); } else { glfwHideWindow(offscreenWindow); } } AlloyContext::AlloyContext(int width, int height, const std::string& title, const Theme& theme) : nvgContext(nullptr), window(nullptr), theme(theme) { threadId = std::this_thread::get_id(); if (glfwInit() != GL_TRUE) { throw std::runtime_error("Could not initialize GLFW."); } glfwSetErrorCallback( [](int error, const char* desc) { std::cout << "GLFW Error [" << error << "] " << desc << std::endl; }); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, 1); glfwWindowHint(GLFW_VISIBLE, 0); window = glfwCreateWindow(width, height, title.c_str(), NULL, NULL); if (!window) { glfwTerminate(); throw std::runtime_error("Could not create window."); } glfwMakeContextCurrent(window); glewExperimental = GL_TRUE; if (glewInit() != GLEW_OK) { throw std::runtime_error("Could not initialize GLEW."); } glGetError(); glDepthFunc(GL_LEQUAL); glEnable(GL_DEPTH_TEST); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glfwGetFramebufferSize(window, &width, &height); glViewport(0, 0, width, height); viewSize = int2(width, height); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, 1); glfwWindowHint(GLFW_VISIBLE, 0); offscreenWindow = glfwCreateWindow(width, height, "Offscreen", NULL, NULL); if (!offscreenWindow) { glfwTerminate(); throw std::runtime_error("Could not create window."); } glfwMakeContextCurrent(offscreenWindow); glewExperimental = GL_TRUE; if (glewInit() != GLEW_OK) { throw std::runtime_error("Could not initialize GLEW."); } glGetError(); glDepthFunc(GL_LEQUAL); glEnable(GL_DEPTH_TEST); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glViewport(0, 0, width, height); glfwMakeContextCurrent(window); nvgContext = nvgCreateGL3(NVG_ANTIALIAS | NVG_STENCIL_STROKES); const float2 TextureCoords[6] = { float2(1.0f, 0.0f), float2(0.0f, 0.0f), float2(0.0f, 1.0f), float2(0.0f, 1.0f), float2(1.0f, 1.0f), float2( 1.0f, 0.0f) }; const float3 PositionCoords[6] = { float3(1.0f, 1.0f, 0.0f), float3(0.0f, 1.0f, 0.0f), float3(0.0f, 0.0f, 0.0f), float3(0.0f, 0.0f, 0.0f), float3(1.0f, 0.0f, 0.0f), float3(1.0f, 1.0f, 0.0f) }; glGenVertexArrays(1, &vaoImageOnScreen.vao); glBindVertexArray(vaoImageOnScreen.vao); glGenBuffers(1, &vaoImageOnScreen.positionBuffer); glBindBuffer(GL_ARRAY_BUFFER, vaoImageOnScreen.positionBuffer); glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 3 * 6, PositionCoords, GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, 0); glGenBuffers(1, &vaoImageOnScreen.uvBuffer); glBindBuffer(GL_ARRAY_BUFFER, vaoImageOnScreen.uvBuffer); glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 2 * 6, TextureCoords, GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); glfwHideWindow(offscreenWindow); glfwMakeContextCurrent(offscreenWindow); glGenVertexArrays(1, &vaoImageOffScreen.vao); glBindVertexArray(vaoImageOffScreen.vao); glGenBuffers(1, &vaoImageOffScreen.positionBuffer); glBindBuffer(GL_ARRAY_BUFFER, vaoImageOffScreen.positionBuffer); glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 3 * 6, PositionCoords, GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, 0); glGenBuffers(1, &vaoImageOffScreen.uvBuffer); glBindBuffer(GL_ARRAY_BUFFER, vaoImageOffScreen.uvBuffer); glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 2 * 6, TextureCoords, GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); glfwMakeContextCurrent(window); int widthMM, heightMM; GLFWmonitor* monitor = glfwGetPrimaryMonitor(); if (monitor == nullptr) throw std::runtime_error("Could not find monitor."); const GLFWvidmode* mode = glfwGetVideoMode(monitor); if (mode == nullptr) throw std::runtime_error("Could not find video monitor."); glfwGetMonitorPhysicalSize(monitor, &widthMM, &heightMM); dpmm = double2(mode->width / (double) widthMM, mode->height / (double) heightMM); int winWidth, winHeight, fbWidth, fbHeight; glfwGetWindowSize(window, &winWidth, &winHeight); glfwGetFramebufferSize(window, &fbWidth, &fbHeight); // Calculate pixel ration for hi-dpi devices. screenSize = int2(winWidth,winHeight); viewSize = int2(fbWidth,fbHeight); pixelRatio = pixel((float) fbWidth / (float) winWidth); lastAnimateTime = std::chrono::steady_clock::now(); lastCursorTime = std::chrono::steady_clock::now(); lastUpdateTime = std::chrono::steady_clock::now(); cursor = &Cursor::Normal; } void AlloyContext::addDeferredTask(const std::function<void()>& func, bool block) { std::lock_guard<std::mutex> guard(taskLock); deferredTasks.push_back(func); if (block) { std::thread::id currentThread = std::this_thread::get_id(); if (currentThread != threadId) { std::this_thread::yield(); while (deferredTasks.size() > 0) { std::this_thread::yield(); std::this_thread::sleep_for(std::chrono::milliseconds(100)); } } else { throw std::runtime_error( "Cannot block and wait for deferred task on same thread as Alloy context."); } } } bool AlloyContext::executeDeferredTasks() { std::lock_guard<std::mutex> guard(taskLock); if (deferredTasks.size() > 0) { for (std::function<void()>& func : deferredTasks) { func(); } deferredTasks.clear(); return true; } return false; } bool AlloyContext::isMouseContainedIn(Region* region) const { return (region->getBounds().contains(cursorPosition)); } bool AlloyContext::isMouseContainedIn(const box2px& box) const { return (box.contains(cursorPosition)); } bool AlloyContext::isMouseContainedIn(const pixel2& pos, const pixel2& dims) const { return ((box2px(pos, dims)).contains(cursorPosition)); } bool AlloyContext::isMouseOver(Region* region, bool includeParent) const { if (includeParent) { return (mouseOverRegion == region || (mouseOverRegion != nullptr && mouseOverRegion->hasParent(region))); } else { return (mouseOverRegion == region); } } void AlloyContext::setMouseDownObject(Region* region) { if (region != nullptr && mouseDownRegion != nullptr) { cursorDownPosition = cursorDownPosition + mouseDownRegion->getBoundsPosition() - region->getBoundsPosition(); } mouseDownRegion = region; } bool AlloyContext::isMouseDown(Region* region, bool includeParent) const { if (includeParent) { return (mouseDownRegion == region || (mouseDownRegion != nullptr && mouseDownRegion->hasParent(region))); } else { return (mouseDownRegion == region); } } std::shared_ptr<Composite>& AlloyContext::getGlassPane() { if (glassPane.get() == nullptr) { glassPane = std::shared_ptr<Composite>( new Composite("Glass Pane", CoordPX(0, 0), CoordPercent(1.0f, 1.0f))); glassPane->backgroundColor = MakeColor( theme.DARKEST.toSemiTransparent(0.5f)); } return glassPane; } void AlloyContext::clearEvents() { mouseOverRegion = nullptr; mouseDownRegion = nullptr; mouseFocusRegion = nullptr; onTopRegion = nullptr; } void AlloyContext::clearEvents(Region* region) { if (mouseOverRegion!=nullptr&&(region==mouseOverRegion||mouseOverRegion->hasParent(region))) mouseOverRegion = nullptr; if (mouseDownRegion != nullptr && (region == mouseDownRegion || mouseDownRegion->hasParent(region))) mouseDownRegion = nullptr; if (mouseFocusRegion != nullptr && (region == mouseFocusRegion || mouseFocusRegion->hasParent(region))) mouseFocusRegion = nullptr; if (onTopRegion != nullptr && (region == onTopRegion || onTopRegion->hasParent(region))) onTopRegion = nullptr; } Region* AlloyContext::locate(const pixel2& cursor) const { if (onTopRegion != nullptr) { if (onTopRegion->isVisible()) { Region* r = onTopRegion->locate(cursor); if (r != nullptr) return r; } } return cursorLocator.locate(cursor); } bool AlloyContext::isOnScreenRender() const { return (windowHistory.back() == window); } bool AlloyContext::isOffScreenRender() const { return (windowHistory.back() == offscreenWindow); } bool AlloyContext::begin(bool onScreen) { windowHistory.push_back(glfwGetCurrentContext()); if (onScreen) { glfwMakeContextCurrent(window); } else { glfwMakeContextCurrent(offscreenWindow); } return (windowHistory.size() == 1); } void AlloyContext::initOffScreenDraw() { begin(false); glViewport(0, 0, viewSize.x, viewSize.y); glScissor(0, 0, viewSize.x, viewSize.y); glClearColor(0.0, 0.0, 0.0, 0.0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); glEnable(GL_DEPTH_TEST); glDisable(GL_SCISSOR_TEST); end(); } bool AlloyContext::end() { if (windowHistory.size() > 0) { glfwMakeContextCurrent(windowHistory.back()); windowHistory.pop_back(); return true; } else return false; } bool AlloyContext::isFocused(Region* region) { if (mouseFocusRegion != nullptr) { if (mouseFocusRegion->isVisible()) { return (region == mouseFocusRegion); } else { mouseFocusRegion = nullptr; return false; } } return false; } void AlloyContext::update(Composite& rootNode) { endTime = std::chrono::steady_clock::now(); double updateElapsed = std::chrono::duration<double>( endTime - lastUpdateTime).count(); double animateElapsed = std::chrono::duration<double>( endTime - lastAnimateTime).count(); double cursorElapsed = std::chrono::duration<double>( endTime - lastCursorTime).count(); if (deferredTasks.size() > 0) { executeDeferredTasks(); cursorLocator.reset(screenSize); rootNode.updateCursor(&cursorLocator); dirtyCursorLocator = false; mouseOverRegion = locate(cursorPosition); dirtyCursor = false; dirtyLayout = true; } if (updateElapsed > UPDATE_LOCATOR_INTERVAL_SEC) { if (dirtyCursorLocator) { cursorLocator.reset(screenSize); rootNode.updateCursor(&cursorLocator); dirtyCursorLocator = false; mouseOverRegion = locate(cursorPosition); dirtyCursor = false; } lastUpdateTime = endTime; } if (cursorElapsed >= UPDATE_CURSOR_INTERVAL_SEC) { //Dont try to animate faster than 60 fps. if (dirtyCursor && !dirtyCursorLocator) { mouseOverRegion = locate(cursorPosition); dirtyCursor = false; } dirtyUI = true; lastCursorTime = endTime; } if (animateElapsed >= ANIMATE_INTERVAL_SEC) { //Dont try to animate faster than 60 fps. lastAnimateTime = endTime; if (animator.step(animateElapsed)) { dirtyLayout = true; dirtyUI = true; } } if (dirtyLayout) { rootNode.pack(this); animator.firePostEvents(); dirtyCursorLocator = true; dirtyLayout = false; } } void AlloyContext::makeCurrent() { glfwMakeContextCurrent(window); } AlloyContext::~AlloyContext() { glfwMakeContextCurrent(window); if (vaoImageOnScreen.vao) { glDeleteVertexArrays(1, &vaoImageOnScreen.vao); } if (vaoImageOnScreen.uvBuffer) { glDeleteBuffers(1, &vaoImageOnScreen.uvBuffer); } if (vaoImageOnScreen.positionBuffer) { glDeleteBuffers(1, &vaoImageOnScreen.positionBuffer); } glfwMakeContextCurrent(offscreenWindow); if (vaoImageOffScreen.vao) { glDeleteVertexArrays(1, &vaoImageOffScreen.vao); } if (vaoImageOffScreen.uvBuffer) { glDeleteBuffers(1, &vaoImageOffScreen.uvBuffer); } if (vaoImageOffScreen.positionBuffer) { glDeleteBuffers(1, &vaoImageOffScreen.positionBuffer); } nvgDeleteGL3(nvgContext); glfwDestroyWindow(window); glfwTerminate(); } }
pixel2 AlloyContext::getCursorDownPosition() const { return cursorDownPosition + ((mouseDownRegion != nullptr) ? mouseDownRegion->getBoundsPosition() : pixel2(0.0f)); }
void GraphPane::draw(AlloyContext* context) { Region::draw(context); box2px rbounds = getBounds(); NVGcontext* nvg = context->nvgContext; box2px gbounds = rbounds; const float LARGE_TEXT = 18.0f; const float MEDIUM_TEXT = 16.0f; const float SMALL_TEXT = 12.0f; float2 gpos(-1, -1); gbounds.position = pixel2(rbounds.position.x + GRAPH_PADDING, rbounds.position.y + GRAPH_PADDING); gbounds.dimensions = pixel2(rbounds.dimensions.x - GRAPH_PADDING * 2, rbounds.dimensions.y - GRAPH_PADDING * 2); if (graphBounds.dimensions.x < 0 || graphBounds.dimensions.y < 0) { updateGraphBounds(); } nvgBeginPath(nvg); nvgRoundedRect(nvg, gbounds.position.x - 2, gbounds.position.y - 2, gbounds.dimensions.x + 4, gbounds.dimensions.y + 4, context->theme.CORNER_RADIUS); nvgFillColor(nvg, context->theme.LIGHTEST); nvgFill(nvg); //Draw vertical line for x=0 if (graphBounds.position.x < 0 && graphBounds.position.x + graphBounds.dimensions.x > 0) { float xpos = -graphBounds.position.x / graphBounds.dimensions.x; nvgBeginPath(nvg); nvgMoveTo(nvg, xpos * gbounds.dimensions.x + gbounds.position.x, gbounds.position.y); nvgLineTo(nvg, xpos * gbounds.dimensions.x + gbounds.position.x, gbounds.position.y + gbounds.dimensions.y); nvgStrokeWidth(nvg, 2.0f); nvgStrokeColor(nvg, context->theme.DARK.toSemiTransparent(0.75f)); nvgStroke(nvg); } //Draw horizontal line for y=0 if (graphBounds.position.y < 0 && graphBounds.position.y + graphBounds.dimensions.y > 0) { float ypos = -graphBounds.position.y / graphBounds.dimensions.y; nvgBeginPath(nvg); nvgMoveTo(nvg, gbounds.position.x, ypos * gbounds.dimensions.y + gbounds.position.y); nvgLineTo(nvg, gbounds.position.x + gbounds.dimensions.x, ypos * gbounds.dimensions.y + gbounds.position.y); nvgStrokeWidth(nvg, 2.0f); nvgStrokeColor(nvg, context->theme.DARK.toSemiTransparent(0.75f)); nvgStroke(nvg); } if (gbounds.contains(cursorPosition)) { context->setCursor(&Cursor::CrossHairs); gpos = (cursorPosition - gbounds.position) / gbounds.dimensions; gpos.y = 1 - gpos.y; gpos = gpos * graphBounds.dimensions + graphBounds.position; nvgBeginPath(nvg); nvgMoveTo(nvg, cursorPosition.x, gbounds.position.y); nvgLineTo(nvg, cursorPosition.x, gbounds.position.y + gbounds.dimensions.y); nvgStrokeWidth(nvg, 1.0f); nvgStrokeColor(nvg, context->theme.DARK.toSemiTransparent(0.25f)); nvgStroke(nvg); nvgBeginPath(nvg); nvgMoveTo(nvg, gbounds.position.x, cursorPosition.y); nvgLineTo(nvg, gbounds.position.x + gbounds.dimensions.x, cursorPosition.y); nvgStrokeWidth(nvg, 1.0f); nvgStrokeColor(nvg, context->theme.DARK.toSemiTransparent(0.25f)); nvgStroke(nvg); } for (GraphDataPtr& curve : curves) { std::vector<float2> points = curve->points; if (points.size() > 1 && graphBounds.dimensions.x > 0.0f && graphBounds.dimensions.y > 0.0f) { NVGcontext* nvg = context->nvgContext; float2 last = points[0]; last = (last - graphBounds.position) / graphBounds.dimensions; last.y = 1.0f - last.y; last = last * gbounds.dimensions + gbounds.position; nvgBeginPath(nvg); nvgMoveTo(nvg, last.x, last.y); for (int i = 1; i < (int)points.size(); i++) { float2 pt = points[i]; pt = (pt - graphBounds.position) / graphBounds.dimensions; pt.y = 1.0f - pt.y; pt = pt * gbounds.dimensions + gbounds.position; nvgLineTo(nvg, pt.x, pt.y); last = pt; } nvgStrokeWidth(nvg, 2.0f); nvgStrokeColor(nvg, curve->color); nvgStroke(nvg); } } nvgFontFaceId(nvg, context->getFontHandle(FontType::Bold)); nvgFontSize(nvg, LARGE_TEXT); nvgTextAlign(nvg, NVG_ALIGN_CENTER | NVG_ALIGN_TOP); drawText(nvg, rbounds.position + float2(rbounds.dimensions.x / 2, 2.0f), name, FontStyle::Outline, context->theme.LIGHTEST, context->theme.DARK); nvgFontSize(nvg, MEDIUM_TEXT); nvgFontFaceId(nvg, context->getFontHandle(FontType::Bold)); nvgTextAlign(nvg, NVG_ALIGN_CENTER | NVG_ALIGN_BOTTOM); drawText(nvg, rbounds.position + float2(rbounds.dimensions.x / 2, rbounds.dimensions.y - 4.0f), xAxisLabel, FontStyle::Outline, context->theme.LIGHTEST, context->theme.DARK); nvgTextAlign(nvg, NVG_ALIGN_CENTER | NVG_ALIGN_TOP); nvgSave(nvg); pixel2 center = rbounds.position + float2(2.0f, rbounds.dimensions.y * 0.5f); nvgTranslate(nvg, center.x, center.y); nvgRotate(nvg, -ALY_PI * 0.5f); drawText(nvg, pixel2(0, 2), yAxisLabel, FontStyle::Outline, context->theme.LIGHTEST, context->theme.DARK); nvgRestore(nvg); nvgFontSize(nvg, SMALL_TEXT); nvgTextAlign(nvg, NVG_ALIGN_RIGHT | NVG_ALIGN_TOP); drawText(nvg, rbounds.position + float2(GRAPH_PADDING, GRAPH_PADDING), MakeString() << std::setprecision(2) << (graphBounds.position.y + graphBounds.dimensions.y), FontStyle::Outline, context->theme.LIGHTER, context->theme.DARK); nvgTextAlign(nvg, NVG_ALIGN_RIGHT | NVG_ALIGN_BOTTOM); drawText(nvg, rbounds.position + float2(GRAPH_PADDING, rbounds.dimensions.y - GRAPH_PADDING), MakeString() << std::setprecision(2) << graphBounds.position.y, FontStyle::Outline, context->theme.LIGHTER, context->theme.DARK); nvgTextAlign(nvg, NVG_ALIGN_RIGHT | NVG_ALIGN_TOP); drawText(nvg, rbounds.position + float2(rbounds.dimensions.x - GRAPH_PADDING, rbounds.dimensions.y - GRAPH_PADDING + 2), MakeString() << std::setprecision(2) << (graphBounds.position.x + graphBounds.dimensions.x), FontStyle::Outline, context->theme.LIGHTER, context->theme.DARK); nvgTextAlign(nvg, NVG_ALIGN_LEFT | NVG_ALIGN_TOP); drawText(nvg, rbounds.position + float2(GRAPH_PADDING, rbounds.dimensions.y - GRAPH_PADDING + 2), MakeString() << std::setprecision(2) << graphBounds.position.x, FontStyle::Outline, context->theme.LIGHTER, context->theme.DARK); if (cursorPosition.x >= 0) { float minDist = 1E30f; float bestY = 0; GraphDataPtr closestCurve; for (GraphDataPtr& curve : curves) { float y = curve->interpolate(gpos.x); if (y != GraphData::NO_INTERSECT) { if (std::abs(y - gpos.y) < minDist) { minDist = std::abs(y - gpos.y); bestY = y; closestCurve = curve; } } } if (closestCurve.get() != nullptr) { nvgBeginPath(nvg); nvgStrokeWidth(nvg, 2.0f); nvgFillColor(nvg, closestCurve->color); nvgStrokeColor(nvg, context->theme.LIGHTER); float2 pt(gpos.x, bestY); pt = (pt - graphBounds.position) / graphBounds.dimensions; pt.y = 1.0f - pt.y; pt = pt * gbounds.dimensions + gbounds.position; nvgCircle(nvg, pt.x, pt.y, 4); nvgFill(nvg); nvgStroke(nvg); nvgBeginPath(nvg); nvgFillColor(nvg, context->theme.DARK); nvgCircle(nvg, cursorPosition.x, cursorPosition.y, 2); nvgFill(nvg); nvgTextAlign(nvg, NVG_ALIGN_RIGHT | NVG_ALIGN_MIDDLE); nvgFontSize(nvg, MEDIUM_TEXT); drawText(nvg, float2(pt.x - 8, pt.y), closestCurve->name, FontStyle::Outline, context->theme.LIGHTEST, context->theme.DARK); nvgTextAlign(nvg, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE); drawText(nvg, float2(pt.x + 8, pt.y), MakeString() << "(" << std::setprecision(2) << gpos.x << ", " << std::setprecision(2) << bestY << ")", FontStyle::Outline, context->theme.LIGHTEST, context->theme.DARK); } else { nvgBeginPath(nvg); nvgFillColor(nvg, context->theme.DARK); nvgCircle(nvg, cursorPosition.x, cursorPosition.y, 2); nvgFill(nvg); } } }
void Application::fireEvent(const InputEvent& event) { if (event.type == InputType::Cursor || event.type == InputType::MouseButton) { context->requestUpdateCursor(); } bool consumed = false; if (event.type == InputType::Scroll && context->mouseOverRegion != nullptr && context->mouseOverRegion->onScroll) { consumed = context->mouseOverRegion->onScroll(context.get(), event); } else if (event.type == InputType::MouseButton) { if (event.isDown()) { if (event.button == GLFW_MOUSE_BUTTON_LEFT) { context->leftMouseButton = true; } if (event.button == GLFW_MOUSE_BUTTON_RIGHT) { context->rightMouseButton = true; } context->mouseOverRegion = context->mouseFocusRegion = context->mouseDownRegion = context->locate( context->cursorPosition); if (context->mouseDownRegion != nullptr) { context->cursorDownPosition = event.cursor - context->mouseDownRegion->getBoundsPosition(); } } else if (event.isUp()) { if (context->mouseDownRegion != nullptr && context->getOnTopRegion() == context->mouseDownRegion && context->mouseDownRegion->isDragEnabled()) { context->removeOnTopRegion(context->mouseDownRegion); } context->leftMouseButton = false; context->rightMouseButton = false; context->mouseDownRegion = nullptr; context->cursorDownPosition = pixel2(0, 0); } } //Fire events if (context->mouseDownRegion != nullptr && event.type != InputType::MouseButton && context->mouseDownRegion->isDragEnabled()) { if (context->mouseDownRegion->onMouseDrag) { consumed |= context->mouseDownRegion->onMouseDrag(context.get(), event); } else { if (context->leftMouseButton) { //context->setOnTopRegion(context->mouseDownRegion); context->mouseDownRegion->setDragOffset( context->cursorPosition, context->cursorDownPosition); } } context->requestPack(); } else if (context->mouseOverRegion != nullptr) { if (event.type == InputType::MouseButton) { if (event.isDown()) { if (context->mouseOverRegion->onMouseDown) { consumed |= context->mouseOverRegion->onMouseDown( context.get(), event); } else if (context->mouseDownRegion->isDragEnabled()) { //context->setOnTopRegion(context->mouseDownRegion); context->mouseDownRegion->setDragOffset( context->cursorPosition, context->cursorDownPosition); } } if (context->mouseOverRegion!=nullptr&&context->mouseOverRegion->onMouseUp && event.isUp()) consumed |= context->mouseOverRegion->onMouseUp(context.get(), event); context->requestPack(); } if (event.type == InputType::Cursor) { if (context->mouseOverRegion != nullptr && context->mouseOverRegion->onMouseOver) { consumed |= context->mouseOverRegion->onMouseOver(context.get(), event); } } } if (!consumed) { consumed = context->fireListeners(event); } if (consumed) context->dirtyUI = true; }
LeafItem::LeafItem( const std::function<void(AlloyContext* context, const box2px& bounds)>& onDraw, const pixel2& dimensions) : onDraw(onDraw) { bounds = box2px(pixel2(0.0f), dimensions); }