Created Container and Window.

This commit is contained in:
pointer-to-bios 2024-08-03 04:11:49 +08:00
commit afd7dc29ad
12 changed files with 1009 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/build
/.vscode

24
CMakeLists.txt Normal file
View File

@ -0,0 +1,24 @@
cmake_minimum_required(VERSION 3.16)
project(SFTK LANGUAGES CXX)
set(CXX_STANDARD_REQUIRED 23)
include(FetchContent)
FetchContent_Declare(SFML
GIT_REPOSITORY https://github.com/SFML/SFML.git
GIT_TAG 2.6.x)
FetchContent_MakeAvailable(SFML)
set(ROOT src/)
set(CONTAINER src/containers)
set(SOURCES ${SOURCES} ${ROOT}/window.cpp)
set(SOURCES ${SOURCES} ${CONTAINER}/container.cpp)
add_library(sftk SHARED ${SOURCES})
target_link_libraries(sftk PRIVATE sfml-graphics sfml-audio sfml-network)
target_include_directories(sftk PUBLIC include/)
add_executable(demo1 demo/demo1.cpp)
target_link_libraries(demo1 PRIVATE sftk sfml-graphics sfml-audio sfml-network)
target_include_directories(demo1 PRIVATE include/)

3
README.md Normal file
View File

@ -0,0 +1,3 @@
# SFTK - SFml ToolKits
基于**sfml**的UI工具库。

9
demo/demo1.cpp Normal file
View File

@ -0,0 +1,9 @@
#include <sftk.hpp>
int main()
{
auto &window = sftk::Window::create(800, 600, L"demo1");
auto container = sftk::Container::create(vec(100.f, 100.f), vec(400.f, 400.f), window.getRoot());
container->setBackgroudColor(sf::Color::White);
window.exec();
}

View File

@ -0,0 +1,123 @@
#ifndef CONTAINER_HPP
#define CONTAINER_HPP
#include <vector>
#include <memory>
#include <functional>
#include <exceptions.hpp>
#include <event.hpp>
#include <SFML/Graphics.hpp>
namespace sftk
{
class Container;
typedef std::shared_ptr<Container> pContainer;
bool operator==(pContainer &shp, Container *rawp);
class Container
{
public: // Public base functions
static pContainer create(pContainer parent = nullptr);
static pContainer create(sf::Vector2f pos, sf::Vector2f size, pContainer parent = nullptr);
void setParent(pContainer parent);
void setPosition(sf::Vector2f pos = sf::Vector2f(0, 0));
void setSize(sf::Vector2f pos);
void setBackgroudColor(sf::Color color);
pContainer getParent() const;
const sf::Vector2f &getPosition() const;
const sf::Vector2f &getSize() const;
sf::FloatRect getGlobalBouds() const;
virtual bool isRoot() const;
virtual bool hasInternalChange() const;
virtual void update() const;
public: // Event hadler register functions
template <class T>
void registerEvent(EventType type, std::function<T> handler);
public: // Static and operators
static pContainer getRoot(Container *p = nullptr);
friend bool sftk::operator==(pContainer &shp, Container *rawp);
protected: // Must be overrided by child classes
Container(pContainer parent = nullptr);
Container(sf::Vector2f pos, sf::Vector2f size, pContainer parent = nullptr);
Container(sf::Vector2f pos, sf::Vector2f size, bool root);
virtual sf::RenderWindow &windowSFWindow();
// The events bases on sfml's Event,
// so the Window class must use this function to
// pass event to the root container
// (itself's parent class)'s dispatchEvent(Event&)
// to process it as Event.
void dispatchEvent(sf::Event &event);
private: // For this class to use
sf::RenderWindow &getSfWindow();
// When this container did deal with the passed event
// it returns true. On the oppsite case it returns false.
bool dispatchEvent(Event &event);
pContainer parent;
std::vector<pContainer> childs;
sf::Vector2f basepos;
sf::Vector2f pos, size;
sf::View view;
sf::Color backgroudColor;
static const int64_t maxClickTime = 300;
private: // Called on construction
static void rootTest(Container *self);
inline static int64_t currentTime();
private: // Event handlers
std::function<void()> closed;
std::function<void(sf::Vector2f)> resized;
std::function<void(sf::Vector2f, sf::Mouse::Button)> mousePressed;
std::function<void(sf::Vector2f, sf::Mouse::Button)> mouseReleased;
std::function<void(sf::Vector2f, sf::Mouse::Button)> mouseClicked;
std::function<void(sf::Vector2f)> mouseEntered;
std::function<void(sf::Vector2f)> mouseLeft;
std::function<void(sf::Vector2f)> mouseMoved;
std::function<void(sf::Vector2f, sf::Vector2f, sf::Mouse::Button)> mouseDragged;
std::function<void(float)> mouseScrolled;
std::function<void(wchar_t)> textInputed;
private: // Second events asistant vars
int64_t lastMousePressTime[sf::Mouse::Button::ButtonCount];
sf::Vector2f lastMousePressPos[sf::Mouse::Button::ButtonCount];
bool focused;
bool mouseLocated;
public: // Exceptions
class RootContainerNotPresent : public SFTKException<Container>
{
public:
RootContainerNotPresent(Container *container);
const char *what() const noexcept override;
};
class NotAWindowObj : public SFTKException<Container>
{
public:
NotAWindowObj(Container *container);
const char *what() const noexcept override;
};
};
};
#endif

81
include/event.hpp Normal file
View File

@ -0,0 +1,81 @@
#ifndef EVENT_HPP
#define EVENT_HPP
#include <SFML/Graphics.hpp>
namespace sftk
{
enum EventType
{
Closed,
Resized,
MousePressed,
MouseReleased,
MouseClicked,
MouseEntered,
MouseLeft,
MouseMoved,
MouseDragged,
MouseScrolled,
TextInputed,
EventTypeLength,
};
struct _Resized
{
sf::Vector2f size; // The parent Container's new size.
};
struct _MouseEnteredLeftMoved
{
sf::Vector2f pos;
};
typedef _MouseEnteredLeftMoved MousePosition;
struct _MousePressedReleasedClicked
{
sf::Vector2f pos;
sf::Mouse::Button button;
};
struct _MouseDragged
{
sf::Vector2f start;
sf::Vector2f now;
sf::Mouse::Button button;
};
struct _MouseScrolled
{
float delta;
};
struct _TextInputed
{
wchar_t uchar;
};
union EventData
{
_Resized resized;
_MousePressedReleasedClicked mousePressed;
_MousePressedReleasedClicked mouseReleased;
_MousePressedReleasedClicked mouseClicked;
_MouseEnteredLeftMoved mouseEntered;
_MouseEnteredLeftMoved mouseLeft;
_MouseEnteredLeftMoved mouseMoved;
_MouseDragged mouseDragged;
_MouseScrolled mouseScrolled;
_TextInputed textInputed;
};
struct Event
{
EventType type;
EventData data;
};
};
#endif

63
include/exceptions.hpp Normal file
View File

@ -0,0 +1,63 @@
#ifndef EXCEPTIONS_HPP
#define EXCEPTIONS_HPP
#include <exception>
#include <sstream>
#include <iomanip>
namespace sftk
{
/**
* @brief # SFTK exceptions super class
*
* @tparam T - The SFTK object types
*/
template <class T>
class SFTKException : public std::exception
{
public:
SFTKException(T *obj) : sftkObj(obj) {}
/**
* @brief # Address formatter
*
* This formatter function use 16 `*`s as address placeholder.
*
* When throwing an exception, Exceptions can use placeholder with this function
* to fill the `what()` message. So the reported exception can tell where the crashed
* SFTK object at.
*
* # Example
*
* ```c
* const char *SomeException::what() const noexcept
* {
* static char msg[] = "sftk::SomeClass 0x********_******** reported an exception.";
* SFTKException::formatAddress(msg, sizeof(msg));
* return msg;
* }
* ```
*
* @param msg exception message
* @param len message length
*/
void formatAddress(char *msg, int len) const
{
std::stringstream ss;
ss << std::hex << std::setw(16) << std::setfill('0') << reinterpret_cast<uintptr_t>(sftkObj);
auto it = ss.str().begin();
for (int i = 0; i < len; i++)
{
if (msg[i] == '*')
{
msg[i] = *it;
++it;
}
}
}
private:
T *sftkObj;
};
};
#endif

24
include/gtypes.hpp Normal file
View File

@ -0,0 +1,24 @@
#ifndef GTYPES_HPP
#define GTYPES_HPP
#include <SFML/Graphics.hpp>
template <class T>
constexpr sf::Vector2<T> vec(T x, T y)
{
return sf::Vector2(x, y);
}
template <class T>
constexpr sf::Vector3<T> vec(T x, T y, T z)
{
return sf::Vector3(x, y, z);
}
template <class T>
constexpr sf::Rect<T> vec(T x, T y, T width, T height)
{
return sf::Rect(x, y, width, height);
}
#endif

10
include/sftk.hpp Normal file
View File

@ -0,0 +1,10 @@
#ifndef SFTK_HPP
#define SFTK_HPP
#include <gtypes.hpp>
#include <window.hpp>
#include <containers/container.hpp>
#endif

59
include/window.hpp Normal file
View File

@ -0,0 +1,59 @@
#ifndef WINDOW_HPP
#define WINDOW_HPP
#include <memory>
#include <string>
#include <vector>
#include <gtypes.hpp>
#include <exceptions.hpp>
#include <containers/container.hpp>
namespace sftk
{
class Window;
typedef std::shared_ptr<Window> pWindow;
/**
* @brief Window type
*
* To create a unique SFML window in the current thread.
*/
class Window : public Container
{
public:
static Window &create(int width, int height, std::wstring name = L"");
void exec();
void setSize(sf::Vector2u size);
bool isRoot() const;
sf::Vector2f &getPosition() const;
protected:
Window(int width, int height, std::wstring name = L"");
private:
sf::RenderWindow &windowSFWindow();
sf::RenderWindow sfwindow;
std::unique_ptr<bool> running;
private:
std::unique_ptr<std::vector<sf::Vector2f>> v2f_dumpst;
public:
class WindowMultipleConstructed : public SFTKException<Window>
{
public:
WindowMultipleConstructed(Window *);
const char *what() const noexcept override;
};
};
};
#endif

View File

@ -0,0 +1,524 @@
#include <containers/container.hpp>
#include <gtypes.hpp>
#include <window.hpp>
#include <chrono>
using namespace sftk;
pContainer Container::getRoot(Container *p)
{
static thread_local pContainer root;
if (p)
root.reset(p);
return root;
}
void Container::rootTest(Container *self)
{
if (!getRoot().get())
throw RootContainerNotPresent(self);
}
Container::Container(pContainer parent) : parent(nullptr)
{
if (!parent.get())
{
Container::rootTest(this);
this->parent = getRoot();
}
else
{
this->parent = parent;
}
this->parent->childs.push_back(pContainer(this));
}
Container::Container(sf::Vector2f pos, sf::Vector2f size, pContainer parent)
: parent(nullptr),
pos(pos),
size(size),
view(vec(0.f, 0.f, size.x, size.y)),
basepos(parent->basepos + parent->pos),
backgroudColor(sf::Color::Black)
{
if (!parent.get())
{
Container::rootTest(this);
this->parent = getRoot();
}
else
{
this->parent = parent;
}
this->parent->childs.push_back(pContainer(this));
auto rootsize = getRoot()->getSize();
view.setViewport(vec(
(parent->basepos.x + pos.x) / rootsize.x, (parent->basepos.y + pos.y) / rootsize.y,
size.x / rootsize.x, size.y / rootsize.y));
}
Container::Container(sf::Vector2f pos, sf::Vector2f size, bool root)
: parent(nullptr),
pos(pos),
size(size),
view(vec(0.f, 0.f, size.x, size.y)),
basepos(vec(0.f, 0.f)),
backgroudColor(sf::Color::Black)
{
view.setViewport(vec(0.f, 0.f, 1.f, 1.f));
}
pContainer Container::create(pContainer parent)
{
return pContainer(new Container(parent));
}
pContainer Container::create(sf::Vector2f pos, sf::Vector2f size, pContainer parent)
{
return pContainer(new Container(pos, size, parent));
}
void Container::setParent(pContainer parent)
{
auto it = std::find(this->parent->childs.begin(), this->parent->childs.end(), this);
this->parent->childs.erase(it);
this->parent = parent;
this->parent->childs.push_back(pContainer(this));
}
void Container::setPosition(sf::Vector2f pos)
{
this->pos = pos;
auto rootsize = getRoot()->getSize();
view.setViewport(vec(
(parent->pos.x + pos.x) / rootsize.x, (parent->pos.y + pos.y) / rootsize.y,
size.x / rootsize.x, size.y / rootsize.y));
}
void Container::setSize(sf::Vector2f size)
{
this->size = size;
auto rootsize = getRoot()->getSize();
view.setViewport(vec(
(basepos.x + pos.x) / rootsize.x, (basepos.y + pos.y) / rootsize.y,
size.x / rootsize.x, size.y / rootsize.y));
}
void Container::setBackgroudColor(sf::Color color)
{
backgroudColor = color;
}
pContainer Container::getParent() const
{
return parent;
}
const sf::Vector2f &Container::getPosition() const
{
return pos;
}
const sf::Vector2f &Container::getSize() const
{
return size;
}
sf::FloatRect Container::getGlobalBouds() const
{
return vec(basepos.x + pos.x, basepos.y + pos.y, size.x, size.y);
}
bool Container::isRoot() const
{
return false;
}
bool Container::hasInternalChange() const
{
return false;
}
void Container::update() const
{
auto &window = getRoot()->windowSFWindow();
window.setView(view);
sf::RectangleShape bg(vec(size.x, size.y));
bg.setPosition(vec(0.f, 0.f));
bg.setFillColor(backgroudColor);
window.draw(bg);
for (auto c : childs)
{
c->update();
}
}
template <>
void Container::registerEvent(EventType type, std::function<void()> handler)
{
if (type != EventType::Closed)
return;
closed = handler;
}
template <>
void Container::registerEvent(EventType type, std::function<void(sf::Vector2f)> handler)
{
switch (type)
{
case EventType::Resized:
resized = handler;
break;
case EventType::MouseEntered:
mouseEntered = handler;
break;
case EventType::MouseLeft:
mouseLeft = handler;
break;
case EventType::MouseMoved:
mouseMoved = handler;
break;
}
}
template <>
void Container::registerEvent(EventType type, std::function<void(sf::Vector2f, sf::Mouse::Button)> handler)
{
switch (type)
{
case EventType::MousePressed:
mousePressed = handler;
break;
case EventType::MouseReleased:
mouseReleased = handler;
break;
case EventType::MouseClicked:
mouseClicked = handler;
break;
}
}
template <>
void Container::registerEvent(EventType type, std::function<void(sf::Vector2f, sf::Vector2f, sf::Mouse::Button)> handler)
{
if (type != EventType::MouseDragged)
return;
mouseDragged = handler;
}
template <>
void Container::registerEvent(EventType type, std::function<void(float)> handler)
{
if (type != EventType::MouseScrolled)
return;
mouseScrolled = handler;
}
template <>
void Container::registerEvent(EventType type, std::function<void(wchar_t)> handler)
{
if (type != EventType::TextInputed)
return;
textInputed = handler;
}
void Container::dispatchEvent(sf::Event &event)
{
Event eve{};
switch (event.type)
{
case sf::Event::Closed:
eve = Event{.type = EventType::Closed};
break;
case sf::Event::Resized:
eve = Event{
.type = EventType::Resized,
.data = EventData{
.resized = _Resized{
.size = vec((float)event.size.width, (float)event.size.height)}}};
break;
case sf::Event::MouseEntered:
eve = Event{
.type = EventType::MouseEntered,
.data = EventData{
.mouseEntered = _MouseEnteredLeftMoved{
.pos = vec((float)event.mouseMove.x, (float)event.mouseMove.y)}}};
break;
case sf::Event::MouseLeft:
eve = Event{
.type = EventType::MouseLeft,
.data = EventData{
.mouseLeft = _MouseEnteredLeftMoved{
.pos = vec((float)event.mouseMove.x, (float)event.mouseMove.y)}}};
break;
case sf::Event::MouseButtonPressed:
eve = Event{
.type = EventType::MousePressed,
.data = EventData{
.mousePressed = _MousePressedReleasedClicked{
.pos = vec((float)event.mouseButton.x, (float)event.mouseButton.y),
.button = event.mouseButton.button}}};
break;
case sf::Event::MouseButtonReleased:
eve = Event{
.type = EventType::MouseReleased,
.data = EventData{
.mouseReleased = _MousePressedReleasedClicked{
.pos = vec((float)event.mouseButton.x, (float)event.mouseButton.y),
.button = event.mouseButton.button}}};
break;
case sf::Event::MouseMoved:
eve = Event{
.type = EventType::MouseMoved,
.data = EventData{
.mouseMoved = MousePosition{
.pos = vec((float)event.mouseMove.x, (float)event.mouseMove.y)}}};
break;
case sf::Event::MouseWheelScrolled:
if (event.mouseWheelScroll.wheel != sf::Mouse::VerticalWheel)
return;
eve = Event{
.type = EventType::MouseScrolled,
.data = EventData{
.mouseScrolled = _MouseScrolled{
.delta = event.mouseWheelScroll.delta,
}}};
break;
case sf::Event::TextEntered:
eve = Event{
.type = EventType::TextInputed,
.data = EventData{
.textInputed = _TextInputed{
.uchar = static_cast<wchar_t>(event.text.unicode)}}};
break;
default:
return;
}
dispatchEvent(eve);
}
bool Container::dispatchEvent(Event &event)
{
bool spread = true; // If this event will pass to children containers.
bool broadcast = false; // If this event will send to all children containers.
bool handler_effective = false; // The return value, if this container processed this event.
_Resized rsz;
_MousePressedReleasedClicked mpr;
_MousePressedReleasedClicked mrel;
_MousePressedReleasedClicked mcli;
_MouseEnteredLeftMoved mmov;
_MouseDragged mdra;
switch (event.type)
{
case EventType::Closed:
broadcast = true;
if (closed)
{
handler_effective = true;
closed();
}
break;
case EventType::Resized:
rsz = event.data.resized;
if (resized)
{
handler_effective = true;
resized(rsz.size);
}
if (isRoot())
{
setSize(rsz.size);
}
else
{
setPosition(pos);
setSize(size);
}
break;
case EventType::MousePressed:
mpr = event.data.mousePressed;
if (mousePressed && getGlobalBouds().contains(mpr.pos))
{
handler_effective = true;
mousePressed(mpr.pos - basepos - pos, mpr.button);
}
lastMousePressTime[mpr.button] = currentTime();
break;
case EventType::MouseReleased:
mrel = event.data.mouseReleased;
lastMousePressTime[mrel.button] = -1;
lastMousePressPos[mrel.button] = vec(-1.f, -1.f);
if (mouseReleased && getGlobalBouds().contains(mrel.pos))
{
handler_effective = true;
mouseReleased(mrel.pos - basepos - pos, mrel.button);
}
if (isRoot() &&
lastMousePressTime[mrel.button] >= 0 &&
lastMousePressTime[mrel.button] < maxClickTime)
{
auto tmp = Event{
.type = EventType::MouseClicked,
.data = EventData{
.mouseClicked = mrel}};
dispatchEvent(tmp);
}
break;
case EventType::MouseClicked:
mcli = event.data.mouseClicked;
if (mouseClicked && mouseLocated)
{
handler_effective = true;
mouseClicked(mcli.pos - basepos - pos, mcli.button);
}
if (mouseLocated)
{
focused = true;
}
else
{
focused = false;
}
break;
case EventType::MouseEntered:
mouseLocated = true;
if (mouseEntered)
{
handler_effective = true;
mouseEntered(event.data.mouseEntered.pos - basepos - pos);
}
break;
case EventType::MouseLeft:
mouseLocated = false;
if (mouseLeft)
{
handler_effective = true;
mouseLeft(event.data.mouseLeft.pos - basepos - pos);
}
break;
case EventType::MouseMoved:
mmov = event.data.mouseMoved;
if (getGlobalBouds().contains(mmov.pos))
{
if (mouseMoved)
{
handler_effective = true;
mouseMoved(mmov.pos - basepos - pos);
}
if (isRoot() && !mouseLocated)
{
auto tmp = Event{
.type = EventType::MouseEntered,
.data = event.data};
dispatchEvent(tmp);
}
}
else if (isRoot() && mouseLocated)
{
auto tmp = Event{
.type = EventType::MouseLeft,
.data = event.data};
dispatchEvent(tmp);
}
if (!isRoot())
break;
for (auto i = 0; i < sf::Mouse::Button::ButtonCount; i++)
{
if (lastMousePressPos[i] == vec(-1.f, -1.f))
continue;
auto tmp = Event{
.type = EventType::MouseDragged,
.data = {
.mouseDragged = _MouseDragged{
.start = lastMousePressPos[i],
.now = mmov.pos,
.button = (sf::Mouse::Button)i}}};
dispatchEvent(tmp);
}
break;
case EventType::MouseDragged:
mdra = event.data.mouseDragged;
if (mouseDragged)
{
handler_effective = true;
mouseDragged(mdra.start, mdra.now, mdra.button);
}
break;
case EventType::MouseScrolled:
if (mouseScrolled)
{
handler_effective = true;
mouseScrolled(event.data.mouseScrolled.delta);
}
break;
case EventType::TextInputed:
if (textInputed)
{
handler_effective = true;
textInputed(event.data.textInputed.uchar);
}
break;
}
if (!spread)
return handler_effective;
bool child_handled;
bool handler_handled = false;
for (auto container : childs)
{
if ((child_handled = container->dispatchEvent(event)) && !broadcast)
{
if (child_handled)
handler_handled = true;
break;
}
}
return handler_handled;
}
sf::RenderWindow &Container::windowSFWindow()
{
throw NotAWindowObj(const_cast<Container *>(this));
}
sf::RenderWindow &Container::getSfWindow()
{
return getRoot()->windowSFWindow();
}
bool sftk::operator==(pContainer &shp, Container *rawp)
{
return shp.get() == rawp;
}
int64_t Container::currentTime()
{
return std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch())
.count();
}
Container::RootContainerNotPresent::RootContainerNotPresent(Container *container)
: SFTKException(container) {}
const char *Container::RootContainerNotPresent::what() const noexcept
{
static char msg[] = "sftk::Container 0x********_********: Root container not present.";
SFTKException::formatAddress(msg, sizeof(msg));
return msg;
}
Container::NotAWindowObj::NotAWindowObj(Container *container)
: SFTKException(container) {}
const char *Container::NotAWindowObj::what() const noexcept
{
static char msg[] = "sftk::Container 0x********_********: Not a sftk::Window.";
SFTKException::formatAddress(msg, sizeof(msg));
return msg;
}

86
src/window.cpp Normal file
View File

@ -0,0 +1,86 @@
#include <window.hpp>
#include <sstream>
#include <iomanip>
#include <gtypes.hpp>
#include <containers/container.hpp>
using namespace sftk;
Window &Window::create(int width, int height, std::wstring name)
{
return *new Window(width, height, name);
}
Window::Window(int width, int height, std::wstring name)
: Container(vec(0.f, 0.f), vec((float)width, (float)height), true),
sfwindow(sf::VideoMode(width, height), name),
running(new bool(true))
{
if (Container::getRoot().get())
throw WindowMultipleConstructed(this);
Container::getRoot(this);
}
void Window::exec()
{
while (*running)
{
sf::Event event;
bool event_occured = false;
while (sfwindow.pollEvent(event))
{
event_occured = true;
dispatchEvent(event);
switch (event.type)
{
case sf::Event::Closed:
*running = false;
sfwindow.close();
break;
default:
break;
}
}
if (event_occured || hasInternalChange())
{
update();
sfwindow.display();
}
}
}
void Window::setSize(sf::Vector2u size)
{
sfwindow.setSize(size);
Container::setSize(vec((float)size.x, (float)size.y));
}
bool Window::isRoot() const
{
return true;
}
sf::Vector2f &Window::getPosition() const
{
auto _pos = sfwindow.getPosition();
auto pos = vec((float)_pos.x, (float)_pos.y);
v2f_dumpst->push_back(pos);
return v2f_dumpst->back();
}
sf::RenderWindow &Window::windowSFWindow()
{
return sfwindow;
}
Window::WindowMultipleConstructed::WindowMultipleConstructed(Window *window)
: SFTKException(window) {}
const char *Window::WindowMultipleConstructed::what() const noexcept
{
static char msg[] = "sftk::Window 0x********_********: Multiply constructed in one thread.";
SFTKException::formatAddress(msg, sizeof(msg));
return msg;
}