Get an opengl window showing.
This commit is contained in:
parent
e69c86283c
commit
9feff47a35
|
@ -5,12 +5,10 @@ project(SimpleEngine)
|
||||||
# Add some CMake options:
|
# Add some CMake options:
|
||||||
option(SIMPLE_ENGINE_BUILD_EXAMPLES "Build example projects" ON)
|
option(SIMPLE_ENGINE_BUILD_EXAMPLES "Build example projects" ON)
|
||||||
|
|
||||||
set(SFML_BUILD_AUDIO ON)
|
find_package(GLEW REQUIRED)
|
||||||
set(SFML_BUILD_GRAPHICS ON)
|
find_package(glfw3 CONFIG REQUIRED)
|
||||||
set(SFML_BUILD_WINDOW ON)
|
find_package(glm CONFIG REQUIRED)
|
||||||
set(SFML_BUILD_SYSTEM ON)
|
find_package(soil2 CONFIG REQUIRED)
|
||||||
|
|
||||||
find_package(SFML 2 COMPONENTS system main window graphics audio REQUIRED)
|
|
||||||
|
|
||||||
# Link sources
|
# Link sources
|
||||||
file(GLOB_RECURSE source_list src/*.cpp)
|
file(GLOB_RECURSE source_list src/*.cpp)
|
||||||
|
@ -20,13 +18,15 @@ add_library(simpleengine STATIC ${source_list})
|
||||||
target_include_directories(simpleengine PUBLIC include PRIVATE include/simpleengine)
|
target_include_directories(simpleengine PUBLIC include PRIVATE include/simpleengine)
|
||||||
|
|
||||||
# Link dependencies
|
# Link dependencies
|
||||||
target_link_libraries(simpleengine PUBLIC sfml-system sfml-main sfml-window sfml-network sfml-graphics sfml-audio OpenGL Freetype OpenAL Vorbis FLAC)
|
target_link_libraries(simpleengine PUBLIC GLEW::GLEW)
|
||||||
#target_link_libraries(simpleengine PUBLIC FLAC OpenAL OpenGL Vorbis)
|
target_link_libraries(simpleengine PUBLIC glfw)
|
||||||
|
target_link_libraries(simpleengine PUBLIC glm::glm)
|
||||||
|
target_link_libraries(simpleengine PUBLIC soil2)
|
||||||
|
|
||||||
# Add examples as a target if the user has them enabled
|
# Add examples as a target if the user has them enabled
|
||||||
if (SIMPLE_ENGINE_BUILD_EXAMPLES)
|
if (SIMPLE_ENGINE_BUILD_EXAMPLES)
|
||||||
add_subdirectory(examples)
|
add_subdirectory(examples)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Set C++ standard to C++17
|
# Set C++ standard to C++20
|
||||||
set_target_properties(simpleengine PROPERTIES CXX_STANDARD 17 CXX_EXTENSIONS OFF)
|
set_target_properties(simpleengine PROPERTIES CXX_STANDARD 20 CXX_EXTENSIONS OFF)
|
|
@ -1,3 +1,2 @@
|
||||||
# Add examples as a subdirectory
|
# Add examples as a subdirectory
|
||||||
add_subdirectory(snake)
|
add_subdirectory(dev_testing)
|
||||||
add_subdirectory(animation)
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
cmake_minimum_required (VERSION 3.6)
|
||||||
|
project(DevTesting DESCRIPTION "A testing project for engine developers.")
|
||||||
|
|
||||||
|
add_executable(dev_testing src/main.cpp)
|
||||||
|
|
||||||
|
# Link headers and source files.
|
||||||
|
file(GLOB_RECURSE source_list src/*.cpp)
|
||||||
|
target_sources(dev_testing PRIVATE ${source_list})
|
||||||
|
target_include_directories(dev_testing PUBLIC include)
|
||||||
|
|
||||||
|
# Link simpleengine
|
||||||
|
target_link_libraries(dev_testing PUBLIC simpleengine)
|
||||||
|
|
||||||
|
# Set standard to C++20
|
||||||
|
set_target_properties(dev_testing PROPERTIES CXX_STANDARD 20 CXX_EXTENSIONS OFF)
|
|
@ -0,0 +1,15 @@
|
||||||
|
//
|
||||||
|
// Created by SeanOMik on 7/2/2020.
|
||||||
|
// Github: https://github.com/SeanOMik
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <simpleengine/game.h>
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
simpleengine::Game game(1280, 720, "SimpleEngine - Developer Testing", false);
|
||||||
|
|
||||||
|
return game.run();
|
||||||
|
}
|
|
@ -1,145 +1,44 @@
|
||||||
//
|
//
|
||||||
// Created by SeanOMik on 7/2/2020.
|
// Created by SeanOMik on 7/2/2020.
|
||||||
// Github: https://github.com/SeanOMik
|
// Github: https://github.com/SeanOMik
|
||||||
// Email: seanomik@gmail.com
|
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef SIMPLEENGINE_GAME_H
|
#ifndef SIMPLEENGINE_GAME_H
|
||||||
#define SIMPLEENGINE_GAME_H
|
#define SIMPLEENGINE_GAME_H
|
||||||
|
|
||||||
#include <SFML/Graphics.hpp>
|
#include <string>
|
||||||
#include <SFML/System.hpp>
|
#include <memory>
|
||||||
#include <SFML/Audio.hpp>
|
|
||||||
|
|
||||||
#include <SFML/Window/Event.hpp>
|
#include <gl/glew.h>
|
||||||
|
#include <GLFW/glfw3.h>
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
#include "entity.h"
|
|
||||||
#include "events/collision_handler.h"
|
|
||||||
|
|
||||||
namespace simpleengine {
|
namespace simpleengine {
|
||||||
class Event;
|
|
||||||
|
|
||||||
class Game {
|
class Game {
|
||||||
public:
|
public:
|
||||||
friend class CollisionHandler;
|
friend class CollisionHandler;
|
||||||
|
|
||||||
Game(int w, int h, const std::string& window_name);
|
/**
|
||||||
Game(const sf::Vector2u& window_size, const std::string& window_name);
|
* @brief Construct a new Game object. Initializes GLEW and OpenGL
|
||||||
|
*
|
||||||
|
* @param w Width of viewport
|
||||||
|
* @param h Height of viewport
|
||||||
|
* @param window_name The name of the window
|
||||||
|
*/
|
||||||
|
Game(int w, int h, const std::string& window_name, const bool& resizeable = false);
|
||||||
virtual ~Game();
|
virtual ~Game();
|
||||||
|
|
||||||
void UpdateSFMLEvents();
|
void update();
|
||||||
void Update();
|
void render_window();
|
||||||
void RenderWindow();
|
void render_items();
|
||||||
void RenderItems();
|
void exit();
|
||||||
void ExitGame();
|
int run();
|
||||||
int Run();
|
|
||||||
|
|
||||||
void AddEvent(Event* event);
|
//void AddEvent(Event* event);
|
||||||
sf::RenderWindow* GetWindow();
|
std::shared_ptr<GLFWwindow> get_window();
|
||||||
|
|
||||||
template<sf::Event::EventType type, class _Fty>
|
|
||||||
void AddEventCallback(const std::function<_Fty>& func) {
|
|
||||||
if constexpr (type == sf::Event::Closed) {
|
|
||||||
close_event.Add(func);
|
|
||||||
} else if constexpr (type == sf::Event::Resized) {
|
|
||||||
resized_event.Add(func);
|
|
||||||
} else if constexpr (type == sf::Event::LostFocus) {
|
|
||||||
lost_focus_event.Add(func);
|
|
||||||
} else if constexpr (type == sf::Event::GainedFocus) {
|
|
||||||
gained_focus_event.Add(func);
|
|
||||||
} else if constexpr (type == sf::Event::TextEntered) {
|
|
||||||
text_entered_event.Add(func);
|
|
||||||
} else if constexpr (type == sf::Event::KeyPressed) {
|
|
||||||
key_pressed_event.Add(func);
|
|
||||||
} else if constexpr (type == sf::Event::KeyReleased) {
|
|
||||||
key_released_event.Add(func);
|
|
||||||
} else if constexpr (type == sf::Event::MouseWheelMoved) {
|
|
||||||
mouse_wheel_moved_event.Add(func);
|
|
||||||
} else if constexpr (type == sf::Event::MouseWheelScrolled) {
|
|
||||||
mouse_wheel_scrolled_event.Add(func);
|
|
||||||
} else if constexpr (type == sf::Event::MouseButtonPressed) {
|
|
||||||
mouse_button_pressed_event.Add(func);
|
|
||||||
} else if constexpr (type == sf::Event::MouseButtonReleased) {
|
|
||||||
mouse_button_released_event.Add(func);
|
|
||||||
} else if constexpr (type == sf::Event::MouseMoved) {
|
|
||||||
mouse_move_event.Add(func);
|
|
||||||
} else if constexpr (type == sf::Event::MouseEntered) {
|
|
||||||
mouse_entered_event.Add(func);
|
|
||||||
} else if constexpr (type == sf::Event::MouseLeft) {
|
|
||||||
mouse_left_event.Add(func);
|
|
||||||
} else if constexpr (type == sf::Event::JoystickButtonPressed) {
|
|
||||||
joy_btn_pressed_event.Add(func);
|
|
||||||
} else if constexpr (type == sf::Event::JoystickButtonReleased) {
|
|
||||||
joy_btn_released_event.Add(func);
|
|
||||||
} else if constexpr (type == sf::Event::JoystickMoved) {
|
|
||||||
joy_moved_event.Add(func);
|
|
||||||
} else if constexpr (type == sf::Event::JoystickConnected) {
|
|
||||||
joy_connected_event.Add(func);
|
|
||||||
} else if constexpr (type == sf::Event::JoystickDisconnected) {
|
|
||||||
joy_disconnected_event.Add(func);
|
|
||||||
} else if constexpr (type == sf::Event::TouchBegan) {
|
|
||||||
touch_began_event.Add(func);
|
|
||||||
} else if constexpr (type == sf::Event::TouchMoved) {
|
|
||||||
touch_moved_event.Add(func);
|
|
||||||
} else if constexpr (type == sf::Event::TouchEnded) {
|
|
||||||
touch_ended_event.Add(func);
|
|
||||||
} else if constexpr (type == sf::Event::SensorChanged) {
|
|
||||||
sensor_event.Add(func);
|
|
||||||
} else if constexpr (type == sf::Event::Count) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private:
|
private:
|
||||||
template<class _Fty>
|
static void framebuffer_resize_callback(GLFWwindow*, int fbW, int fbH);
|
||||||
class EventHolder {
|
|
||||||
public:
|
|
||||||
template<typename... Args>
|
|
||||||
void Trigger(Args... args) {
|
|
||||||
for(const std::function<_Fty>& func : callbacks) {
|
|
||||||
func(std::forward<Args>(args)...);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Add(const std::function<_Fty>& func) {
|
std::shared_ptr<GLFWwindow> window;
|
||||||
callbacks.emplace_back(func);
|
|
||||||
}
|
|
||||||
private:
|
|
||||||
std::vector<std::function<_Fty>> callbacks;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Window event callbacks:
|
|
||||||
EventHolder<void()> close_event;
|
|
||||||
EventHolder<void(sf::Event::SizeEvent)> resized_event;
|
|
||||||
EventHolder<void()> lost_focus_event;
|
|
||||||
EventHolder<void()> gained_focus_event;
|
|
||||||
EventHolder<void(sf::Event::TextEvent)> text_entered_event;
|
|
||||||
EventHolder<void(sf::Event::KeyEvent)> key_pressed_event;
|
|
||||||
EventHolder<void(sf::Event::KeyEvent)> key_released_event;
|
|
||||||
EventHolder<void(sf::Event::MouseWheelEvent)> mouse_wheel_moved_event;
|
|
||||||
EventHolder<void(sf::Event::MouseWheelScrollEvent)> mouse_wheel_scrolled_event;
|
|
||||||
EventHolder<void(sf::Event::MouseButtonEvent)> mouse_button_pressed_event;
|
|
||||||
EventHolder<void(sf::Event::MouseButtonEvent)> mouse_button_released_event;
|
|
||||||
EventHolder<void(sf::Event::MouseMoveEvent)> mouse_move_event;
|
|
||||||
EventHolder<void()> mouse_entered_event;
|
|
||||||
EventHolder<void()> mouse_left_event;
|
|
||||||
EventHolder<void(sf::Event::JoystickButtonEvent)> joy_btn_pressed_event;
|
|
||||||
EventHolder<void(sf::Event::JoystickButtonEvent)> joy_btn_released_event;
|
|
||||||
EventHolder<void(sf::Event::JoystickMoveEvent)> joy_moved_event;
|
|
||||||
EventHolder<void(sf::Event::JoystickConnectEvent)> joy_connected_event;
|
|
||||||
EventHolder<void(sf::Event::JoystickConnectEvent)> joy_disconnected_event;
|
|
||||||
EventHolder<void(sf::Event::TouchEvent)> touch_began_event;
|
|
||||||
EventHolder<void(sf::Event::TouchEvent)> touch_moved_event;
|
|
||||||
EventHolder<void(sf::Event::TouchEvent)> touch_ended_event;
|
|
||||||
EventHolder<void(sf::Event::SensorEvent)> sensor_event;
|
|
||||||
|
|
||||||
sf::RenderWindow* window;
|
|
||||||
|
|
||||||
sf::Clock delta_time_clock; // Delta time clock
|
|
||||||
float delta_time; // Delta time
|
|
||||||
|
|
||||||
std::vector<Event*> events;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
188
src/game.cpp
188
src/game.cpp
|
@ -1,160 +1,94 @@
|
||||||
//
|
//
|
||||||
// Created by SeanOMik on 7/2/2020.
|
// Created by SeanOMik on 7/2/2020.
|
||||||
// Github: https://github.com/SeanOMik
|
// Github: https://github.com/SeanOMik
|
||||||
// Email: seanomik@gmail.com
|
|
||||||
//
|
//
|
||||||
|
|
||||||
#include "game.h"
|
#include "game.h"
|
||||||
#include "entity.h"
|
|
||||||
#include "event.h"
|
|
||||||
|
|
||||||
simpleengine::Game::Game(int w, int h, const std::string& window_name) {
|
#include <iostream>
|
||||||
// Create a render window
|
|
||||||
window = new sf::RenderWindow(sf::VideoMode(w, h), window_name);
|
#include <gl/glew.h>
|
||||||
|
#include <GLFW/glfw3.h>
|
||||||
|
|
||||||
|
#include <gl/gl.h>
|
||||||
|
|
||||||
|
simpleengine::Game::Game(int w, int h, const std::string& window_name, const bool& resizeable) {
|
||||||
|
// Create a window
|
||||||
|
glfwInit();
|
||||||
|
|
||||||
|
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
||||||
|
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
|
||||||
|
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 4);
|
||||||
|
glfwWindowHint(GLFW_RESIZABLE, resizeable);
|
||||||
|
|
||||||
|
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
|
||||||
|
|
||||||
|
window = std::shared_ptr<GLFWwindow>(glfwCreateWindow(w, h, window_name.c_str(), NULL, NULL));
|
||||||
|
|
||||||
|
// If we're not resizeable, we need to set the viewport size.
|
||||||
|
if (!resizeable) {
|
||||||
|
int fbWidth;
|
||||||
|
int fbHeight;
|
||||||
|
glfwGetFramebufferSize(window.get(), &fbWidth, &fbHeight);
|
||||||
|
glViewport(0, 0, fbWidth, fbHeight);
|
||||||
|
} else {
|
||||||
|
glfwSetFramebufferSizeCallback(window.get(), simpleengine::Game::framebuffer_resize_callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
simpleengine::Game::Game(const sf::Vector2u& window_size, const std::string& window_name) : simpleengine::Game(window_size.x, window_size.y, window_name) {
|
glfwMakeContextCurrent(window.get());
|
||||||
|
|
||||||
|
glewExperimental = GL_TRUE;
|
||||||
|
|
||||||
|
if (glewInit() != GLEW_OK) {
|
||||||
|
std::cout << "Failed to initialize glew!" << std::endl;
|
||||||
|
glfwTerminate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
simpleengine::Game::~Game() {
|
simpleengine::Game::~Game() {
|
||||||
delete window;
|
|
||||||
|
|
||||||
std::vector<Event*>::iterator it = events.begin();
|
|
||||||
while (it != events.end()) {
|
|
||||||
delete (*it);
|
|
||||||
it = events.erase(it);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void simpleengine::Game::UpdateSFMLEvents() {
|
void simpleengine::Game::update() {
|
||||||
sf::Event event;
|
|
||||||
while (window->pollEvent(event)) {
|
|
||||||
switch (event.type) {
|
|
||||||
case sf::Event::Closed:
|
|
||||||
close_event.Trigger();
|
|
||||||
window->close();
|
|
||||||
break;
|
|
||||||
case sf::Event::Resized:
|
|
||||||
resized_event.Trigger(event.size);
|
|
||||||
break;
|
|
||||||
case sf::Event::LostFocus:
|
|
||||||
lost_focus_event.Trigger();
|
|
||||||
break;
|
|
||||||
case sf::Event::GainedFocus:
|
|
||||||
gained_focus_event.Trigger();
|
|
||||||
break;
|
|
||||||
case sf::Event::TextEntered:
|
|
||||||
text_entered_event.Trigger(event.text);
|
|
||||||
break;
|
|
||||||
case sf::Event::KeyPressed:
|
|
||||||
key_pressed_event.Trigger(event.key);
|
|
||||||
break;
|
|
||||||
case sf::Event::KeyReleased:
|
|
||||||
key_released_event.Trigger(event.key);
|
|
||||||
break;
|
|
||||||
case sf::Event::MouseWheelMoved:
|
|
||||||
mouse_wheel_moved_event.Trigger(event.mouseWheel);
|
|
||||||
break;
|
|
||||||
case sf::Event::MouseWheelScrolled:
|
|
||||||
mouse_wheel_scrolled_event.Trigger(event.mouseWheelScroll);
|
|
||||||
break;
|
|
||||||
case sf::Event::MouseButtonPressed:
|
|
||||||
mouse_button_pressed_event.Trigger(event.mouseButton);
|
|
||||||
break;
|
|
||||||
case sf::Event::MouseButtonReleased:
|
|
||||||
mouse_button_released_event.Trigger(event.mouseButton);
|
|
||||||
break;
|
|
||||||
case sf::Event::MouseMoved:
|
|
||||||
mouse_move_event.Trigger(event.mouseMove);
|
|
||||||
break;
|
|
||||||
case sf::Event::MouseEntered:
|
|
||||||
mouse_entered_event.Trigger();
|
|
||||||
break;
|
|
||||||
case sf::Event::MouseLeft:
|
|
||||||
mouse_left_event.Trigger();
|
|
||||||
break;
|
|
||||||
case sf::Event::JoystickButtonPressed:
|
|
||||||
joy_btn_pressed_event.Trigger(event.joystickButton);
|
|
||||||
break;
|
|
||||||
case sf::Event::JoystickButtonReleased:
|
|
||||||
joy_btn_released_event.Trigger(event.joystickButton);
|
|
||||||
break;
|
|
||||||
case sf::Event::JoystickMoved:
|
|
||||||
joy_moved_event.Trigger(event.joystickMove);
|
|
||||||
break;
|
|
||||||
case sf::Event::JoystickConnected:
|
|
||||||
joy_connected_event.Trigger(event.joystickConnect);
|
|
||||||
break;
|
|
||||||
case sf::Event::JoystickDisconnected:
|
|
||||||
joy_disconnected_event.Trigger(event.joystickConnect);
|
|
||||||
break;
|
|
||||||
case sf::Event::TouchBegan:
|
|
||||||
touch_began_event.Trigger(event.touch);
|
|
||||||
break;
|
|
||||||
case sf::Event::TouchMoved:
|
|
||||||
touch_moved_event.Trigger(event.touch);
|
|
||||||
break;
|
|
||||||
case sf::Event::TouchEnded:
|
|
||||||
touch_ended_event.Trigger(event.touch);
|
|
||||||
break;
|
|
||||||
case sf::Event::SensorChanged:
|
|
||||||
sensor_event.Trigger(event.sensor);
|
|
||||||
break;
|
|
||||||
case sf::Event::Count:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void simpleengine::Game::Update() {
|
void simpleengine::Game::render_window() {
|
||||||
delta_time = delta_time_clock.restart().asSeconds(); // Update delta time
|
glClearColor(0.f, 0.f, 0.f, 1.f);
|
||||||
UpdateSFMLEvents();
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
|
||||||
|
render_items();
|
||||||
for (std::vector<Event*>::iterator it = events.begin(); it != events.end(); ) {
|
|
||||||
(*it)->Update(delta_time);
|
|
||||||
|
|
||||||
if ((*it)->IsDestroying()) {
|
|
||||||
delete (*it);
|
|
||||||
it = events.erase(it);
|
|
||||||
} else {
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void simpleengine::Game::RenderWindow() {
|
void simpleengine::Game::render_items() {
|
||||||
window->clear();
|
|
||||||
RenderItems();
|
|
||||||
window->display();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void simpleengine::Game::RenderItems() {
|
int simpleengine::Game::run() {
|
||||||
for (std::vector<Event*>::iterator it = events.begin(); it != events.end(); it++) {
|
while (!glfwWindowShouldClose(window.get())) {
|
||||||
(*it)->Render(window);
|
// Update input
|
||||||
}
|
glfwPollEvents();
|
||||||
}
|
|
||||||
|
|
||||||
int simpleengine::Game::Run() {
|
update();
|
||||||
sf::CircleShape shape(100);
|
|
||||||
shape.setFillColor(sf::Color::Green);
|
|
||||||
|
|
||||||
while (window->isOpen()) {
|
render_window();
|
||||||
Update();
|
|
||||||
RenderWindow();
|
// End draw
|
||||||
|
glfwSwapBuffers(window.get());
|
||||||
|
glFlush();
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void simpleengine::Game::AddEvent(simpleengine::Event *event) {
|
std::shared_ptr<GLFWwindow> simpleengine::Game::get_window() {
|
||||||
events.emplace_back(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
sf::RenderWindow* simpleengine::Game::GetWindow() {
|
|
||||||
return window;
|
return window;
|
||||||
}
|
}
|
||||||
|
|
||||||
void simpleengine::Game::ExitGame() {
|
void simpleengine::Game::exit() {
|
||||||
window->close();
|
glfwSetWindowShouldClose(window.get(), true);
|
||||||
|
glfwTerminate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void simpleengine::Game::framebuffer_resize_callback(GLFWwindow*, int fbW, int fbH) {
|
||||||
|
glViewport(0, 0, fbW, fbH);
|
||||||
}
|
}
|
Loading…
Reference in New Issue