mirror of
https://github.com/hyprwm/hyprutils.git
synced 2025-12-20 12:50:10 +01:00
Compare commits
22 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ac060bfcf | ||
| 1c527b30fe | |||
| fe686486ac | |||
| 2f2413801b | |||
|
|
9f8e158dbd | ||
|
|
7e6346f84b | ||
|
|
a64517c236 | ||
| 0168583075 | |||
| 5e1a14bc29 | |||
| bc9803c4b8 | |||
|
|
44c2ba0354 | ||
| 96df6f6535 | |||
| a9fe9748ae | |||
|
|
b311dc90dc | ||
|
|
16a7fe760d | ||
| 31f29957df | |||
| 1454845751 | |||
|
|
2698ac1194 | ||
| 671792bcfe | |||
| 0c6411851c | |||
| e3cae692f6 | |||
|
|
cb3e797fde |
53 changed files with 2594 additions and 1365 deletions
|
|
@ -33,9 +33,11 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE)
|
||||||
if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG)
|
if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG)
|
||||||
message(STATUS "Configuring hyprutils in Debug")
|
message(STATUS "Configuring hyprutils in Debug")
|
||||||
add_compile_definitions(HYPRLAND_DEBUG)
|
add_compile_definitions(HYPRLAND_DEBUG)
|
||||||
|
set(BUILD_TESTING ON)
|
||||||
else()
|
else()
|
||||||
add_compile_options(-O3)
|
add_compile_options(-O3)
|
||||||
message(STATUS "Configuring hyprutils in Release")
|
message(STATUS "Configuring hyprutils in Release")
|
||||||
|
set(BUILD_TESTING OFF)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp" "include/*.hpp")
|
file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp" "include/*.hpp")
|
||||||
|
|
@ -50,24 +52,30 @@ target_include_directories(
|
||||||
PUBLIC "./include"
|
PUBLIC "./include"
|
||||||
PRIVATE "./src")
|
PRIVATE "./src")
|
||||||
set_target_properties(hyprutils PROPERTIES VERSION ${hyprutils_VERSION}
|
set_target_properties(hyprutils PROPERTIES VERSION ${hyprutils_VERSION}
|
||||||
SOVERSION 9)
|
SOVERSION 10)
|
||||||
target_link_libraries(hyprutils PkgConfig::deps)
|
target_link_libraries(hyprutils PkgConfig::deps)
|
||||||
|
|
||||||
if(BUILD_TESTING)
|
if(BUILD_TESTING)
|
||||||
# GTest
|
# GTest
|
||||||
find_package(GTest CONFIG REQUIRED)
|
find_package(GTest CONFIG REQUIRED)
|
||||||
include(GoogleTest)
|
include(GoogleTest)
|
||||||
add_executable(hyprutils_inline_tests ${SRCFILES})
|
file(GLOB_RECURSE TESTFILES CONFIGURE_DEPENDS "tests/*.cpp")
|
||||||
target_compile_definitions(hyprutils_inline_tests PRIVATE HU_UNIT_TESTS=1)
|
add_executable(hyprutils_tests ${TESTFILES})
|
||||||
target_compile_options(hyprutils_inline_tests PRIVATE --coverage)
|
|
||||||
target_link_options(hyprutils_inline_tests PRIVATE --coverage)
|
target_compile_options(hyprutils_tests PRIVATE --coverage)
|
||||||
|
target_link_options(hyprutils_tests PRIVATE --coverage)
|
||||||
|
|
||||||
target_include_directories(
|
target_include_directories(
|
||||||
hyprutils_inline_tests
|
hyprutils_tests
|
||||||
PUBLIC "./include"
|
PUBLIC "./include"
|
||||||
PRIVATE "./src" "./src/include" "./protocols" "${CMAKE_BINARY_DIR}")
|
PRIVATE "./src" "./src/include" "./protocols" "${CMAKE_BINARY_DIR}")
|
||||||
target_link_libraries(hyprutils_inline_tests PRIVATE GTest::gtest_main
|
target_link_libraries(hyprutils_tests PRIVATE hyprutils GTest::gtest_main
|
||||||
PkgConfig::deps)
|
PkgConfig::deps)
|
||||||
gtest_discover_tests(hyprutils_inline_tests)
|
gtest_discover_tests(hyprutils_tests)
|
||||||
|
|
||||||
|
# Add coverage to hyprutils for test builds
|
||||||
|
target_compile_options(hyprutils PRIVATE --coverage)
|
||||||
|
target_link_options(hyprutils PRIVATE --coverage)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
|
|
|
||||||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
||||||
0.10.2
|
0.11.0
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ namespace Hyprutils {
|
||||||
};
|
};
|
||||||
|
|
||||||
void create(CAnimationManager*, int, Memory::CSharedPointer<CBaseAnimatedVariable>);
|
void create(CAnimationManager*, int, Memory::CSharedPointer<CBaseAnimatedVariable>);
|
||||||
|
void create2(CAnimationManager*, int, Memory::CWeakPointer<CBaseAnimatedVariable>);
|
||||||
void connectToActive();
|
void connectToActive();
|
||||||
void disconnectFromActive();
|
void disconnectFromActive();
|
||||||
|
|
||||||
|
|
@ -136,6 +137,7 @@ namespace Hyprutils {
|
||||||
public:
|
public:
|
||||||
CGenericAnimatedVariable() = default;
|
CGenericAnimatedVariable() = default;
|
||||||
|
|
||||||
|
/* Deprecated: use create2 */
|
||||||
void create(const int typeInfo, CAnimationManager* pAnimationManager, Memory::CSharedPointer<CGenericAnimatedVariable<VarType, AnimationContext>> pSelf,
|
void create(const int typeInfo, CAnimationManager* pAnimationManager, Memory::CSharedPointer<CGenericAnimatedVariable<VarType, AnimationContext>> pSelf,
|
||||||
const VarType& initialValue) {
|
const VarType& initialValue) {
|
||||||
m_Begun = initialValue;
|
m_Begun = initialValue;
|
||||||
|
|
@ -145,6 +147,16 @@ namespace Hyprutils {
|
||||||
CBaseAnimatedVariable::create(pAnimationManager, typeInfo, pSelf);
|
CBaseAnimatedVariable::create(pAnimationManager, typeInfo, pSelf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Equivalent to create, except that it allows animated variables to be UP's */
|
||||||
|
void create2(const int typeInfo, CAnimationManager* pAnimationManager, Memory::CWeakPointer<CGenericAnimatedVariable<VarType, AnimationContext>> pSelf,
|
||||||
|
const VarType& initialValue) {
|
||||||
|
m_Begun = initialValue;
|
||||||
|
m_Value = initialValue;
|
||||||
|
m_Goal = initialValue;
|
||||||
|
|
||||||
|
CBaseAnimatedVariable::create2(pAnimationManager, typeInfo, pSelf);
|
||||||
|
}
|
||||||
|
|
||||||
CGenericAnimatedVariable(const CGenericAnimatedVariable&) = delete;
|
CGenericAnimatedVariable(const CGenericAnimatedVariable&) = delete;
|
||||||
CGenericAnimatedVariable(CGenericAnimatedVariable&&) = delete;
|
CGenericAnimatedVariable(CGenericAnimatedVariable&&) = delete;
|
||||||
CGenericAnimatedVariable& operator=(const CGenericAnimatedVariable&) = delete;
|
CGenericAnimatedVariable& operator=(const CGenericAnimatedVariable&) = delete;
|
||||||
|
|
|
||||||
36
include/hyprutils/cli/ArgumentParser.hpp
Normal file
36
include/hyprutils/cli/ArgumentParser.hpp
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <span>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <optional>
|
||||||
|
#include <expected>
|
||||||
|
#include "../memory/UniquePtr.hpp"
|
||||||
|
|
||||||
|
namespace Hyprutils::CLI {
|
||||||
|
class CArgumentParserImpl;
|
||||||
|
|
||||||
|
class CArgumentParser {
|
||||||
|
public:
|
||||||
|
CArgumentParser(const std::span<const char*>& args);
|
||||||
|
~CArgumentParser() = default;
|
||||||
|
|
||||||
|
std::expected<void, std::string> registerBoolOption(const std::string_view& name, const std::string_view& abbrev, const std::string_view& description);
|
||||||
|
std::expected<void, std::string> registerIntOption(const std::string_view& name, const std::string_view& abbrev, const std::string_view& description);
|
||||||
|
std::expected<void, std::string> registerFloatOption(const std::string_view& name, const std::string_view& abbrev, const std::string_view& description);
|
||||||
|
std::expected<void, std::string> registerStringOption(const std::string_view& name, const std::string_view& abbrev, const std::string_view& description);
|
||||||
|
|
||||||
|
std::optional<bool> getBool(const std::string_view& name);
|
||||||
|
std::optional<int> getInt(const std::string_view& name);
|
||||||
|
std::optional<float> getFloat(const std::string_view& name);
|
||||||
|
std::optional<std::string_view> getString(const std::string_view& name);
|
||||||
|
|
||||||
|
// commence the parsing after registering
|
||||||
|
std::expected<void, std::string> parse();
|
||||||
|
|
||||||
|
std::string getDescription(const std::string_view& header, std::optional<size_t> maxWidth = {});
|
||||||
|
|
||||||
|
private:
|
||||||
|
Memory::CUniquePointer<CArgumentParserImpl> m_impl;
|
||||||
|
};
|
||||||
|
};
|
||||||
116
include/hyprutils/cli/Logger.hpp
Normal file
116
include/hyprutils/cli/Logger.hpp
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <format>
|
||||||
|
#include <expected>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
#include "../memory/UniquePtr.hpp"
|
||||||
|
#include "../memory/WeakPtr.hpp"
|
||||||
|
|
||||||
|
namespace Hyprutils::CLI {
|
||||||
|
class CLoggerImpl;
|
||||||
|
|
||||||
|
enum eLogLevel : uint8_t {
|
||||||
|
LOG_TRACE = 0,
|
||||||
|
LOG_DEBUG,
|
||||||
|
LOG_WARN,
|
||||||
|
LOG_ERR,
|
||||||
|
LOG_CRIT,
|
||||||
|
};
|
||||||
|
|
||||||
|
// CLogger is a thread-safe, general purpose logger.
|
||||||
|
// the logger's stdout is enabled by default.
|
||||||
|
// color is enabled by default, it's only for stdout.
|
||||||
|
// everything else is disabled.
|
||||||
|
class CLogger {
|
||||||
|
public:
|
||||||
|
CLogger();
|
||||||
|
~CLogger();
|
||||||
|
|
||||||
|
CLogger(const CLogger&) = delete;
|
||||||
|
CLogger(CLogger&) = delete;
|
||||||
|
CLogger(CLogger&&) = delete;
|
||||||
|
|
||||||
|
void setLogLevel(eLogLevel level);
|
||||||
|
void setTime(bool enabled);
|
||||||
|
void setEnableStdout(bool enabled);
|
||||||
|
void setEnableColor(bool enabled);
|
||||||
|
void setEnableRolling(bool enabled);
|
||||||
|
std::expected<void, std::string> setOutputFile(const std::string_view& file);
|
||||||
|
const std::string& rollingLog();
|
||||||
|
|
||||||
|
void log(eLogLevel level, const std::string_view& msg);
|
||||||
|
|
||||||
|
template <typename... Args>
|
||||||
|
// NOLINTNEXTLINE
|
||||||
|
void log(eLogLevel level, std::format_string<Args...> fmt, Args&&... args) {
|
||||||
|
if (!m_shouldLogAtAll)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (level < m_logLevel)
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::string logMsg = std::vformat(fmt.get(), std::make_format_args(args...));
|
||||||
|
log(level, logMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Memory::CUniquePointer<CLoggerImpl> m_impl;
|
||||||
|
|
||||||
|
// this has to be here as part of important optimization of trace logs
|
||||||
|
eLogLevel m_logLevel = LOG_DEBUG;
|
||||||
|
|
||||||
|
// this has to be here as part of important optimization of disabled logging
|
||||||
|
bool m_shouldLogAtAll = false;
|
||||||
|
|
||||||
|
friend class CLoggerImpl;
|
||||||
|
friend class CLoggerConnection;
|
||||||
|
};
|
||||||
|
|
||||||
|
// CLoggerConnection is a "handle" to a logger, that can be created from a logger and
|
||||||
|
// allows to send messages to a logger via a proxy
|
||||||
|
// this does not allow for any changes to the logger itself, only sending logs.
|
||||||
|
// Logger connections keep their own logLevel. They inherit it at creation, but can be changed
|
||||||
|
class CLoggerConnection {
|
||||||
|
public:
|
||||||
|
CLoggerConnection(CLogger& logger);
|
||||||
|
~CLoggerConnection();
|
||||||
|
|
||||||
|
CLoggerConnection(const CLoggerConnection&) = delete;
|
||||||
|
CLoggerConnection(CLoggerConnection&) = delete;
|
||||||
|
|
||||||
|
// Allow move
|
||||||
|
CLoggerConnection(CLoggerConnection&&) = default;
|
||||||
|
|
||||||
|
void setName(const std::string_view& name);
|
||||||
|
void setLogLevel(eLogLevel level);
|
||||||
|
|
||||||
|
void log(eLogLevel level, const std::string_view& msg);
|
||||||
|
|
||||||
|
CLogger* getLogger();
|
||||||
|
void redirect(CLogger& logger);
|
||||||
|
|
||||||
|
template <typename... Args>
|
||||||
|
// NOLINTNEXTLINE
|
||||||
|
void log(eLogLevel level, std::format_string<Args...> fmt, Args&&... args) {
|
||||||
|
if (!m_impl || !m_logger)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!m_logger->m_shouldLogAtAll)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (level < m_logLevel)
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::string logMsg = std::vformat(fmt.get(), std::make_format_args(args...));
|
||||||
|
log(level, logMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Memory::CWeakPointer<CLoggerImpl> m_impl;
|
||||||
|
CLogger* m_logger = nullptr;
|
||||||
|
eLogLevel m_logLevel = LOG_DEBUG;
|
||||||
|
|
||||||
|
std::string m_name = "";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
@ -54,11 +54,11 @@ namespace Hyprutils::Memory {
|
||||||
using validHierarchy = std::enable_if_t<std::is_assignable_v<CAtomicSharedPointer<T>&, X>, CAtomicSharedPointer&>;
|
using validHierarchy = std::enable_if_t<std::is_assignable_v<CAtomicSharedPointer<T>&, X>, CAtomicSharedPointer&>;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit CAtomicSharedPointer(T* object) noexcept : m_ptr(new Atomic_::impl(sc<void*>(object), _delete)) {
|
explicit CAtomicSharedPointer(T* object) noexcept : m_ptr(new Atomic_::impl(sc<void*>(object), _delete), sc<void*>(object)) {
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
CAtomicSharedPointer(Impl_::impl_base* impl) noexcept : m_ptr(impl) {
|
CAtomicSharedPointer(Impl_::impl_base* impl, void* data) noexcept : m_ptr(impl, data) {
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -219,13 +219,17 @@ namespace Hyprutils::Memory {
|
||||||
return m_ptr.impl_ ? m_ptr.impl_->ref() : 0;
|
return m_ptr.impl_ ? m_ptr.impl_->ref() : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Atomic_::impl* impl() const {
|
||||||
|
return sc<Atomic_::impl*>(m_ptr.impl_);
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void _delete(void* p) {
|
static void _delete(void* p) {
|
||||||
std::default_delete<T>{}(sc<T*>(p));
|
std::default_delete<T>{}(sc<T*>(p));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::lock_guard<std::recursive_mutex> implLockGuard() const {
|
std::lock_guard<std::recursive_mutex> implLockGuard() const {
|
||||||
return sc<Atomic_::impl*>(m_ptr.impl_)->lockGuard();
|
return impl()->lockGuard();
|
||||||
}
|
}
|
||||||
|
|
||||||
CSharedPointer<T> m_ptr;
|
CSharedPointer<T> m_ptr;
|
||||||
|
|
@ -391,12 +395,16 @@ namespace Hyprutils::Memory {
|
||||||
if (!m_ptr.impl_->dataNonNull() || m_ptr.impl_->destroying() || !m_ptr.impl_->lockable())
|
if (!m_ptr.impl_->dataNonNull() || m_ptr.impl_->destroying() || !m_ptr.impl_->lockable())
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
return CAtomicSharedPointer<T>(m_ptr.impl_);
|
return CAtomicSharedPointer<T>(m_ptr.impl_, m_ptr.m_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
Atomic_::impl* impl() const {
|
||||||
|
return sc<Atomic_::impl*>(m_ptr.impl_);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::lock_guard<std::recursive_mutex> implLockGuard() const {
|
std::lock_guard<std::recursive_mutex> implLockGuard() const {
|
||||||
return sc<Atomic_::impl*>(m_ptr.impl_)->lockGuard();
|
return impl()->lockGuard();
|
||||||
}
|
}
|
||||||
|
|
||||||
CWeakPointer<T> m_ptr;
|
CWeakPointer<T> m_ptr;
|
||||||
|
|
@ -411,4 +419,19 @@ namespace Hyprutils::Memory {
|
||||||
[[nodiscard]] inline CAtomicSharedPointer<U> makeAtomicShared(Args&&... args) {
|
[[nodiscard]] inline CAtomicSharedPointer<U> makeAtomicShared(Args&&... args) {
|
||||||
return CAtomicSharedPointer<U>(new U(std::forward<Args>(args)...));
|
return CAtomicSharedPointer<U>(new U(std::forward<Args>(args)...));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T, typename U>
|
||||||
|
CAtomicSharedPointer<T> reinterpretPointerCast(const CAtomicSharedPointer<U>& ref) {
|
||||||
|
return CAtomicSharedPointer<T>(ref.impl(), ref.m_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename U>
|
||||||
|
CAtomicSharedPointer<T> dynamicPointerCast(const CAtomicSharedPointer<U>& ref) {
|
||||||
|
if (!ref)
|
||||||
|
return nullptr;
|
||||||
|
T* newPtr = dynamic_cast<T*>(sc<U*>(ref.impl()->getData()));
|
||||||
|
if (!newPtr)
|
||||||
|
return nullptr;
|
||||||
|
return CAtomicSharedPointer<T>(ref.impl(), newPtr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,31 +28,33 @@ namespace Hyprutils {
|
||||||
|
|
||||||
/* creates a new shared pointer managing a resource
|
/* creates a new shared pointer managing a resource
|
||||||
avoid calling. Could duplicate ownership. Prefer makeShared */
|
avoid calling. Could duplicate ownership. Prefer makeShared */
|
||||||
explicit CSharedPointer(T* object) noexcept : impl_(new Impl_::impl_base(sc<void*>(object), _delete)) {
|
explicit CSharedPointer(T* object) noexcept : impl_(new Impl_::impl_base(sc<void*>(object), _delete)), m_data(sc<void*>(object)) {
|
||||||
increment();
|
increment();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* creates a shared pointer from a reference */
|
/* creates a shared pointer from a reference */
|
||||||
template <typename U, typename = isConstructible<U>>
|
template <typename U, typename = isConstructible<U>>
|
||||||
CSharedPointer(const CSharedPointer<U>& ref) noexcept : impl_(ref.impl_) {
|
CSharedPointer(const CSharedPointer<U>& ref) noexcept : impl_(ref.impl_), m_data(ref.m_data) {
|
||||||
increment();
|
increment();
|
||||||
}
|
}
|
||||||
|
|
||||||
CSharedPointer(const CSharedPointer& ref) noexcept : impl_(ref.impl_) {
|
CSharedPointer(const CSharedPointer& ref) noexcept : impl_(ref.impl_), m_data(ref.m_data) {
|
||||||
increment();
|
increment();
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename U, typename = isConstructible<U>>
|
template <typename U, typename = isConstructible<U>>
|
||||||
CSharedPointer(CSharedPointer<U>&& ref) noexcept {
|
CSharedPointer(CSharedPointer<U>&& ref) noexcept {
|
||||||
std::swap(impl_, ref.impl_);
|
std::swap(impl_, ref.impl_);
|
||||||
|
std::swap(m_data, ref.m_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
CSharedPointer(CSharedPointer&& ref) noexcept {
|
CSharedPointer(CSharedPointer&& ref) noexcept {
|
||||||
std::swap(impl_, ref.impl_);
|
std::swap(impl_, ref.impl_);
|
||||||
|
std::swap(m_data, ref.m_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* allows weakPointer to create from an impl */
|
/* allows weakPointer to create from an impl */
|
||||||
CSharedPointer(Impl_::impl_base* implementation) noexcept : impl_(implementation) {
|
CSharedPointer(Impl_::impl_base* implementation, void* data) noexcept : impl_(implementation), m_data(data) {
|
||||||
increment();
|
increment();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -74,7 +76,8 @@ namespace Hyprutils {
|
||||||
return *this;
|
return *this;
|
||||||
|
|
||||||
decrement();
|
decrement();
|
||||||
impl_ = rhs.impl_;
|
impl_ = rhs.impl_;
|
||||||
|
m_data = rhs.m_data;
|
||||||
increment();
|
increment();
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
@ -84,7 +87,8 @@ namespace Hyprutils {
|
||||||
return *this;
|
return *this;
|
||||||
|
|
||||||
decrement();
|
decrement();
|
||||||
impl_ = rhs.impl_;
|
impl_ = rhs.impl_;
|
||||||
|
m_data = rhs.m_data;
|
||||||
increment();
|
increment();
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
@ -92,11 +96,13 @@ namespace Hyprutils {
|
||||||
template <typename U>
|
template <typename U>
|
||||||
validHierarchy<const CSharedPointer<U>&> operator=(CSharedPointer<U>&& rhs) {
|
validHierarchy<const CSharedPointer<U>&> operator=(CSharedPointer<U>&& rhs) {
|
||||||
std::swap(impl_, rhs.impl_);
|
std::swap(impl_, rhs.impl_);
|
||||||
|
std::swap(m_data, rhs.m_data);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
CSharedPointer& operator=(CSharedPointer&& rhs) noexcept {
|
CSharedPointer& operator=(CSharedPointer&& rhs) noexcept {
|
||||||
std::swap(impl_, rhs.impl_);
|
std::swap(impl_, rhs.impl_);
|
||||||
|
std::swap(m_data, rhs.m_data);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -104,6 +110,8 @@ namespace Hyprutils {
|
||||||
return impl_ && impl_->dataNonNull();
|
return impl_ && impl_->dataNonNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this compares that the pointed-to object is the same, but in multiple inheritance,
|
||||||
|
// different typed pointers can be equal if the object is the same
|
||||||
bool operator==(const CSharedPointer& rhs) const {
|
bool operator==(const CSharedPointer& rhs) const {
|
||||||
return impl_ == rhs.impl_;
|
return impl_ == rhs.impl_;
|
||||||
}
|
}
|
||||||
|
|
@ -126,11 +134,12 @@ namespace Hyprutils {
|
||||||
|
|
||||||
void reset() {
|
void reset() {
|
||||||
decrement();
|
decrement();
|
||||||
impl_ = nullptr;
|
impl_ = nullptr;
|
||||||
|
m_data = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
T* get() const {
|
T* get() const {
|
||||||
return impl_ ? sc<T*>(impl_->getData()) : nullptr;
|
return impl_ && impl_->dataNonNull() ? sc<T*>(m_data) : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int strongRef() const {
|
unsigned int strongRef() const {
|
||||||
|
|
@ -139,6 +148,9 @@ namespace Hyprutils {
|
||||||
|
|
||||||
Impl_::impl_base* impl_ = nullptr;
|
Impl_::impl_base* impl_ = nullptr;
|
||||||
|
|
||||||
|
// Never use directly: raw data ptr, could be UAF
|
||||||
|
void* m_data = nullptr;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void _delete(void* p) {
|
static void _delete(void* p) {
|
||||||
std::default_delete<T>{}(sc<T*>(p));
|
std::default_delete<T>{}(sc<T*>(p));
|
||||||
|
|
@ -188,7 +200,17 @@ namespace Hyprutils {
|
||||||
|
|
||||||
template <typename T, typename U>
|
template <typename T, typename U>
|
||||||
CSharedPointer<T> reinterpretPointerCast(const CSharedPointer<U>& ref) {
|
CSharedPointer<T> reinterpretPointerCast(const CSharedPointer<U>& ref) {
|
||||||
return CSharedPointer<T>(ref.impl_);
|
return CSharedPointer<T>(ref.impl_, ref.m_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename U>
|
||||||
|
CSharedPointer<T> dynamicPointerCast(const CSharedPointer<U>& ref) {
|
||||||
|
if (!ref)
|
||||||
|
return nullptr;
|
||||||
|
T* newPtr = dynamic_cast<T*>(sc<U*>(ref.impl_->getData()));
|
||||||
|
if (!newPtr)
|
||||||
|
return nullptr;
|
||||||
|
return CSharedPointer<T>(ref.impl_, newPtr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,8 @@ namespace Hyprutils {
|
||||||
if (!ref.impl_)
|
if (!ref.impl_)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
impl_ = ref.impl_;
|
impl_ = ref.impl_;
|
||||||
|
m_data = ref.m_data;
|
||||||
incrementWeak();
|
incrementWeak();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -36,7 +37,8 @@ namespace Hyprutils {
|
||||||
if (!ref.impl_)
|
if (!ref.impl_)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
impl_ = ref.impl_;
|
impl_ = ref.impl_;
|
||||||
|
m_data = ref.impl_->getData();
|
||||||
incrementWeak();
|
incrementWeak();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -46,7 +48,8 @@ namespace Hyprutils {
|
||||||
if (!ref.impl_)
|
if (!ref.impl_)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
impl_ = ref.impl_;
|
impl_ = ref.impl_;
|
||||||
|
m_data = ref.m_data;
|
||||||
incrementWeak();
|
incrementWeak();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -54,17 +57,20 @@ namespace Hyprutils {
|
||||||
if (!ref.impl_)
|
if (!ref.impl_)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
impl_ = ref.impl_;
|
impl_ = ref.impl_;
|
||||||
|
m_data = ref.m_data;
|
||||||
incrementWeak();
|
incrementWeak();
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename U, typename = isConstructible<U>>
|
template <typename U, typename = isConstructible<U>>
|
||||||
CWeakPointer(CWeakPointer<U>&& ref) noexcept {
|
CWeakPointer(CWeakPointer<U>&& ref) noexcept {
|
||||||
std::swap(impl_, ref.impl_);
|
std::swap(impl_, ref.impl_);
|
||||||
|
std::swap(m_data, ref.m_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
CWeakPointer(CWeakPointer&& ref) noexcept {
|
CWeakPointer(CWeakPointer&& ref) noexcept {
|
||||||
std::swap(impl_, ref.impl_);
|
std::swap(impl_, ref.impl_);
|
||||||
|
std::swap(m_data, ref.m_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* create a weak ptr from another weak ptr with assignment */
|
/* create a weak ptr from another weak ptr with assignment */
|
||||||
|
|
@ -74,7 +80,8 @@ namespace Hyprutils {
|
||||||
return *this;
|
return *this;
|
||||||
|
|
||||||
decrementWeak();
|
decrementWeak();
|
||||||
impl_ = rhs.impl_;
|
impl_ = rhs.impl_;
|
||||||
|
m_data = rhs.m_data;
|
||||||
incrementWeak();
|
incrementWeak();
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
@ -84,7 +91,8 @@ namespace Hyprutils {
|
||||||
return *this;
|
return *this;
|
||||||
|
|
||||||
decrementWeak();
|
decrementWeak();
|
||||||
impl_ = rhs.impl_;
|
impl_ = rhs.impl_;
|
||||||
|
m_data = rhs.m_data;
|
||||||
incrementWeak();
|
incrementWeak();
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
@ -96,7 +104,8 @@ namespace Hyprutils {
|
||||||
return *this;
|
return *this;
|
||||||
|
|
||||||
decrementWeak();
|
decrementWeak();
|
||||||
impl_ = rhs.impl_;
|
impl_ = rhs.impl_;
|
||||||
|
m_data = rhs.m_data;
|
||||||
incrementWeak();
|
incrementWeak();
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
@ -125,14 +134,15 @@ namespace Hyprutils {
|
||||||
|
|
||||||
void reset() {
|
void reset() {
|
||||||
decrementWeak();
|
decrementWeak();
|
||||||
impl_ = nullptr;
|
impl_ = nullptr;
|
||||||
|
m_data = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
CSharedPointer<T> lock() const {
|
CSharedPointer<T> lock() const {
|
||||||
if (!impl_ || !impl_->dataNonNull() || impl_->destroying() || !impl_->lockable())
|
if (!impl_ || !impl_->dataNonNull() || impl_->destroying() || !impl_->lockable())
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
return CSharedPointer<T>(impl_);
|
return CSharedPointer<T>(impl_, m_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* this returns valid() */
|
/* this returns valid() */
|
||||||
|
|
@ -169,7 +179,7 @@ namespace Hyprutils {
|
||||||
}
|
}
|
||||||
|
|
||||||
T* get() const {
|
T* get() const {
|
||||||
return impl_ ? sc<T*>(impl_->getData()) : nullptr;
|
return impl_ && impl_->dataNonNull() ? sc<T*>(m_data) : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
T* operator->() const {
|
T* operator->() const {
|
||||||
|
|
@ -182,6 +192,9 @@ namespace Hyprutils {
|
||||||
|
|
||||||
Impl_::impl_base* impl_ = nullptr;
|
Impl_::impl_base* impl_ = nullptr;
|
||||||
|
|
||||||
|
// Never use directly: raw data ptr, could be UAF
|
||||||
|
void* m_data = nullptr;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/* no-op if there is no impl_ */
|
/* no-op if there is no impl_ */
|
||||||
void decrementWeak() {
|
void decrementWeak() {
|
||||||
|
|
@ -207,6 +220,16 @@ namespace Hyprutils {
|
||||||
impl_->incWeak();
|
impl_->incWeak();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <typename T, typename U>
|
||||||
|
CWeakPointer<T> dynamicPointerCast(const CWeakPointer<U>& ref) {
|
||||||
|
if (!ref)
|
||||||
|
return nullptr;
|
||||||
|
T* newPtr = dynamic_cast<T*>(sc<U*>(ref.impl_->getData()));
|
||||||
|
if (!newPtr)
|
||||||
|
return nullptr;
|
||||||
|
return CWeakPointer<T>(ref.impl_, newPtr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
9
include/hyprutils/os/File.hpp
Normal file
9
include/hyprutils/os/File.hpp
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <expected>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
namespace Hyprutils::File {
|
||||||
|
std::expected<std::string, std::string> readFileAsString(const std::string_view& path);
|
||||||
|
}
|
||||||
|
|
@ -26,10 +26,13 @@ namespace Hyprutils {
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
class CSignalT : public CSignalBase {
|
class CSignalT : public CSignalBase {
|
||||||
template <typename T>
|
template <typename T>
|
||||||
using RefArg = std::conditional_t<std::is_reference_v<T> || std::is_arithmetic_v<T>, T, const T&>;
|
using RefArg = std::conditional_t<std::is_trivially_copyable_v<T>, T, const T&>;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void emit(RefArg<Args>... args) {
|
void emit(RefArg<Args>... args) {
|
||||||
|
if (m_vListeners.empty() && m_vStaticListeners.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
if constexpr (sizeof...(Args) == 0)
|
if constexpr (sizeof...(Args) == 0)
|
||||||
emitInternal(nullptr);
|
emitInternal(nullptr);
|
||||||
else {
|
else {
|
||||||
|
|
|
||||||
|
|
@ -9,5 +9,6 @@ namespace Hyprutils {
|
||||||
std::string_view trim(const std::string_view& in);
|
std::string_view trim(const std::string_view& in);
|
||||||
bool isNumber(const std::string& str, bool allowfloat = false);
|
bool isNumber(const std::string& str, bool allowfloat = false);
|
||||||
void replaceInString(std::string& string, const std::string& what, const std::string& to);
|
void replaceInString(std::string& string, const std::string& what, const std::string& to);
|
||||||
|
bool truthy(const std::string_view& in);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
@ -7,7 +7,6 @@
|
||||||
gtest,
|
gtest,
|
||||||
pixman,
|
pixman,
|
||||||
version ? "git",
|
version ? "git",
|
||||||
doCheck ? false,
|
|
||||||
debug ? false,
|
debug ? false,
|
||||||
# whether to use the mold linker
|
# whether to use the mold linker
|
||||||
# disable this for older machines without SSE4_2 and AVX2 support
|
# disable this for older machines without SSE4_2 and AVX2 support
|
||||||
|
|
@ -15,9 +14,8 @@
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
inherit (builtins) foldl';
|
inherit (builtins) foldl';
|
||||||
inherit (lib.attrsets) mapAttrsToList;
|
|
||||||
inherit (lib.lists) flatten optional;
|
inherit (lib.lists) flatten optional;
|
||||||
inherit (lib.strings) cmakeBool optionalString;
|
inherit (lib.strings) optionalString;
|
||||||
|
|
||||||
adapters = flatten [
|
adapters = flatten [
|
||||||
(lib.optional withMold stdenvAdapters.useMoldLinker)
|
(lib.optional withMold stdenvAdapters.useMoldLinker)
|
||||||
|
|
@ -28,16 +26,18 @@ let
|
||||||
in
|
in
|
||||||
customStdenv.mkDerivation {
|
customStdenv.mkDerivation {
|
||||||
pname = "hyprutils" + optionalString debug "-debug";
|
pname = "hyprutils" + optionalString debug "-debug";
|
||||||
inherit version doCheck;
|
inherit version;
|
||||||
src = ../.;
|
src = ../.;
|
||||||
|
|
||||||
|
doCheck = debug;
|
||||||
|
|
||||||
nativeBuildInputs = [
|
nativeBuildInputs = [
|
||||||
cmake
|
cmake
|
||||||
pkg-config
|
pkg-config
|
||||||
];
|
];
|
||||||
|
|
||||||
buildInputs = flatten [
|
buildInputs = flatten [
|
||||||
(optional doCheck gtest)
|
(optional debug gtest)
|
||||||
pixman
|
pixman
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
@ -48,10 +48,6 @@ customStdenv.mkDerivation {
|
||||||
|
|
||||||
cmakeBuildType = if debug then "Debug" else "RelWithDebInfo";
|
cmakeBuildType = if debug then "Debug" else "RelWithDebInfo";
|
||||||
|
|
||||||
cmakeFlags = mapAttrsToList cmakeBool {
|
|
||||||
"DISABLE_TESTING" = !doCheck;
|
|
||||||
};
|
|
||||||
|
|
||||||
meta = with lib; {
|
meta = with lib; {
|
||||||
homepage = "https://github.com/hyprwm/hyprutils";
|
homepage = "https://github.com/hyprwm/hyprutils";
|
||||||
description = "Small C++ library for utilities used across the Hypr* ecosystem";
|
description = "Small C++ library for utilities used across the Hypr* ecosystem";
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,6 @@ in {
|
||||||
inherit version;
|
inherit version;
|
||||||
};
|
};
|
||||||
hyprutils-debug = final.hyprutils.override {debug = true;};
|
hyprutils-debug = final.hyprutils.override {debug = true;};
|
||||||
hyprutils-with-tests = final.hyprutils.override {doCheck = true;};
|
hyprutils-with-tests = final.hyprutils-debug;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,16 @@ static const std::string DEFAULTSTYLE = "";
|
||||||
|
|
||||||
void CBaseAnimatedVariable::create(CAnimationManager* pManager, int typeInfo, SP<CBaseAnimatedVariable> pSelf) {
|
void CBaseAnimatedVariable::create(CAnimationManager* pManager, int typeInfo, SP<CBaseAnimatedVariable> pSelf) {
|
||||||
m_Type = typeInfo;
|
m_Type = typeInfo;
|
||||||
m_pSelf = pSelf;
|
m_pSelf = std::move(pSelf);
|
||||||
|
|
||||||
|
m_pAnimationManager = pManager;
|
||||||
|
m_pSignals = pManager->getSignals();
|
||||||
|
m_bDummy = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CBaseAnimatedVariable::create2(CAnimationManager* pManager, int typeInfo, WP<CBaseAnimatedVariable> pSelf) {
|
||||||
|
m_Type = typeInfo;
|
||||||
|
m_pSelf = std::move(pSelf);
|
||||||
|
|
||||||
m_pAnimationManager = pManager;
|
m_pAnimationManager = pManager;
|
||||||
m_pSignals = pManager->getSignals();
|
m_pSignals = pManager->getSignals();
|
||||||
|
|
@ -37,28 +46,22 @@ void CBaseAnimatedVariable::disconnectFromActive() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Hyprutils::Animation::CBaseAnimatedVariable::enabled() const {
|
bool Hyprutils::Animation::CBaseAnimatedVariable::enabled() const {
|
||||||
if (const auto PCONFIG = m_pConfig.lock()) {
|
if (m_pConfig && m_pConfig->pValues)
|
||||||
const auto PVALUES = PCONFIG->pValues.lock();
|
return m_pConfig->pValues->internalEnabled;
|
||||||
return PVALUES ? PVALUES->internalEnabled : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string& CBaseAnimatedVariable::getBezierName() const {
|
const std::string& CBaseAnimatedVariable::getBezierName() const {
|
||||||
if (const auto PCONFIG = m_pConfig.lock()) {
|
if (m_pConfig && m_pConfig->pValues)
|
||||||
const auto PVALUES = PCONFIG->pValues.lock();
|
return m_pConfig->pValues->internalBezier;
|
||||||
return PVALUES ? PVALUES->internalBezier : DEFAULTBEZIERNAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
return DEFAULTBEZIERNAME;
|
return DEFAULTBEZIERNAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string& CBaseAnimatedVariable::getStyle() const {
|
const std::string& CBaseAnimatedVariable::getStyle() const {
|
||||||
if (const auto PCONFIG = m_pConfig.lock()) {
|
if (m_pConfig && m_pConfig->pValues)
|
||||||
const auto PVALUES = PCONFIG->pValues.lock();
|
return m_pConfig->pValues->internalStyle;
|
||||||
return PVALUES ? PVALUES->internalStyle : DEFAULTSTYLE;
|
|
||||||
}
|
|
||||||
|
|
||||||
return DEFAULTSTYLE;
|
return DEFAULTSTYLE;
|
||||||
}
|
}
|
||||||
|
|
@ -66,10 +69,8 @@ const std::string& CBaseAnimatedVariable::getStyle() const {
|
||||||
float CBaseAnimatedVariable::getPercent() const {
|
float CBaseAnimatedVariable::getPercent() const {
|
||||||
const auto DURATIONPASSED = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - animationBegin).count();
|
const auto DURATIONPASSED = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - animationBegin).count();
|
||||||
|
|
||||||
if (const auto PCONFIG = m_pConfig.lock()) {
|
if (m_pConfig && m_pConfig->pValues)
|
||||||
const auto PVALUES = PCONFIG->pValues.lock();
|
return std::clamp((DURATIONPASSED / 100.f) / m_pConfig->pValues->internalSpeed, 0.f, 1.f);
|
||||||
return PVALUES ? std::clamp((DURATIONPASSED / 100.f) / PVALUES->internalSpeed, 0.f, 1.f) : 1.f;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1.f;
|
return 1.f;
|
||||||
}
|
}
|
||||||
|
|
@ -78,14 +79,7 @@ float CBaseAnimatedVariable::getCurveValue() const {
|
||||||
if (!m_bIsBeingAnimated || isAnimationManagerDead())
|
if (!m_bIsBeingAnimated || isAnimationManagerDead())
|
||||||
return 1.f;
|
return 1.f;
|
||||||
|
|
||||||
std::string bezierName = "";
|
const auto BEZIER = m_pAnimationManager->getBezier(getBezierName());
|
||||||
if (const auto PCONFIG = m_pConfig.lock()) {
|
|
||||||
const auto PVALUES = PCONFIG->pValues.lock();
|
|
||||||
if (PVALUES)
|
|
||||||
bezierName = PVALUES->internalBezier;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto BEZIER = m_pAnimationManager->getBezier(bezierName);
|
|
||||||
if (!BEZIER)
|
if (!BEZIER)
|
||||||
return 1.f;
|
return 1.f;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -64,14 +64,13 @@ void CAnimationManager::rotateActive() {
|
||||||
std::vector<CWeakPointer<CBaseAnimatedVariable>> active;
|
std::vector<CWeakPointer<CBaseAnimatedVariable>> active;
|
||||||
active.reserve(m_vActiveAnimatedVariables.size()); // avoid reallocations
|
active.reserve(m_vActiveAnimatedVariables.size()); // avoid reallocations
|
||||||
for (auto const& av : m_vActiveAnimatedVariables) {
|
for (auto const& av : m_vActiveAnimatedVariables) {
|
||||||
const auto PAV = av.lock();
|
if (!av)
|
||||||
if (!PAV)
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (PAV->ok() && PAV->isBeingAnimated())
|
if (av->ok() && av->isBeingAnimated())
|
||||||
active.emplace_back(av);
|
active.emplace_back(av);
|
||||||
else
|
else
|
||||||
PAV->m_bIsConnectedToActive = false;
|
av->m_bIsConnectedToActive = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_vActiveAnimatedVariables = std::move(active);
|
m_vActiveAnimatedVariables = std::move(active);
|
||||||
|
|
@ -99,404 +98,3 @@ const std::unordered_map<std::string, SP<CBezierCurve>>& CAnimationManager::getA
|
||||||
CWeakPointer<CAnimationManager::SAnimationManagerSignals> CAnimationManager::getSignals() const {
|
CWeakPointer<CAnimationManager::SAnimationManagerSignals> CAnimationManager::getSignals() const {
|
||||||
return m_events;
|
return m_events;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HU_UNIT_TESTS
|
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
|
||||||
|
|
||||||
#include <hyprutils/animation/AnimationConfig.hpp>
|
|
||||||
#include <hyprutils/animation/AnimationManager.hpp>
|
|
||||||
#include <hyprutils/animation/AnimatedVariable.hpp>
|
|
||||||
#include <hyprutils/memory/WeakPtr.hpp>
|
|
||||||
#include <hyprutils/memory/UniquePtr.hpp>
|
|
||||||
|
|
||||||
#define SP CSharedPointer
|
|
||||||
#define WP CWeakPointer
|
|
||||||
#define UP CUniquePointer
|
|
||||||
|
|
||||||
using namespace Hyprutils::Animation;
|
|
||||||
using namespace Hyprutils::Math;
|
|
||||||
using namespace Hyprutils::Memory;
|
|
||||||
|
|
||||||
class EmtpyContext {};
|
|
||||||
|
|
||||||
template <typename VarType>
|
|
||||||
using CAnimatedVariable = CGenericAnimatedVariable<VarType, EmtpyContext>;
|
|
||||||
|
|
||||||
template <typename VarType>
|
|
||||||
using PANIMVAR = SP<CAnimatedVariable<VarType>>;
|
|
||||||
|
|
||||||
template <typename VarType>
|
|
||||||
using PANIMVARREF = WP<CAnimatedVariable<VarType>>;
|
|
||||||
|
|
||||||
enum eAVTypes {
|
|
||||||
INT = 1,
|
|
||||||
TEST,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct SomeTestType {
|
|
||||||
bool done = false;
|
|
||||||
bool operator==(const SomeTestType& other) const {
|
|
||||||
return done == other.done;
|
|
||||||
}
|
|
||||||
SomeTestType& operator=(const SomeTestType& other) {
|
|
||||||
done = other.done;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
CAnimationConfigTree animationTree;
|
|
||||||
|
|
||||||
class CMyAnimationManager : public CAnimationManager {
|
|
||||||
public:
|
|
||||||
void tick() {
|
|
||||||
for (size_t i = 0; i < m_vActiveAnimatedVariables.size(); i++) {
|
|
||||||
const auto PAV = m_vActiveAnimatedVariables[i].lock();
|
|
||||||
if (!PAV || !PAV->ok() || !PAV->isBeingAnimated())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
const auto SPENT = PAV->getPercent();
|
|
||||||
const auto PBEZIER = getBezier(PAV->getBezierName());
|
|
||||||
|
|
||||||
if (SPENT >= 1.f || !PAV->enabled()) {
|
|
||||||
PAV->warp(true, false);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto POINTY = PBEZIER->getYForPoint(SPENT);
|
|
||||||
|
|
||||||
switch (PAV->m_Type) {
|
|
||||||
case eAVTypes::INT: {
|
|
||||||
auto avInt = dc<CAnimatedVariable<int>*>(PAV.get());
|
|
||||||
if (!avInt)
|
|
||||||
std::cout << "Dynamic cast upcast failed\n";
|
|
||||||
|
|
||||||
const auto DELTA = avInt->goal() - avInt->value();
|
|
||||||
avInt->value() = avInt->begun() + (DELTA * POINTY);
|
|
||||||
} break;
|
|
||||||
case eAVTypes::TEST: {
|
|
||||||
auto avCustom = dc<CAnimatedVariable<SomeTestType>*>(PAV.get());
|
|
||||||
if (!avCustom)
|
|
||||||
std::cout << "Dynamic cast upcast failed\n";
|
|
||||||
|
|
||||||
if (SPENT >= 1.f)
|
|
||||||
avCustom->value().done = true;
|
|
||||||
} break;
|
|
||||||
default: {
|
|
||||||
std::cout << "What are we even doing?\n";
|
|
||||||
} break;
|
|
||||||
}
|
|
||||||
|
|
||||||
PAV->onUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
tickDone();
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename VarType>
|
|
||||||
void createAnimation(const VarType& v, PANIMVAR<VarType>& av, const std::string& animationConfigName) {
|
|
||||||
constexpr const eAVTypes EAVTYPE = std::is_same_v<VarType, int> ? eAVTypes::INT : eAVTypes::TEST;
|
|
||||||
const auto PAV = makeShared<CGenericAnimatedVariable<VarType, EmtpyContext>>();
|
|
||||||
|
|
||||||
PAV->create(EAVTYPE, sc<CAnimationManager*>(this), PAV, v);
|
|
||||||
PAV->setConfig(animationTree.getConfig(animationConfigName));
|
|
||||||
av = std::move(PAV);
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void scheduleTick() {
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void onTicked() {
|
|
||||||
;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
UP<CMyAnimationManager> pAnimationManager;
|
|
||||||
|
|
||||||
class Subject {
|
|
||||||
public:
|
|
||||||
Subject(const int& a, const int& b) {
|
|
||||||
pAnimationManager->createAnimation(a, m_iA, "default");
|
|
||||||
pAnimationManager->createAnimation(b, m_iB, "internal");
|
|
||||||
pAnimationManager->createAnimation({}, m_iC, "default");
|
|
||||||
}
|
|
||||||
PANIMVAR<int> m_iA;
|
|
||||||
PANIMVAR<int> m_iB;
|
|
||||||
PANIMVAR<SomeTestType> m_iC;
|
|
||||||
};
|
|
||||||
|
|
||||||
static int config() {
|
|
||||||
pAnimationManager = makeUnique<CMyAnimationManager>();
|
|
||||||
|
|
||||||
int ret = 0;
|
|
||||||
|
|
||||||
animationTree.createNode("global");
|
|
||||||
animationTree.createNode("internal");
|
|
||||||
|
|
||||||
animationTree.createNode("foo", "internal");
|
|
||||||
animationTree.createNode("default", "global");
|
|
||||||
animationTree.createNode("bar", "default");
|
|
||||||
|
|
||||||
/*
|
|
||||||
internal
|
|
||||||
↳ foo
|
|
||||||
global
|
|
||||||
↳ default
|
|
||||||
↳ bar
|
|
||||||
*/
|
|
||||||
|
|
||||||
auto barCfg = animationTree.getConfig("bar");
|
|
||||||
auto internalCfg = animationTree.getConfig("internal");
|
|
||||||
|
|
||||||
// internal is a root node and should point to itself
|
|
||||||
EXPECT_EQ(internalCfg->pParentAnimation.get(), internalCfg.get());
|
|
||||||
EXPECT_EQ(internalCfg->pValues.get(), internalCfg.get());
|
|
||||||
|
|
||||||
animationTree.setConfigForNode("global", 1, 4.0, "default", "asdf");
|
|
||||||
|
|
||||||
EXPECT_EQ(barCfg->internalEnabled, -1);
|
|
||||||
{
|
|
||||||
const auto PVALUES = barCfg->pValues.lock();
|
|
||||||
EXPECT_EQ(PVALUES->internalEnabled, 1);
|
|
||||||
EXPECT_EQ(PVALUES->internalBezier, "default");
|
|
||||||
EXPECT_EQ(PVALUES->internalStyle, "asdf");
|
|
||||||
EXPECT_EQ(PVALUES->internalSpeed, 4.0);
|
|
||||||
}
|
|
||||||
EXPECT_EQ(barCfg->pParentAnimation.get(), animationTree.getConfig("default").get());
|
|
||||||
|
|
||||||
// Overwrite our own values
|
|
||||||
animationTree.setConfigForNode("bar", 1, 4.2, "test", "qwer");
|
|
||||||
|
|
||||||
{
|
|
||||||
const auto PVALUES = barCfg->pValues.lock();
|
|
||||||
EXPECT_EQ(PVALUES->internalEnabled, 1);
|
|
||||||
EXPECT_EQ(PVALUES->internalBezier, "test");
|
|
||||||
EXPECT_EQ(PVALUES->internalStyle, "qwer");
|
|
||||||
EXPECT_EQ(PVALUES->internalSpeed, 4.2f);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now overwrite the parent
|
|
||||||
animationTree.setConfigForNode("default", 0, 0.0, "zxcv", "foo");
|
|
||||||
|
|
||||||
{
|
|
||||||
// Expecting no change
|
|
||||||
const auto PVALUES = barCfg->pValues.lock();
|
|
||||||
EXPECT_EQ(PVALUES->internalEnabled, 1);
|
|
||||||
EXPECT_EQ(PVALUES->internalBezier, "test");
|
|
||||||
EXPECT_EQ(PVALUES->internalStyle, "qwer");
|
|
||||||
EXPECT_EQ(PVALUES->internalSpeed, 4.2f);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Animation, animation) {
|
|
||||||
config();
|
|
||||||
|
|
||||||
animationTree.createNode("global");
|
|
||||||
animationTree.createNode("internal");
|
|
||||||
|
|
||||||
animationTree.createNode("default", "global");
|
|
||||||
animationTree.setConfigForNode("global", 1, 4.0, "default", "asdf");
|
|
||||||
|
|
||||||
Subject s(0, 0);
|
|
||||||
|
|
||||||
EXPECT_EQ(s.m_iA->value(), 0);
|
|
||||||
EXPECT_EQ(s.m_iB->value(), 0);
|
|
||||||
|
|
||||||
// Test destruction of a CAnimatedVariable
|
|
||||||
{
|
|
||||||
Subject s2(10, 10);
|
|
||||||
// Adds them to active
|
|
||||||
*s2.m_iA = 1;
|
|
||||||
*s2.m_iB = 2;
|
|
||||||
// We deliberately do not tick here, to make sure the destructor removes active animated variables
|
|
||||||
}
|
|
||||||
|
|
||||||
EXPECT_EQ(pAnimationManager->shouldTickForNext(), false);
|
|
||||||
EXPECT_EQ(s.m_iC->value().done, false);
|
|
||||||
|
|
||||||
*s.m_iA = 10;
|
|
||||||
*s.m_iB = 100;
|
|
||||||
*s.m_iC = SomeTestType(true);
|
|
||||||
|
|
||||||
EXPECT_EQ(s.m_iC->value().done, false);
|
|
||||||
|
|
||||||
while (pAnimationManager->shouldTickForNext()) {
|
|
||||||
pAnimationManager->tick();
|
|
||||||
}
|
|
||||||
|
|
||||||
EXPECT_EQ(s.m_iA->value(), 10);
|
|
||||||
EXPECT_EQ(s.m_iB->value(), 100);
|
|
||||||
EXPECT_EQ(s.m_iC->value().done, true);
|
|
||||||
|
|
||||||
s.m_iA->setValue(0);
|
|
||||||
s.m_iB->setValue(0);
|
|
||||||
|
|
||||||
while (pAnimationManager->shouldTickForNext()) {
|
|
||||||
pAnimationManager->tick();
|
|
||||||
}
|
|
||||||
|
|
||||||
EXPECT_EQ(s.m_iA->value(), 10);
|
|
||||||
EXPECT_EQ(s.m_iB->value(), 100);
|
|
||||||
|
|
||||||
// Test config stuff
|
|
||||||
EXPECT_EQ(s.m_iA->getBezierName(), "default");
|
|
||||||
EXPECT_EQ(s.m_iA->getStyle(), "asdf");
|
|
||||||
EXPECT_EQ(s.m_iA->enabled(), true);
|
|
||||||
|
|
||||||
animationTree.getConfig("global")->internalEnabled = 0;
|
|
||||||
|
|
||||||
EXPECT_EQ(s.m_iA->enabled(), false);
|
|
||||||
|
|
||||||
*s.m_iA = 50;
|
|
||||||
pAnimationManager->tick(); // Expecting a warp
|
|
||||||
EXPECT_EQ(s.m_iA->value(), 50);
|
|
||||||
|
|
||||||
// Test missing pValues
|
|
||||||
animationTree.getConfig("global")->internalEnabled = 0;
|
|
||||||
animationTree.getConfig("default")->pValues.reset();
|
|
||||||
|
|
||||||
EXPECT_EQ(s.m_iA->enabled(), false);
|
|
||||||
EXPECT_EQ(s.m_iA->getBezierName(), "default");
|
|
||||||
EXPECT_EQ(s.m_iA->getStyle(), "");
|
|
||||||
EXPECT_EQ(s.m_iA->getPercent(), 1.f);
|
|
||||||
|
|
||||||
// Reset
|
|
||||||
animationTree.setConfigForNode("default", 1, 1, "default");
|
|
||||||
|
|
||||||
//
|
|
||||||
// Test callbacks
|
|
||||||
//
|
|
||||||
int beginCallbackRan = 0;
|
|
||||||
int updateCallbackRan = 0;
|
|
||||||
int endCallbackRan = 0;
|
|
||||||
s.m_iA->setCallbackOnBegin([&beginCallbackRan](WP<CBaseAnimatedVariable> pav) { beginCallbackRan++; });
|
|
||||||
s.m_iA->setUpdateCallback([&updateCallbackRan](WP<CBaseAnimatedVariable> pav) { updateCallbackRan++; });
|
|
||||||
s.m_iA->setCallbackOnEnd([&endCallbackRan](WP<CBaseAnimatedVariable> pav) { endCallbackRan++; }, false);
|
|
||||||
|
|
||||||
s.m_iA->setValueAndWarp(42);
|
|
||||||
|
|
||||||
EXPECT_EQ(beginCallbackRan, 0);
|
|
||||||
EXPECT_EQ(updateCallbackRan, 1);
|
|
||||||
EXPECT_EQ(endCallbackRan, 2); // first called when setting the callback, then when warping.
|
|
||||||
|
|
||||||
*s.m_iA = 1337;
|
|
||||||
while (pAnimationManager->shouldTickForNext()) {
|
|
||||||
pAnimationManager->tick();
|
|
||||||
}
|
|
||||||
|
|
||||||
EXPECT_EQ(beginCallbackRan, 1);
|
|
||||||
EXPECT_EQ(updateCallbackRan > 2, true);
|
|
||||||
EXPECT_EQ(endCallbackRan, 3);
|
|
||||||
|
|
||||||
std::vector<PANIMVAR<int>> vars;
|
|
||||||
for (int i = 0; i < 10; i++) {
|
|
||||||
vars.resize(vars.size() + 1);
|
|
||||||
pAnimationManager->createAnimation(1, vars.back(), "default");
|
|
||||||
*vars.back() = 1337;
|
|
||||||
}
|
|
||||||
|
|
||||||
// test adding / removing vars during a tick
|
|
||||||
s.m_iA->resetAllCallbacks();
|
|
||||||
s.m_iA->setUpdateCallback([&vars](WP<CBaseAnimatedVariable> v) {
|
|
||||||
if (v.lock() != vars.back())
|
|
||||||
vars.back()->warp();
|
|
||||||
});
|
|
||||||
s.m_iA->setCallbackOnEnd([&s, &vars](auto) {
|
|
||||||
vars.resize(vars.size() + 1);
|
|
||||||
pAnimationManager->createAnimation(1, vars.back(), "default");
|
|
||||||
*vars.back() = 1337;
|
|
||||||
});
|
|
||||||
|
|
||||||
*s.m_iA = 1000000;
|
|
||||||
|
|
||||||
while (pAnimationManager->shouldTickForNext()) {
|
|
||||||
pAnimationManager->tick();
|
|
||||||
}
|
|
||||||
|
|
||||||
EXPECT_EQ(s.m_iA->value(), 1000000);
|
|
||||||
// all vars should be set to 1337
|
|
||||||
EXPECT_EQ(std::find_if(vars.begin(), vars.end(), [](const auto& v) { return v->value() != 1337; }) == vars.end(), true);
|
|
||||||
|
|
||||||
// test one-time callbacks
|
|
||||||
s.m_iA->resetAllCallbacks();
|
|
||||||
s.m_iA->setCallbackOnEnd([&endCallbackRan](auto) { endCallbackRan++; }, true);
|
|
||||||
|
|
||||||
EXPECT_EQ(endCallbackRan, 4);
|
|
||||||
|
|
||||||
s.m_iA->setValueAndWarp(10);
|
|
||||||
|
|
||||||
EXPECT_EQ(endCallbackRan, 4);
|
|
||||||
EXPECT_EQ(s.m_iA->value(), 10);
|
|
||||||
|
|
||||||
// test warp
|
|
||||||
*s.m_iA = 3;
|
|
||||||
s.m_iA->setCallbackOnEnd([&endCallbackRan](auto) { endCallbackRan++; }, false);
|
|
||||||
|
|
||||||
s.m_iA->warp(false);
|
|
||||||
EXPECT_EQ(endCallbackRan, 4);
|
|
||||||
|
|
||||||
*s.m_iA = 4;
|
|
||||||
s.m_iA->warp(true);
|
|
||||||
EXPECT_EQ(endCallbackRan, 5);
|
|
||||||
|
|
||||||
// test getCurveValue
|
|
||||||
*s.m_iA = 0;
|
|
||||||
EXPECT_EQ(s.m_iA->getCurveValue(), 0.f);
|
|
||||||
s.m_iA->warp();
|
|
||||||
EXPECT_EQ(s.m_iA->getCurveValue(), 1.f);
|
|
||||||
EXPECT_EQ(endCallbackRan, 6);
|
|
||||||
|
|
||||||
// test end callback readding the var
|
|
||||||
*s.m_iA = 5;
|
|
||||||
s.m_iA->setCallbackOnEnd([&endCallbackRan](WP<CBaseAnimatedVariable> v) {
|
|
||||||
endCallbackRan++;
|
|
||||||
const auto PAV = dc<CAnimatedVariable<int>*>(v.lock().get());
|
|
||||||
|
|
||||||
*PAV = 10;
|
|
||||||
PAV->setCallbackOnEnd([&endCallbackRan](WP<CBaseAnimatedVariable> v) { endCallbackRan++; });
|
|
||||||
});
|
|
||||||
|
|
||||||
while (pAnimationManager->shouldTickForNext()) {
|
|
||||||
pAnimationManager->tick();
|
|
||||||
}
|
|
||||||
|
|
||||||
EXPECT_EQ(endCallbackRan, 8);
|
|
||||||
EXPECT_EQ(s.m_iA->value(), 10);
|
|
||||||
|
|
||||||
// Test duplicate active anim vars are not allowed
|
|
||||||
{
|
|
||||||
EXPECT_EQ(pAnimationManager->m_vActiveAnimatedVariables.size(), 0);
|
|
||||||
PANIMVAR<int> a;
|
|
||||||
pAnimationManager->createAnimation(1, a, "default");
|
|
||||||
EXPECT_EQ(pAnimationManager->m_vActiveAnimatedVariables.size(), 0);
|
|
||||||
*a = 10;
|
|
||||||
EXPECT_EQ(pAnimationManager->m_vActiveAnimatedVariables.size(), 1);
|
|
||||||
*a = 20;
|
|
||||||
EXPECT_EQ(pAnimationManager->m_vActiveAnimatedVariables.size(), 1);
|
|
||||||
a->warp();
|
|
||||||
EXPECT_EQ(pAnimationManager->m_vActiveAnimatedVariables.size(), 0);
|
|
||||||
EXPECT_EQ(a->value(), 20);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test no crash when animation manager gets destroyed
|
|
||||||
{
|
|
||||||
PANIMVAR<int> a;
|
|
||||||
pAnimationManager->createAnimation(1, a, "default");
|
|
||||||
*a = 10;
|
|
||||||
pAnimationManager.reset();
|
|
||||||
EXPECT_EQ(a->isAnimationManagerDead(), true);
|
|
||||||
a->setValueAndWarp(11);
|
|
||||||
EXPECT_EQ(a->value(), 11);
|
|
||||||
*a = 12;
|
|
||||||
a->warp();
|
|
||||||
EXPECT_EQ(a->value(), 12);
|
|
||||||
*a = 13;
|
|
||||||
} // a gets destroyed
|
|
||||||
|
|
||||||
EXPECT_EQ(pAnimationManager.get(), nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
|
||||||
|
|
@ -105,84 +105,3 @@ float CBezierCurve::getYForPoint(float const& x) const {
|
||||||
const std::vector<Hyprutils::Math::Vector2D>& CBezierCurve::getControlPoints() const {
|
const std::vector<Hyprutils::Math::Vector2D>& CBezierCurve::getControlPoints() const {
|
||||||
return m_vPoints;
|
return m_vPoints;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HU_UNIT_TESTS
|
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
|
||||||
|
|
||||||
using Hyprutils::Animation::CBezierCurve;
|
|
||||||
using Hyprutils::Math::Vector2D;
|
|
||||||
|
|
||||||
static void test_nonmonotonic4_clamps_out_of_range() {
|
|
||||||
// Non-monotonic curve in X
|
|
||||||
// This used to drive the step-halving search to OOB. It should now clamp
|
|
||||||
CBezierCurve curve;
|
|
||||||
std::array<Vector2D, 4> pts = {
|
|
||||||
Vector2D{0.5f, 1.0f}, // P0
|
|
||||||
Vector2D{1.0f, 1.0f}, // P1
|
|
||||||
Vector2D{0.0f, 0.0f}, // P2
|
|
||||||
Vector2D{0.5f, 0.0f} // P3
|
|
||||||
};
|
|
||||||
curve.setup4(pts);
|
|
||||||
|
|
||||||
// x > last baked x
|
|
||||||
EXPECT_EQ(std::isfinite(curve.getYForPoint(0.6f)), true);
|
|
||||||
// Far beyond range
|
|
||||||
EXPECT_EQ(std::isfinite(curve.getYForPoint(std::numeric_limits<float>::max())), true);
|
|
||||||
EXPECT_EQ(std::isfinite(curve.getYForPoint(-std::numeric_limits<float>::max())), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_adjacent_baked_x_equal() {
|
|
||||||
// Curve with flat tail (X=1, Y=1)
|
|
||||||
CBezierCurve curve;
|
|
||||||
std::array<Vector2D, 4> pts = {
|
|
||||||
Vector2D{0.0f, 0.0f}, // P0
|
|
||||||
Vector2D{0.2f, 0.2f}, // P1
|
|
||||||
Vector2D{1.0f, 1.0f}, // P2
|
|
||||||
Vector2D{1.0f, 1.0f} // P3
|
|
||||||
};
|
|
||||||
curve.setup4(pts);
|
|
||||||
|
|
||||||
// Exactly at last baked X
|
|
||||||
const float y_at_end = curve.getYForPoint(1.0f);
|
|
||||||
// Slightly beyond last baked X
|
|
||||||
const float y_past_end = curve.getYForPoint(1.0001f);
|
|
||||||
|
|
||||||
EXPECT_EQ(y_at_end, 1.0f);
|
|
||||||
EXPECT_EQ(y_past_end, y_at_end);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_all_baked_x_equal() {
|
|
||||||
// Extreme case: X is constant along the whole curve
|
|
||||||
CBezierCurve curve;
|
|
||||||
std::array<Vector2D, 4> pts = {
|
|
||||||
Vector2D{0.0f, 0.0f}, // P0
|
|
||||||
Vector2D{0.0f, 0.3f}, // P1
|
|
||||||
Vector2D{0.0f, 0.7f}, // P2
|
|
||||||
Vector2D{0.0f, 1.0f} // P3
|
|
||||||
};
|
|
||||||
curve.setup4(pts);
|
|
||||||
|
|
||||||
// Below any baked X
|
|
||||||
const float y_lo = curve.getYForPoint(-100.0f);
|
|
||||||
const float y_0 = curve.getYForPoint(0.0f);
|
|
||||||
// Above any baked X
|
|
||||||
const float y_hi = curve.getYForPoint(100.0f);
|
|
||||||
|
|
||||||
EXPECT_EQ(std::isfinite(y_lo), true);
|
|
||||||
EXPECT_EQ(std::isfinite(y_0), true);
|
|
||||||
EXPECT_EQ(std::isfinite(y_hi), true);
|
|
||||||
|
|
||||||
// For this curve Y should stay within [0,1]
|
|
||||||
EXPECT_EQ((y_lo >= 0.0f && y_lo <= 1.0f), true);
|
|
||||||
EXPECT_EQ((y_0 >= 0.0f && y_0 <= 1.0f), true);
|
|
||||||
EXPECT_EQ((y_hi >= 0.0f && y_hi <= 1.0f), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Animation, beziercurve) {
|
|
||||||
test_nonmonotonic4_clamps_out_of_range();
|
|
||||||
test_adjacent_baked_x_equal();
|
|
||||||
test_all_baked_x_equal();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
|
||||||
298
src/cli/ArgumentParser.cpp
Normal file
298
src/cli/ArgumentParser.cpp
Normal file
|
|
@ -0,0 +1,298 @@
|
||||||
|
#include "ArgumentParser.hpp"
|
||||||
|
|
||||||
|
#include <format>
|
||||||
|
#include <vector>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include <hyprutils/string/String.hpp>
|
||||||
|
#include <hyprutils/memory/Casts.hpp>
|
||||||
|
|
||||||
|
using namespace Hyprutils::CLI;
|
||||||
|
using namespace Hyprutils::Memory;
|
||||||
|
using namespace Hyprutils::String;
|
||||||
|
using namespace Hyprutils;
|
||||||
|
|
||||||
|
CArgumentParser::CArgumentParser(const std::span<const char*>& args) : m_impl(makeUnique<CArgumentParserImpl>(args)) {
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<void, std::string> CArgumentParser::registerBoolOption(const std::string_view& name, const std::string_view& abbrev, const std::string_view& description) {
|
||||||
|
return m_impl->registerOption(name, abbrev, description, ARG_TYPE_BOOL);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<void, std::string> CArgumentParser::registerIntOption(const std::string_view& name, const std::string_view& abbrev, const std::string_view& description) {
|
||||||
|
return m_impl->registerOption(name, abbrev, description, ARG_TYPE_INT);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<void, std::string> CArgumentParser::registerFloatOption(const std::string_view& name, const std::string_view& abbrev, const std::string_view& description) {
|
||||||
|
return m_impl->registerOption(name, abbrev, description, ARG_TYPE_FLOAT);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<void, std::string> CArgumentParser::registerStringOption(const std::string_view& name, const std::string_view& abbrev, const std::string_view& description) {
|
||||||
|
return m_impl->registerOption(name, abbrev, description, ARG_TYPE_STR);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<bool> CArgumentParser::getBool(const std::string_view& name) {
|
||||||
|
auto ref = m_impl->getValue(name);
|
||||||
|
|
||||||
|
if (ref == m_impl->m_values.end())
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
if (const auto pval = std::get_if<bool>(&ref->val); pval)
|
||||||
|
return *pval;
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> CArgumentParser::getInt(const std::string_view& name) {
|
||||||
|
auto ref = m_impl->getValue(name);
|
||||||
|
|
||||||
|
if (ref == m_impl->m_values.end())
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
if (const auto pval = std::get_if<int>(&ref->val); pval)
|
||||||
|
return *pval;
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<float> CArgumentParser::getFloat(const std::string_view& name) {
|
||||||
|
auto ref = m_impl->getValue(name);
|
||||||
|
|
||||||
|
if (ref == m_impl->m_values.end())
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
if (const auto pval = std::get_if<float>(&ref->val); pval)
|
||||||
|
return *pval;
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::string_view> CArgumentParser::getString(const std::string_view& name) {
|
||||||
|
auto ref = m_impl->getValue(name);
|
||||||
|
|
||||||
|
if (ref == m_impl->m_values.end())
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
if (const auto pval = std::get_if<std::string>(&ref->val); pval)
|
||||||
|
return *pval;
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string CArgumentParser::getDescription(const std::string_view& header, std::optional<size_t> maxWidth) {
|
||||||
|
return m_impl->getDescription(header, maxWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<void, std::string> CArgumentParser::parse() {
|
||||||
|
return m_impl->parse();
|
||||||
|
}
|
||||||
|
|
||||||
|
CArgumentParserImpl::CArgumentParserImpl(const std::span<const char*>& args) {
|
||||||
|
m_argv.reserve(args.size());
|
||||||
|
for (const auto& a : args) {
|
||||||
|
m_argv.emplace_back(a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<SArgumentKey>::iterator CArgumentParserImpl::getValue(const std::string_view& sv) {
|
||||||
|
if (sv.empty())
|
||||||
|
return m_values.end();
|
||||||
|
auto it = std::ranges::find_if(m_values, [&sv](const auto& e) { return e.full == sv || e.abbrev == sv; });
|
||||||
|
return it;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<void, std::string> CArgumentParserImpl::registerOption(const std::string_view& name, const std::string_view& abbrev, const std::string_view& description,
|
||||||
|
eArgumentType type) {
|
||||||
|
if (name.empty())
|
||||||
|
return std::unexpected("Name cannot be empty");
|
||||||
|
|
||||||
|
if (getValue(name) != m_values.end() || getValue(abbrev) != m_values.end())
|
||||||
|
return std::unexpected("Value already exists");
|
||||||
|
|
||||||
|
m_values.emplace_back(SArgumentKey{
|
||||||
|
.full = std::string{name},
|
||||||
|
.abbrev = std::string{abbrev},
|
||||||
|
.desc = std::string{description},
|
||||||
|
.argType = type,
|
||||||
|
.val = std::monostate{},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<void, std::string> CArgumentParserImpl::parse() {
|
||||||
|
// walk the args
|
||||||
|
for (size_t i = 1; i < m_argv.size(); ++i) {
|
||||||
|
auto val = m_values.end();
|
||||||
|
const auto& arg = m_argv.at(i);
|
||||||
|
|
||||||
|
if (arg.starts_with("--"))
|
||||||
|
val = getValue(std::string_view{arg}.substr(2));
|
||||||
|
else if (arg.starts_with('-'))
|
||||||
|
val = getValue(std::string_view{arg}.substr(1));
|
||||||
|
else
|
||||||
|
return std::unexpected(std::format("Invalid element found while parsing: {}", arg));
|
||||||
|
|
||||||
|
if (val == m_values.end())
|
||||||
|
return std::unexpected(std::format("Invalid argument found while parsing: {}", arg));
|
||||||
|
|
||||||
|
switch (val->argType) {
|
||||||
|
case ARG_TYPE_BOOL: {
|
||||||
|
val->val = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ARG_TYPE_INT: {
|
||||||
|
if (i + 1 >= m_argv.size())
|
||||||
|
return std::unexpected(std::format("Failed parsing argument {}, no value supplied", arg));
|
||||||
|
const auto& next = std::string{m_argv.at(++i)};
|
||||||
|
if (!isNumber(next))
|
||||||
|
return std::unexpected(std::format("Failed parsing argument {}, value {} is not an int", arg, next));
|
||||||
|
try {
|
||||||
|
val->val = sc<int>(std::stoi(next));
|
||||||
|
} catch (...) { return std::unexpected(std::format("Failed parsing argument {}, value {} is not an int", arg, next)); }
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ARG_TYPE_FLOAT: {
|
||||||
|
if (i + 1 >= m_argv.size())
|
||||||
|
return std::unexpected(std::format("Failed parsing argument {}, no value supplied", arg));
|
||||||
|
const auto& next = std::string{m_argv.at(++i)};
|
||||||
|
if (!isNumber(next, true))
|
||||||
|
return std::unexpected(std::format("Failed parsing argument {}, value {} is not a float", arg, next));
|
||||||
|
try {
|
||||||
|
val->val = sc<float>(std::stof(next));
|
||||||
|
} catch (...) { return std::unexpected(std::format("Failed parsing argument {}, value {} is not a float", arg, next)); }
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ARG_TYPE_STR: {
|
||||||
|
if (i + 1 >= m_argv.size())
|
||||||
|
return std::unexpected(std::format("Failed parsing argument {}, no value supplied", arg));
|
||||||
|
val->val = std::string{m_argv.at(++i)};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case ARG_TYPE_END: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string CArgumentParserImpl::getDescription(const std::string_view& header, std::optional<size_t> maxWidth) {
|
||||||
|
|
||||||
|
const size_t MAX_COLS = maxWidth.value_or(80);
|
||||||
|
const std::string PAD_STR = " ";
|
||||||
|
|
||||||
|
constexpr const std::array<const char*, ARG_TYPE_END> TYPE_STRS = {
|
||||||
|
"", // bool
|
||||||
|
"[int]", // int
|
||||||
|
"[float]", // float
|
||||||
|
"[str]", // str
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
auto wrap = [](const std::string_view& str, size_t maxW) -> std::vector<std::string_view> {
|
||||||
|
std::vector<std::string_view> result;
|
||||||
|
|
||||||
|
// walk word by word
|
||||||
|
size_t nextSpacePos = 0, lastBreakPos = 0;
|
||||||
|
while (true) {
|
||||||
|
size_t lastSpacePos = nextSpacePos;
|
||||||
|
nextSpacePos = str.find(' ', nextSpacePos + 1);
|
||||||
|
|
||||||
|
if (nextSpacePos == std::string::npos)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (nextSpacePos - lastBreakPos > maxW) {
|
||||||
|
if (lastSpacePos - lastBreakPos <= maxW) {
|
||||||
|
// break
|
||||||
|
result.emplace_back(str.substr(lastBreakPos, lastSpacePos - lastBreakPos));
|
||||||
|
lastBreakPos = lastSpacePos + 1;
|
||||||
|
} else {
|
||||||
|
while (lastSpacePos - lastBreakPos > maxW) {
|
||||||
|
// break
|
||||||
|
result.emplace_back(str.substr(lastBreakPos, maxW));
|
||||||
|
lastBreakPos += maxW;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.emplace_back(str.substr(lastBreakPos));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto pad = [&PAD_STR](size_t len) -> std::string_view {
|
||||||
|
if (len >= PAD_STR.size())
|
||||||
|
return PAD_STR;
|
||||||
|
return std::string_view{PAD_STR}.substr(0, len);
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string rolling;
|
||||||
|
rolling += std::format("┏ {}\n", header);
|
||||||
|
rolling += "┣";
|
||||||
|
for (size_t i = 0; i < MAX_COLS; ++i) {
|
||||||
|
rolling += "━";
|
||||||
|
}
|
||||||
|
rolling += "┓\n";
|
||||||
|
|
||||||
|
// get max widths
|
||||||
|
size_t maxArgWidth = 0, maxShortWidth = 0;
|
||||||
|
for (const auto& v : m_values) {
|
||||||
|
maxShortWidth = std::max(maxShortWidth, v.abbrev.size() + 4 + std::string_view{TYPE_STRS[v.argType]}.length());
|
||||||
|
maxArgWidth = std::max(maxArgWidth, v.full.size() + 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// write the table
|
||||||
|
for (const auto& v : m_values) {
|
||||||
|
size_t lenUsed = 0;
|
||||||
|
rolling += "┣ --" + v.full;
|
||||||
|
lenUsed += 3 + v.full.size();
|
||||||
|
rolling += pad(maxArgWidth - lenUsed);
|
||||||
|
lenUsed = maxArgWidth;
|
||||||
|
|
||||||
|
if (!v.abbrev.empty()) {
|
||||||
|
rolling += " -" + v.abbrev;
|
||||||
|
lenUsed += 2 + v.abbrev.size();
|
||||||
|
|
||||||
|
rolling += " ";
|
||||||
|
rolling += TYPE_STRS[v.argType];
|
||||||
|
lenUsed += std::string_view{TYPE_STRS[v.argType]}.length() + 1;
|
||||||
|
rolling += pad(maxArgWidth + maxShortWidth - lenUsed);
|
||||||
|
} else
|
||||||
|
rolling += pad(maxShortWidth);
|
||||||
|
lenUsed = maxArgWidth + maxShortWidth;
|
||||||
|
|
||||||
|
rolling += " | ";
|
||||||
|
lenUsed += 3;
|
||||||
|
|
||||||
|
const auto ROWS = wrap(v.desc, MAX_COLS - lenUsed);
|
||||||
|
|
||||||
|
const auto LEN_START_DESC = lenUsed;
|
||||||
|
|
||||||
|
rolling += ROWS[0];
|
||||||
|
lenUsed += ROWS[0].size();
|
||||||
|
rolling += pad(MAX_COLS - lenUsed);
|
||||||
|
rolling += "┃\n";
|
||||||
|
|
||||||
|
for (size_t i = 1; i < ROWS.size(); ++i) {
|
||||||
|
lenUsed = LEN_START_DESC;
|
||||||
|
rolling += "┃";
|
||||||
|
rolling += pad(LEN_START_DESC);
|
||||||
|
rolling += ROWS[i];
|
||||||
|
lenUsed += ROWS[i].size();
|
||||||
|
rolling += pad(MAX_COLS - lenUsed);
|
||||||
|
rolling += "┃\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rolling += "┗";
|
||||||
|
for (size_t i = 0; i < MAX_COLS; ++i) {
|
||||||
|
rolling += "━";
|
||||||
|
}
|
||||||
|
rolling += "┛\n";
|
||||||
|
|
||||||
|
return rolling;
|
||||||
|
}
|
||||||
39
src/cli/ArgumentParser.hpp
Normal file
39
src/cli/ArgumentParser.hpp
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
#include <hyprutils/cli/ArgumentParser.hpp>
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <variant>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Hyprutils::CLI {
|
||||||
|
enum eArgumentType : uint8_t {
|
||||||
|
ARG_TYPE_BOOL = 0,
|
||||||
|
ARG_TYPE_INT,
|
||||||
|
ARG_TYPE_FLOAT,
|
||||||
|
ARG_TYPE_STR,
|
||||||
|
ARG_TYPE_END,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SArgumentKey {
|
||||||
|
using Value = std::variant<std::monostate, bool, int, float, std::string>;
|
||||||
|
|
||||||
|
std::string full, abbrev, desc;
|
||||||
|
eArgumentType argType = ARG_TYPE_BOOL;
|
||||||
|
|
||||||
|
Value val;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CArgumentParserImpl {
|
||||||
|
public:
|
||||||
|
CArgumentParserImpl(const std::span<const char*>& args);
|
||||||
|
~CArgumentParserImpl() = default;
|
||||||
|
|
||||||
|
std::string getDescription(const std::string_view& header, std::optional<size_t> maxWidth = {});
|
||||||
|
std::expected<void, std::string> parse();
|
||||||
|
std::vector<SArgumentKey>::iterator getValue(const std::string_view& sv);
|
||||||
|
std::expected<void, std::string> registerOption(const std::string_view& name, const std::string_view& abbrev, const std::string_view& description, eArgumentType type);
|
||||||
|
|
||||||
|
std::vector<SArgumentKey> m_values;
|
||||||
|
|
||||||
|
std::vector<std::string_view> m_argv;
|
||||||
|
};
|
||||||
|
}
|
||||||
199
src/cli/Logger.cpp
Normal file
199
src/cli/Logger.cpp
Normal file
|
|
@ -0,0 +1,199 @@
|
||||||
|
#include "Logger.hpp"
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <print>
|
||||||
|
|
||||||
|
using namespace Hyprutils;
|
||||||
|
using namespace Hyprutils::CLI;
|
||||||
|
|
||||||
|
CLogger::CLogger() {
|
||||||
|
m_impl = Memory::makeUnique<CLoggerImpl>(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
CLogger::~CLogger() = default;
|
||||||
|
|
||||||
|
void CLogger::setLogLevel(eLogLevel level) {
|
||||||
|
m_logLevel = level;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CLogger::setTime(bool enabled) {
|
||||||
|
m_impl->m_timeEnabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CLogger::setEnableStdout(bool enabled) {
|
||||||
|
m_impl->m_stdoutEnabled = enabled;
|
||||||
|
m_impl->updateParentShouldLog();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CLogger::setEnableColor(bool enabled) {
|
||||||
|
m_impl->m_colorEnabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CLogger::setEnableRolling(bool enabled) {
|
||||||
|
m_impl->m_rollingEnabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<void, std::string> CLogger::setOutputFile(const std::string_view& file) {
|
||||||
|
if (file.empty()) {
|
||||||
|
m_impl->m_fileEnabled = false;
|
||||||
|
m_impl->m_logOfs = {};
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::filesystem::path filePath{file};
|
||||||
|
std::error_code ec;
|
||||||
|
|
||||||
|
if (!filePath.has_parent_path())
|
||||||
|
return std::unexpected("Path has no parent");
|
||||||
|
|
||||||
|
auto dir = filePath.parent_path();
|
||||||
|
|
||||||
|
if (!std::filesystem::exists(dir, ec) || ec)
|
||||||
|
return std::unexpected("Parent path is inaccessible, or doesn't exist");
|
||||||
|
|
||||||
|
m_impl->m_logOfs = std::ofstream{filePath, std::ios::trunc};
|
||||||
|
m_impl->m_logFilePath = filePath;
|
||||||
|
|
||||||
|
if (!m_impl->m_logOfs.good())
|
||||||
|
return std::unexpected("Failed to open a write stream");
|
||||||
|
|
||||||
|
m_impl->m_fileEnabled = true;
|
||||||
|
m_impl->updateParentShouldLog();
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void CLogger::log(eLogLevel level, const std::string_view& msg) {
|
||||||
|
if (!m_shouldLogAtAll)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (level < m_logLevel)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_impl->log(level, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& CLogger::rollingLog() {
|
||||||
|
return m_impl->m_rollingLog;
|
||||||
|
}
|
||||||
|
|
||||||
|
CLoggerImpl::CLoggerImpl(CLogger* parent) : m_parent(parent) {
|
||||||
|
updateParentShouldLog();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CLoggerImpl::log(eLogLevel level, const std::string_view& msg, const std::string_view& from) {
|
||||||
|
std::lock_guard<std::mutex> lg(m_logMtx);
|
||||||
|
|
||||||
|
std::string logPrefix = "", logPrefixColor = "";
|
||||||
|
std::string logMsg = "";
|
||||||
|
|
||||||
|
switch (level) {
|
||||||
|
case LOG_TRACE:
|
||||||
|
logPrefix += "TRACE ";
|
||||||
|
logPrefixColor += "\033[1;34mTRACE \033[0m";
|
||||||
|
break;
|
||||||
|
case LOG_DEBUG:
|
||||||
|
logPrefix += "DEBUG ";
|
||||||
|
logPrefixColor += "\033[1;32mDEBUG \033[0m";
|
||||||
|
break;
|
||||||
|
case LOG_WARN:
|
||||||
|
logPrefix += "WARN ";
|
||||||
|
logPrefixColor += "\033[1;33mWARN \033[0m";
|
||||||
|
break;
|
||||||
|
case LOG_ERR:
|
||||||
|
logPrefix += "ERR ";
|
||||||
|
logPrefixColor += "\033[1;31mERR \033[0m";
|
||||||
|
break;
|
||||||
|
case LOG_CRIT:
|
||||||
|
logPrefix += "CRIT ";
|
||||||
|
logPrefixColor += "\033[1;35mCRIT \033[0m";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_timeEnabled) {
|
||||||
|
#ifndef _LIBCPP_VERSION
|
||||||
|
static auto current_zone = std::chrono::current_zone();
|
||||||
|
const auto zt = std::chrono::zoned_time{current_zone, std::chrono::system_clock::now()};
|
||||||
|
const auto hms = std::chrono::hh_mm_ss{zt.get_local_time() - std::chrono::floor<std::chrono::days>(zt.get_local_time())};
|
||||||
|
#else
|
||||||
|
// TODO: current clang 17 does not support `zoned_time`, remove this once clang 19 is ready
|
||||||
|
const auto hms = std::chrono::hh_mm_ss{std::chrono::system_clock::now() - std::chrono::floor<std::chrono::days>(std::chrono::system_clock::now())};
|
||||||
|
#endif
|
||||||
|
logMsg += std::format("@ {} ", hms);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!from.empty()) {
|
||||||
|
logMsg += "from ";
|
||||||
|
logMsg += from;
|
||||||
|
logMsg += " ";
|
||||||
|
}
|
||||||
|
|
||||||
|
logMsg += "]: ";
|
||||||
|
logMsg += msg;
|
||||||
|
|
||||||
|
if (m_stdoutEnabled) {
|
||||||
|
try {
|
||||||
|
std::println("{}{}", m_colorEnabled ? logPrefixColor : logPrefix, logMsg);
|
||||||
|
std::fflush(stdout);
|
||||||
|
} catch (std::exception& e) {
|
||||||
|
; // this could be e.g. stdout closed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (m_fileEnabled)
|
||||||
|
m_logOfs << logPrefix << logMsg << "\n";
|
||||||
|
|
||||||
|
if (m_rollingEnabled)
|
||||||
|
appendToRolling(logPrefix + logMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CLoggerImpl::updateParentShouldLog() {
|
||||||
|
m_parent->m_shouldLogAtAll = m_fileEnabled || m_stdoutEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CLoggerImpl::appendToRolling(const std::string& s) {
|
||||||
|
constexpr const size_t ROLLING_LOG_SIZE = 4096;
|
||||||
|
if (!m_rollingLog.empty())
|
||||||
|
m_rollingLog += "\n";
|
||||||
|
m_rollingLog += s;
|
||||||
|
if (m_rollingLog.size() > ROLLING_LOG_SIZE)
|
||||||
|
m_rollingLog = m_rollingLog.substr(m_rollingLog.find('\n', m_rollingLog.size() - ROLLING_LOG_SIZE) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
CLoggerConnection::CLoggerConnection(CLogger& logger) : m_impl(logger.m_impl), m_logger(&logger), m_logLevel(logger.m_logLevel) {
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
CLoggerConnection::~CLoggerConnection() = default;
|
||||||
|
|
||||||
|
void CLoggerConnection::setName(const std::string_view& name) {
|
||||||
|
m_name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CLoggerConnection::setLogLevel(eLogLevel level) {
|
||||||
|
m_logLevel = level;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CLoggerConnection::log(eLogLevel level, const std::string_view& msg) {
|
||||||
|
if (!m_impl || !m_logger)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!m_logger->m_shouldLogAtAll)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (level < m_logLevel)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_impl->log(level, msg, m_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
CLogger* CLoggerConnection::getLogger() {
|
||||||
|
if (!m_impl)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
return m_logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CLoggerConnection::redirect(CLogger& logger) {
|
||||||
|
m_impl = logger.m_impl;
|
||||||
|
m_logger = &logger;
|
||||||
|
}
|
||||||
35
src/cli/Logger.hpp
Normal file
35
src/cli/Logger.hpp
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
#include <hyprutils/cli/Logger.hpp>
|
||||||
|
#include <fstream>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
namespace Hyprutils::CLI {
|
||||||
|
class CLoggerImpl {
|
||||||
|
public:
|
||||||
|
CLoggerImpl(CLogger*);
|
||||||
|
~CLoggerImpl() = default;
|
||||||
|
|
||||||
|
CLoggerImpl(const CLoggerImpl&) = delete;
|
||||||
|
CLoggerImpl(CLoggerImpl&) = delete;
|
||||||
|
CLoggerImpl(CLoggerImpl&&) = delete;
|
||||||
|
|
||||||
|
void updateParentShouldLog();
|
||||||
|
void appendToRolling(const std::string& s);
|
||||||
|
void log(eLogLevel level, const std::string_view& msg, const std::string_view& from = "");
|
||||||
|
|
||||||
|
std::string m_rollingLog;
|
||||||
|
std::ofstream m_logOfs;
|
||||||
|
std::filesystem::path m_logFilePath;
|
||||||
|
|
||||||
|
bool m_timeEnabled = false;
|
||||||
|
bool m_stdoutEnabled = true;
|
||||||
|
bool m_fileEnabled = false;
|
||||||
|
bool m_colorEnabled = true;
|
||||||
|
bool m_rollingEnabled = false;
|
||||||
|
|
||||||
|
std::mutex m_logMtx;
|
||||||
|
|
||||||
|
// this is fine because CLogger is NOMOVE and NOCOPY
|
||||||
|
CLogger* m_parent = nullptr;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -3,11 +3,11 @@
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <format>
|
#include <format>
|
||||||
#include <locale>
|
#include <locale>
|
||||||
#include <hyprutils/string/String.hpp>
|
#include <hyprutils/utils/ScopeGuard.hpp>
|
||||||
|
|
||||||
using namespace Hyprutils::I18n;
|
using namespace Hyprutils::I18n;
|
||||||
using namespace Hyprutils;
|
using namespace Hyprutils;
|
||||||
using namespace Hyprutils::String;
|
using namespace Hyprutils::Utils;
|
||||||
|
|
||||||
CI18nEngine::CI18nEngine() : m_impl(Memory::makeUnique<SI18nEngineImpl>()) {
|
CI18nEngine::CI18nEngine() : m_impl(Memory::makeUnique<SI18nEngineImpl>()) {
|
||||||
;
|
;
|
||||||
|
|
@ -59,9 +59,10 @@ std::string CI18nEngine::localizeEntry(const std::string& locale, uint64_t key,
|
||||||
if (!entry || !entry->exists) {
|
if (!entry || !entry->exists) {
|
||||||
// try to fall back to any lang prefixed with our prefix
|
// try to fall back to any lang prefixed with our prefix
|
||||||
|
|
||||||
auto stem = locale.substr(0, locale.find('_') + 1);
|
const auto stem = locale.substr(0, locale.find('_') + 1);
|
||||||
|
const auto stemRaw = locale.substr(0, locale.find('_'));
|
||||||
for (const auto& [k, v] : m_impl->entries) {
|
for (const auto& [k, v] : m_impl->entries) {
|
||||||
if (k.starts_with(stem) || k == stem) {
|
if (k.starts_with(stem) || k == stemRaw) {
|
||||||
if (m_impl->entries.contains(k) && m_impl->entries[k].size() > key)
|
if (m_impl->entries.contains(k) && m_impl->entries[k].size() > key)
|
||||||
entry = &m_impl->entries[k][key];
|
entry = &m_impl->entries[k][key];
|
||||||
|
|
||||||
|
|
@ -93,16 +94,68 @@ std::string CI18nEngine::localizeEntry(const std::string& locale, uint64_t key,
|
||||||
if (!entry || !entry->exists)
|
if (!entry || !entry->exists)
|
||||||
return "";
|
return "";
|
||||||
|
|
||||||
std::string rawStr = entry->entry;
|
std::string_view rawStr = entry->entry;
|
||||||
|
std::string fnStringContainer;
|
||||||
|
|
||||||
if (entry->fn)
|
if (entry->fn) {
|
||||||
rawStr = entry->fn(map);
|
fnStringContainer = entry->fn(map);
|
||||||
|
rawStr = fnStringContainer;
|
||||||
for (const auto& e : map) {
|
|
||||||
replaceInString(rawStr, "{" + e.first + "}", e.second);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return rawStr;
|
struct SRange {
|
||||||
|
size_t begin = 0;
|
||||||
|
size_t end = 0;
|
||||||
|
const std::string* val = nullptr;
|
||||||
|
};
|
||||||
|
std::vector<SRange> rangesFound;
|
||||||
|
|
||||||
|
// discover all replacable ranges
|
||||||
|
for (const auto& [k, v] : map) {
|
||||||
|
size_t start = rawStr.find(k, 0);
|
||||||
|
while (start != std::string::npos) {
|
||||||
|
if (start == 0 || start + k.size() >= rawStr.size())
|
||||||
|
break;
|
||||||
|
|
||||||
|
// always move the pointer
|
||||||
|
CScopeGuard x([&start, &rawStr, &k] { start = rawStr.find(k, start + 1); });
|
||||||
|
|
||||||
|
if (rawStr[start - 1] != '{' || rawStr[start + k.size()] != '}')
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// add range
|
||||||
|
rangesFound.emplace_back(SRange{.begin = start - 1, .end = start + k.size() + 1, .val = &v});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rangesFound.empty())
|
||||||
|
return std::string{rawStr};
|
||||||
|
|
||||||
|
// build the new string. First, sort our entries
|
||||||
|
std::ranges::sort(rangesFound, [](const auto& a, const auto& b) { return a.begin < b.begin; });
|
||||||
|
|
||||||
|
// calc the size
|
||||||
|
size_t stringLen = 0;
|
||||||
|
size_t lastBegin = 0;
|
||||||
|
for (const auto& r : rangesFound) {
|
||||||
|
stringLen += r.begin - lastBegin + r.val->size();
|
||||||
|
lastBegin = r.end;
|
||||||
|
}
|
||||||
|
stringLen += rawStr.size() - lastBegin;
|
||||||
|
|
||||||
|
lastBegin = 0;
|
||||||
|
const auto ORIGINAL_STR = std::string_view{rawStr};
|
||||||
|
std::string newStr;
|
||||||
|
newStr.reserve(stringLen);
|
||||||
|
|
||||||
|
for (const auto& r : rangesFound) {
|
||||||
|
newStr += ORIGINAL_STR.substr(lastBegin, r.begin - lastBegin);
|
||||||
|
newStr += *r.val;
|
||||||
|
|
||||||
|
lastBegin = r.end;
|
||||||
|
}
|
||||||
|
newStr += ORIGINAL_STR.substr(lastBegin);
|
||||||
|
|
||||||
|
return newStr;
|
||||||
}
|
}
|
||||||
|
|
||||||
CI18nLocale CI18nEngine::getSystemLocale() {
|
CI18nLocale CI18nEngine::getSystemLocale() {
|
||||||
|
|
@ -110,65 +163,3 @@ CI18nLocale CI18nEngine::getSystemLocale() {
|
||||||
return CI18nLocale(std::locale("").name());
|
return CI18nLocale(std::locale("").name());
|
||||||
} catch (...) { return CI18nLocale("en_US.UTF-8"); }
|
} catch (...) { return CI18nLocale("en_US.UTF-8"); }
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HU_UNIT_TESTS
|
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
|
||||||
|
|
||||||
enum eTxtKeys : uint64_t {
|
|
||||||
TXT_KEY_HELLO,
|
|
||||||
TXT_KEY_I_HAVE_APPLES,
|
|
||||||
TXT_KEY_FALLBACK,
|
|
||||||
};
|
|
||||||
|
|
||||||
TEST(I18n, Engine) {
|
|
||||||
CI18nEngine engine;
|
|
||||||
|
|
||||||
engine.setFallbackLocale("en_US");
|
|
||||||
|
|
||||||
engine.registerEntry("en_US", TXT_KEY_HELLO, "Hello World!");
|
|
||||||
engine.registerEntry("en_US", TXT_KEY_I_HAVE_APPLES, [](const translationVarMap& m) {
|
|
||||||
if (std::stoi(m.at("count")) == 1)
|
|
||||||
return "I have {count} apple.";
|
|
||||||
else
|
|
||||||
return "I have {count} apples.";
|
|
||||||
});
|
|
||||||
engine.registerEntry("en_US", TXT_KEY_FALLBACK, "Fallback string!");
|
|
||||||
|
|
||||||
engine.registerEntry("pl_PL", TXT_KEY_HELLO, "Witaj świecie!");
|
|
||||||
engine.registerEntry("pl_PL", TXT_KEY_I_HAVE_APPLES, [](const translationVarMap& m) {
|
|
||||||
const auto COUNT = std::stoi(m.at("count"));
|
|
||||||
if (COUNT == 1)
|
|
||||||
return "Mam {count} jabłko.";
|
|
||||||
else if (COUNT < 5)
|
|
||||||
return "Mam {count} jabłka.";
|
|
||||||
else
|
|
||||||
return "Mam {count} jabłek.";
|
|
||||||
});
|
|
||||||
|
|
||||||
engine.registerEntry("es_XX", TXT_KEY_FALLBACK, "I don't speak spanish");
|
|
||||||
engine.registerEntry("es_ES", TXT_KEY_FALLBACK, "I don't speak spanish here either");
|
|
||||||
|
|
||||||
EXPECT_EQ(engine.localizeEntry("en_US", TXT_KEY_HELLO, {}), "Hello World!");
|
|
||||||
EXPECT_EQ(engine.localizeEntry("pl_PL", TXT_KEY_HELLO, {}), "Witaj świecie!");
|
|
||||||
EXPECT_EQ(engine.localizeEntry("de_DE", TXT_KEY_HELLO, {}), "Hello World!");
|
|
||||||
|
|
||||||
EXPECT_EQ(engine.localizeEntry("en_US", TXT_KEY_I_HAVE_APPLES, {{"count", "1"}}), "I have 1 apple.");
|
|
||||||
EXPECT_EQ(engine.localizeEntry("en_US", TXT_KEY_I_HAVE_APPLES, {{"count", "2"}}), "I have 2 apples.");
|
|
||||||
|
|
||||||
EXPECT_EQ(engine.localizeEntry("pl_PL", TXT_KEY_I_HAVE_APPLES, {{"count", "1"}}), "Mam 1 jabłko.");
|
|
||||||
EXPECT_EQ(engine.localizeEntry("pl_PL", TXT_KEY_I_HAVE_APPLES, {{"count", "2"}}), "Mam 2 jabłka.");
|
|
||||||
EXPECT_EQ(engine.localizeEntry("pl_PL", TXT_KEY_I_HAVE_APPLES, {{"count", "5"}}), "Mam 5 jabłek.");
|
|
||||||
|
|
||||||
EXPECT_EQ(engine.localizeEntry("pl", TXT_KEY_I_HAVE_APPLES, {{"count", "5"}}), "Mam 5 jabłek.");
|
|
||||||
|
|
||||||
EXPECT_EQ(engine.localizeEntry("pl_XX", TXT_KEY_I_HAVE_APPLES, {{"count", "5"}}), "Mam 5 jabłek.");
|
|
||||||
EXPECT_EQ(engine.localizeEntry("en_XX", TXT_KEY_I_HAVE_APPLES, {{"count", "2"}}), "I have 2 apples.");
|
|
||||||
|
|
||||||
EXPECT_EQ(engine.localizeEntry("es_YY", TXT_KEY_FALLBACK, {}), "I don't speak spanish here either");
|
|
||||||
EXPECT_EQ(engine.localizeEntry("es_XX", TXT_KEY_FALLBACK, {}), "I don't speak spanish");
|
|
||||||
|
|
||||||
EXPECT_EQ(engine.localizeEntry("pl_PL", TXT_KEY_FALLBACK, {}), "Fallback string!");
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
|
||||||
|
|
@ -13,4 +13,6 @@ namespace Hyprutils::I18n {
|
||||||
std::unordered_map<std::string, std::vector<SI18nTranslationEntry>> entries;
|
std::unordered_map<std::string, std::vector<SI18nTranslationEntry>> entries;
|
||||||
std::string fallbackLocale = "en_US";
|
std::string fallbackLocale = "en_US";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
std::string extractLocale(std::string locale);
|
||||||
};
|
};
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
#include <hyprutils/i18n/I18nEngine.hpp>
|
#include "I18nEngine.hpp"
|
||||||
|
|
||||||
using namespace Hyprutils::I18n;
|
using namespace Hyprutils::I18n;
|
||||||
|
|
||||||
static std::string extractLocale(std::string locale) {
|
std::string Hyprutils::I18n::extractLocale(std::string locale) {
|
||||||
// localeStr is very arbitrary... from my testing, it can be:
|
// localeStr is very arbitrary... from my testing, it can be:
|
||||||
// en_US.UTF-8
|
// en_US.UTF-8
|
||||||
// LC_CTYPE=en_US
|
// LC_CTYPE=en_US
|
||||||
|
|
@ -42,19 +42,3 @@ std::string CI18nLocale::stem() {
|
||||||
std::string CI18nLocale::full() {
|
std::string CI18nLocale::full() {
|
||||||
return m_rawFullLocale;
|
return m_rawFullLocale;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HU_UNIT_TESTS
|
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
|
||||||
#include <hyprutils/i18n/I18nEngine.hpp>
|
|
||||||
|
|
||||||
TEST(I18n, Locale) {
|
|
||||||
CI18nEngine engine;
|
|
||||||
|
|
||||||
EXPECT_EQ(extractLocale("pl_PL.UTF-8"), "pl_PL");
|
|
||||||
EXPECT_EQ(extractLocale("POSIX"), "en_US");
|
|
||||||
EXPECT_EQ(extractLocale("*"), "en_US");
|
|
||||||
EXPECT_EQ(extractLocale("LC_CTYPE=pl_PL.UTF-8"), "pl_PL");
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
|
||||||
|
|
@ -235,78 +235,3 @@ Vector2D Hyprutils::Math::CBox::closestPoint(const Vector2D& vec) const {
|
||||||
SBoxExtents Hyprutils::Math::CBox::extentsFrom(const CBox& small) {
|
SBoxExtents Hyprutils::Math::CBox::extentsFrom(const CBox& small) {
|
||||||
return {.topLeft = {small.x - x, small.y - y}, .bottomRight = {w - small.w - (small.x - x), h - small.h - (small.y - y)}};
|
return {.topLeft = {small.x - x, small.y - y}, .bottomRight = {w - small.w - (small.x - x), h - small.h - (small.y - y)}};
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HU_UNIT_TESTS
|
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
|
||||||
|
|
||||||
TEST(Math, box) {
|
|
||||||
// Test default constructor and accessors
|
|
||||||
{
|
|
||||||
CBox box1;
|
|
||||||
EXPECT_EQ(box1.x, 0);
|
|
||||||
EXPECT_EQ(box1.y, 0);
|
|
||||||
EXPECT_EQ(box1.width, 0);
|
|
||||||
EXPECT_EQ(box1.height, 0);
|
|
||||||
|
|
||||||
// Test parameterized constructor and accessors
|
|
||||||
CBox box2(10, 20, 30, 40);
|
|
||||||
EXPECT_EQ(box2.x, 10);
|
|
||||||
EXPECT_EQ(box2.y, 20);
|
|
||||||
EXPECT_EQ(box2.width, 30);
|
|
||||||
EXPECT_EQ(box2.height, 40);
|
|
||||||
|
|
||||||
// Test setters and getters
|
|
||||||
box2.translate(Vector2D(5, -5));
|
|
||||||
EXPECT_EQ(box2.pos(), Vector2D(15, 15));
|
|
||||||
}
|
|
||||||
|
|
||||||
//Test Scaling and Transformation
|
|
||||||
{
|
|
||||||
CBox box(10, 10, 20, 30);
|
|
||||||
|
|
||||||
// Test scaling
|
|
||||||
box.scale(2.0);
|
|
||||||
EXPECT_EQ(box.size(), Vector2D(40, 60));
|
|
||||||
EXPECT_EQ(box.pos(), Vector2D(20, 20));
|
|
||||||
|
|
||||||
// Test scaling from center
|
|
||||||
box.scaleFromCenter(0.5);
|
|
||||||
EXPECT_EQ(box.size(), Vector2D(20, 30));
|
|
||||||
EXPECT_EQ(box.pos(), Vector2D(30, 35));
|
|
||||||
|
|
||||||
// Test transformation
|
|
||||||
box.transform(HYPRUTILS_TRANSFORM_90, 100, 200);
|
|
||||||
EXPECT_EQ(box.pos(), Vector2D(135, 30));
|
|
||||||
EXPECT_EQ(box.size(), Vector2D(30, 20));
|
|
||||||
|
|
||||||
// Test Intersection and Extents
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
CBox box1(0, 0, 100, 100);
|
|
||||||
CBox box2(50, 50, 100, 100);
|
|
||||||
|
|
||||||
CBox intersection = box1.intersection(box2);
|
|
||||||
EXPECT_EQ(intersection.pos(), Vector2D(50, 50));
|
|
||||||
EXPECT_EQ(intersection.size(), Vector2D(50, 50));
|
|
||||||
|
|
||||||
SBoxExtents extents = box1.extentsFrom(box2);
|
|
||||||
EXPECT_EQ(extents.topLeft, Vector2D(50, 50));
|
|
||||||
EXPECT_EQ(extents.bottomRight, Vector2D(-50, -50));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test Boundary Conditions and Special Cases
|
|
||||||
{
|
|
||||||
CBox box(0, 0, 50, 50);
|
|
||||||
|
|
||||||
EXPECT_EQ(box.empty(), false);
|
|
||||||
|
|
||||||
EXPECT_EQ(box.containsPoint(Vector2D(25, 25)), true);
|
|
||||||
EXPECT_EQ(box.containsPoint(Vector2D(60, 60)), false);
|
|
||||||
EXPECT_EQ(box.overlaps(CBox(25, 25, 50, 50)), true);
|
|
||||||
EXPECT_EQ(box.inside(CBox(0, 0, 100, 100)), false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
|
||||||
|
|
@ -153,26 +153,3 @@ std::string Mat3x3::toString() const {
|
||||||
return std::format("[mat3x3: {}, {}, {}, {}, {}, {}, {}, {}, {}]", matrix.at(0), matrix.at(1), matrix.at(2), matrix.at(3), matrix.at(4), matrix.at(5), matrix.at(6),
|
return std::format("[mat3x3: {}, {}, {}, {}, {}, {}, {}, {}, {}]", matrix.at(0), matrix.at(1), matrix.at(2), matrix.at(3), matrix.at(4), matrix.at(5), matrix.at(6),
|
||||||
matrix.at(7), matrix.at(8));
|
matrix.at(7), matrix.at(8));
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HU_UNIT_TESTS
|
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
|
||||||
|
|
||||||
TEST(Math, mat3x3) {
|
|
||||||
Mat3x3 jeremy = Mat3x3::outputProjection({1920, 1080}, HYPRUTILS_TRANSFORM_FLIPPED_90);
|
|
||||||
Mat3x3 matrixBox = jeremy.projectBox(CBox{10, 10, 200, 200}, HYPRUTILS_TRANSFORM_NORMAL).translate({100, 100}).scale({1.25F, 1.5F}).transpose();
|
|
||||||
|
|
||||||
Mat3x3 expected = std::array<float, 9>{0, 0.46296296, 0, 0.3125, 0, 0, 19.84375, 36.055557, 1};
|
|
||||||
// we need to do this to avoid precision errors on 32-bit archs
|
|
||||||
EXPECT_EQ(std::abs(expected.getMatrix().at(0) - matrixBox.getMatrix().at(0)) < 0.1, true);
|
|
||||||
EXPECT_EQ(std::abs(expected.getMatrix().at(1) - matrixBox.getMatrix().at(1)) < 0.1, true);
|
|
||||||
EXPECT_EQ(std::abs(expected.getMatrix().at(2) - matrixBox.getMatrix().at(2)) < 0.1, true);
|
|
||||||
EXPECT_EQ(std::abs(expected.getMatrix().at(3) - matrixBox.getMatrix().at(3)) < 0.1, true);
|
|
||||||
EXPECT_EQ(std::abs(expected.getMatrix().at(4) - matrixBox.getMatrix().at(4)) < 0.1, true);
|
|
||||||
EXPECT_EQ(std::abs(expected.getMatrix().at(5) - matrixBox.getMatrix().at(5)) < 0.1, true);
|
|
||||||
EXPECT_EQ(std::abs(expected.getMatrix().at(6) - matrixBox.getMatrix().at(6)) < 0.1, true);
|
|
||||||
EXPECT_EQ(std::abs(expected.getMatrix().at(7) - matrixBox.getMatrix().at(7)) < 0.1, true);
|
|
||||||
EXPECT_EQ(std::abs(expected.getMatrix().at(8) - matrixBox.getMatrix().at(8)) < 0.1, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
|
||||||
|
|
@ -220,22 +220,3 @@ Vector2D Hyprutils::Math::CRegion::closestPoint(const Vector2D& vec) const {
|
||||||
|
|
||||||
return leader;
|
return leader;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HU_UNIT_TESTS
|
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
|
||||||
|
|
||||||
TEST(Math, region) {
|
|
||||||
CRegion rg(CBox{{20, 20}, {40, 40}});
|
|
||||||
|
|
||||||
auto extents = rg.getExtents();
|
|
||||||
EXPECT_EQ(extents.pos(), Vector2D(20, 20));
|
|
||||||
EXPECT_EQ(extents.size(), Vector2D(40, 40));
|
|
||||||
|
|
||||||
rg.scale(2);
|
|
||||||
extents = rg.getExtents();
|
|
||||||
EXPECT_EQ(extents.pos(), Vector2D(40, 40));
|
|
||||||
EXPECT_EQ(extents.size(), Vector2D(80, 80));
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
|
||||||
|
|
@ -57,23 +57,3 @@ Vector2D Hyprutils::Math::Vector2D::transform(eTransform transform, const Vector
|
||||||
default: return *this;
|
default: return *this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HU_UNIT_TESTS
|
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
|
||||||
|
|
||||||
TEST(Math, vector2d) {
|
|
||||||
Vector2D original(30, 40);
|
|
||||||
Vector2D monitorSize(100, 200);
|
|
||||||
|
|
||||||
EXPECT_EQ(original.transform(HYPRUTILS_TRANSFORM_NORMAL, monitorSize), Vector2D(30, 40));
|
|
||||||
EXPECT_EQ(original.transform(HYPRUTILS_TRANSFORM_90, monitorSize), Vector2D(40, 200 - 30));
|
|
||||||
EXPECT_EQ(original.transform(HYPRUTILS_TRANSFORM_180, monitorSize), Vector2D(100 - 30, 200 - 40));
|
|
||||||
EXPECT_EQ(original.transform(HYPRUTILS_TRANSFORM_270, monitorSize), Vector2D(100 - 40, 30));
|
|
||||||
EXPECT_EQ(original.transform(HYPRUTILS_TRANSFORM_FLIPPED, monitorSize), Vector2D(100 - 30, 40));
|
|
||||||
EXPECT_EQ(original.transform(HYPRUTILS_TRANSFORM_FLIPPED_90, monitorSize), Vector2D(40, 30));
|
|
||||||
EXPECT_EQ(original.transform(HYPRUTILS_TRANSFORM_FLIPPED_180, monitorSize), Vector2D(30, 200 - 40));
|
|
||||||
EXPECT_EQ(original.transform(HYPRUTILS_TRANSFORM_FLIPPED_270, monitorSize), Vector2D(100 - 40, 200 - 30));
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
|
||||||
20
src/os/File.cpp
Normal file
20
src/os/File.cpp
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
#include <hyprutils/os/File.hpp>
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
using namespace Hyprutils;
|
||||||
|
using namespace Hyprutils::File;
|
||||||
|
|
||||||
|
std::expected<std::string, std::string> File::readFileAsString(const std::string_view& path) {
|
||||||
|
std::error_code ec;
|
||||||
|
|
||||||
|
if (!std::filesystem::exists(path, ec) || ec)
|
||||||
|
return std::unexpected("File not found");
|
||||||
|
|
||||||
|
std::ifstream file(std::string{path});
|
||||||
|
if (!file.good())
|
||||||
|
return std::unexpected("Failed to open file");
|
||||||
|
|
||||||
|
return std::string((std::istreambuf_iterator<char>(file)), (std::istreambuf_iterator<char>()));
|
||||||
|
}
|
||||||
|
|
@ -84,49 +84,3 @@ bool CFileDescriptor::isReadable(int fd) {
|
||||||
|
|
||||||
return poll(&pfd, 1, 0) > 0 && (pfd.revents & POLLIN);
|
return poll(&pfd, 1, 0) > 0 && (pfd.revents & POLLIN);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HU_UNIT_TESTS
|
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
|
||||||
#include <sys/mman.h>
|
|
||||||
|
|
||||||
TEST(OS, fd) {
|
|
||||||
std::string name = "/test_filedescriptors";
|
|
||||||
CFileDescriptor fd(shm_open(name.c_str(), O_RDWR | O_CREAT | O_EXCL, 0600));
|
|
||||||
|
|
||||||
EXPECT_EQ(fd.isValid(), true);
|
|
||||||
EXPECT_EQ(fd.isReadable(), true);
|
|
||||||
|
|
||||||
int flags = fd.getFlags();
|
|
||||||
EXPECT_EQ(fd.getFlags(), FD_CLOEXEC);
|
|
||||||
flags &= ~FD_CLOEXEC;
|
|
||||||
fd.setFlags(flags);
|
|
||||||
EXPECT_EQ(fd.getFlags(), !FD_CLOEXEC);
|
|
||||||
|
|
||||||
CFileDescriptor fd2 = fd.duplicate();
|
|
||||||
EXPECT_EQ(fd.isValid(), true);
|
|
||||||
EXPECT_EQ(fd.isReadable(), true);
|
|
||||||
EXPECT_EQ(fd2.isValid(), true);
|
|
||||||
EXPECT_EQ(fd2.isReadable(), true);
|
|
||||||
|
|
||||||
CFileDescriptor fd3(fd2.take());
|
|
||||||
EXPECT_EQ(fd.isValid(), true);
|
|
||||||
EXPECT_EQ(fd.isReadable(), true);
|
|
||||||
EXPECT_EQ(fd2.isValid(), false);
|
|
||||||
EXPECT_EQ(fd2.isReadable(), false);
|
|
||||||
|
|
||||||
// .duplicate default flags is FD_CLOEXEC
|
|
||||||
EXPECT_EQ(fd3.getFlags(), FD_CLOEXEC);
|
|
||||||
|
|
||||||
fd.reset();
|
|
||||||
fd2.reset();
|
|
||||||
fd3.reset();
|
|
||||||
|
|
||||||
EXPECT_EQ(fd.isReadable(), false);
|
|
||||||
EXPECT_EQ(fd2.isReadable(), false);
|
|
||||||
EXPECT_EQ(fd3.isReadable(), false);
|
|
||||||
|
|
||||||
shm_unlink(name.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
|
||||||
|
|
@ -283,32 +283,3 @@ void Hyprutils::OS::CProcess::setStdoutFD(int fd) {
|
||||||
void Hyprutils::OS::CProcess::setStderrFD(int fd) {
|
void Hyprutils::OS::CProcess::setStderrFD(int fd) {
|
||||||
m_impl->stderrFD = fd;
|
m_impl->stderrFD = fd;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HU_UNIT_TESTS
|
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
|
||||||
|
|
||||||
TEST(OS, process) {
|
|
||||||
CProcess process("sh", {"-c", "echo \"Hello $WORLD!\""});
|
|
||||||
process.addEnv("WORLD", "World");
|
|
||||||
|
|
||||||
EXPECT_EQ(process.runAsync(), true);
|
|
||||||
EXPECT_EQ(process.runSync(), true);
|
|
||||||
|
|
||||||
EXPECT_EQ(process.stdOut(), std::string{"Hello World!\n"});
|
|
||||||
EXPECT_EQ(process.stdErr(), std::string{""});
|
|
||||||
EXPECT_EQ(process.exitCode(), 0);
|
|
||||||
|
|
||||||
CProcess process2("sh", {"-c", "while true; do sleep 1; done;"});
|
|
||||||
|
|
||||||
EXPECT_EQ(process2.runAsync(), true);
|
|
||||||
EXPECT_EQ(getpgid(process2.pid()) >= 0, true);
|
|
||||||
|
|
||||||
kill(process2.pid(), SIGKILL);
|
|
||||||
|
|
||||||
CProcess process3("sh", {"-c", "cat /geryueruggbuergheruger/reugiheruygyuerghuryeghyer/eruihgyuerguyerghyuerghuyergerguyer/NON_EXISTENT"});
|
|
||||||
EXPECT_EQ(process3.runSync(), true);
|
|
||||||
EXPECT_EQ(process3.exitCode(), 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
|
||||||
|
|
@ -58,376 +58,3 @@ void Hyprutils::Signal::CSignalBase::registerStaticListenerInternal(std::functio
|
||||||
void Hyprutils::Signal::CSignal::emit(std::any data) {
|
void Hyprutils::Signal::CSignal::emit(std::any data) {
|
||||||
CSignalT::emit(data);
|
CSignalT::emit(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HU_UNIT_TESTS
|
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
|
||||||
|
|
||||||
#include <any>
|
|
||||||
#include <hyprutils/signal/Signal.hpp>
|
|
||||||
#include <hyprutils/signal/Listener.hpp>
|
|
||||||
#include <hyprutils/memory/WeakPtr.hpp>
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
using namespace Hyprutils::Signal;
|
|
||||||
using namespace Hyprutils::Memory;
|
|
||||||
|
|
||||||
#pragma GCC diagnostic push
|
|
||||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
|
||||||
//
|
|
||||||
|
|
||||||
static void legacy() {
|
|
||||||
CSignal signal;
|
|
||||||
int data = 0;
|
|
||||||
auto listener = signal.registerListener([&]([[maybe_unused]] std::any d) { data = 1; });
|
|
||||||
|
|
||||||
signal.emit();
|
|
||||||
|
|
||||||
EXPECT_EQ(data, 1);
|
|
||||||
|
|
||||||
data = 0;
|
|
||||||
|
|
||||||
listener.reset();
|
|
||||||
|
|
||||||
signal.emit();
|
|
||||||
|
|
||||||
EXPECT_EQ(data, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void legacyListenerEmit() {
|
|
||||||
int data = 0;
|
|
||||||
CSignal signal;
|
|
||||||
auto listener = signal.registerListener([&](std::any d) { data = std::any_cast<int>(d); });
|
|
||||||
|
|
||||||
listener->emit(1); // not a typo
|
|
||||||
EXPECT_EQ(data, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void legacyListeners() {
|
|
||||||
int data = 0;
|
|
||||||
|
|
||||||
CSignalT<> signal0;
|
|
||||||
CSignalT<int> signal1;
|
|
||||||
|
|
||||||
auto listener0 = signal0.registerListener([&](std::any d) { data += 1; });
|
|
||||||
auto listener1 = signal1.registerListener([&](std::any d) { data += std::any_cast<int>(d); });
|
|
||||||
|
|
||||||
signal0.registerStaticListener([&](void* o, std::any d) { data += 10; }, nullptr);
|
|
||||||
signal1.registerStaticListener([&](void* o, std::any d) { data += std::any_cast<int>(d) * 10; }, nullptr);
|
|
||||||
|
|
||||||
signal0.emit();
|
|
||||||
signal1.emit(2);
|
|
||||||
|
|
||||||
EXPECT_EQ(data, 33);
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma GCC diagnostic pop
|
|
||||||
//
|
|
||||||
|
|
||||||
static void empty() {
|
|
||||||
int data = 0;
|
|
||||||
|
|
||||||
CSignalT<> signal;
|
|
||||||
auto listener = signal.listen([&] { data = 1; });
|
|
||||||
|
|
||||||
signal.emit();
|
|
||||||
EXPECT_EQ(data, 1);
|
|
||||||
|
|
||||||
data = 0;
|
|
||||||
listener.reset();
|
|
||||||
signal.emit();
|
|
||||||
EXPECT_EQ(data, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void typed() {
|
|
||||||
int data = 0;
|
|
||||||
|
|
||||||
CSignalT<int> signal;
|
|
||||||
auto listener = signal.listen([&](int newData) { data = newData; });
|
|
||||||
|
|
||||||
signal.emit(1);
|
|
||||||
EXPECT_EQ(data, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void ignoreParams() {
|
|
||||||
int data = 0;
|
|
||||||
|
|
||||||
CSignalT<int> signal;
|
|
||||||
auto listener = signal.listen([&] { data += 1; });
|
|
||||||
|
|
||||||
signal.listenStatic([&] { data += 1; });
|
|
||||||
|
|
||||||
signal.emit(2);
|
|
||||||
EXPECT_EQ(data, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void typedMany() {
|
|
||||||
int data1 = 0;
|
|
||||||
int data2 = 0;
|
|
||||||
int data3 = 0;
|
|
||||||
|
|
||||||
CSignalT<int, int, int> signal;
|
|
||||||
auto listener = signal.listen([&](int d1, int d2, int d3) {
|
|
||||||
data1 = d1;
|
|
||||||
data2 = d2;
|
|
||||||
data3 = d3;
|
|
||||||
});
|
|
||||||
|
|
||||||
signal.emit(1, 2, 3);
|
|
||||||
EXPECT_EQ(data1, 1);
|
|
||||||
EXPECT_EQ(data2, 2);
|
|
||||||
EXPECT_EQ(data3, 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void ref() {
|
|
||||||
int count = 0;
|
|
||||||
int data = 0;
|
|
||||||
|
|
||||||
CSignalT<int&> signal;
|
|
||||||
auto l1 = signal.listen([&](int& v) { v += 1; });
|
|
||||||
auto l2 = signal.listen([&](int v) { count += v; });
|
|
||||||
signal.emit(data);
|
|
||||||
|
|
||||||
CSignalT<const int&> constSignal;
|
|
||||||
auto l3 = constSignal.listen([&](const int& v) { count += v; });
|
|
||||||
auto l4 = constSignal.listen([&](int v) { count += v; });
|
|
||||||
constSignal.emit(data);
|
|
||||||
|
|
||||||
EXPECT_EQ(data, 1);
|
|
||||||
EXPECT_EQ(count, 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void refMany() {
|
|
||||||
int count = 0;
|
|
||||||
int data1 = 0;
|
|
||||||
int data2 = 10;
|
|
||||||
|
|
||||||
CSignalT<int&, const int&> signal;
|
|
||||||
auto l1 = signal.listen([&](int& v, const int&) { v += 1; });
|
|
||||||
auto l2 = signal.listen([&](int v1, int v2) { count += v1 + v2; });
|
|
||||||
|
|
||||||
signal.emit(data1, data2);
|
|
||||||
EXPECT_EQ(data1, 1);
|
|
||||||
EXPECT_EQ(count, 11);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void autoRefTypes() {
|
|
||||||
class CCopyCounter {
|
|
||||||
public:
|
|
||||||
CCopyCounter(int& createCount, int& destroyCount) : createCount(createCount), destroyCount(destroyCount) {
|
|
||||||
createCount += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
CCopyCounter(CCopyCounter&& other) noexcept : CCopyCounter(other.createCount, other.destroyCount) {}
|
|
||||||
CCopyCounter(const CCopyCounter& other) noexcept : CCopyCounter(other.createCount, other.destroyCount) {}
|
|
||||||
|
|
||||||
~CCopyCounter() {
|
|
||||||
destroyCount += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
int& createCount;
|
|
||||||
int& destroyCount;
|
|
||||||
};
|
|
||||||
|
|
||||||
auto createCount = 0;
|
|
||||||
auto destroyCount = 0;
|
|
||||||
|
|
||||||
CSignalT<CCopyCounter> signal;
|
|
||||||
auto listener = signal.listen([](const CCopyCounter& counter) {});
|
|
||||||
|
|
||||||
signal.emit(CCopyCounter(createCount, destroyCount));
|
|
||||||
EXPECT_EQ(createCount, 1);
|
|
||||||
EXPECT_EQ(destroyCount, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void forward() {
|
|
||||||
int count = 0;
|
|
||||||
|
|
||||||
CSignalT<int> sig;
|
|
||||||
CSignalT<int> connected1;
|
|
||||||
CSignalT<> connected2;
|
|
||||||
|
|
||||||
auto conn1 = sig.forward(connected1);
|
|
||||||
auto conn2 = sig.forward(connected2);
|
|
||||||
|
|
||||||
auto listener1 = connected1.listen([&](int v) { count += v; });
|
|
||||||
auto listener2 = connected2.listen([&] { count += 1; });
|
|
||||||
|
|
||||||
sig.emit(2);
|
|
||||||
|
|
||||||
EXPECT_EQ(count, 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void listenerAdded() {
|
|
||||||
int count = 0;
|
|
||||||
|
|
||||||
CSignalT<> signal;
|
|
||||||
CHyprSignalListener secondListener;
|
|
||||||
|
|
||||||
auto listener = signal.listen([&] {
|
|
||||||
count += 1;
|
|
||||||
|
|
||||||
if (!secondListener)
|
|
||||||
secondListener = signal.listen([&] { count += 1; });
|
|
||||||
});
|
|
||||||
|
|
||||||
signal.emit();
|
|
||||||
EXPECT_EQ(count, 1); // second should NOT be invoked as it was registed during emit
|
|
||||||
|
|
||||||
signal.emit();
|
|
||||||
EXPECT_EQ(count, 3); // second should be invoked
|
|
||||||
}
|
|
||||||
|
|
||||||
static void lastListenerSwapped() {
|
|
||||||
int count = 0;
|
|
||||||
|
|
||||||
CSignalT<> signal;
|
|
||||||
CHyprSignalListener removedListener;
|
|
||||||
CHyprSignalListener addedListener;
|
|
||||||
|
|
||||||
auto firstListener = signal.listen([&] {
|
|
||||||
removedListener.reset(); // dropped and should NOT be invoked
|
|
||||||
|
|
||||||
if (!addedListener)
|
|
||||||
addedListener = signal.listen([&] { count += 2; });
|
|
||||||
});
|
|
||||||
|
|
||||||
removedListener = signal.listen([&] { count += 1; });
|
|
||||||
|
|
||||||
signal.emit();
|
|
||||||
EXPECT_EQ(count, 0); // neither the removed nor added listeners should fire
|
|
||||||
|
|
||||||
signal.emit();
|
|
||||||
EXPECT_EQ(count, 2); // only the new listener should fire
|
|
||||||
}
|
|
||||||
|
|
||||||
static void signalDestroyed() {
|
|
||||||
int count = 0;
|
|
||||||
|
|
||||||
auto signal = std::make_unique<CSignalT<>>();
|
|
||||||
|
|
||||||
// This ensures a destructor of a listener called before signal reset is safe.
|
|
||||||
auto preListener = signal->listen([&] { count += 1; });
|
|
||||||
|
|
||||||
auto listener = signal->listen([&] { signal.reset(); });
|
|
||||||
|
|
||||||
// This ensures a destructor of a listener called after signal reset is safe
|
|
||||||
// and gets called.
|
|
||||||
auto postListener = signal->listen([&] { count += 1; });
|
|
||||||
|
|
||||||
signal->emit();
|
|
||||||
EXPECT_EQ(count, 2); // all listeners should fire regardless of signal deletion
|
|
||||||
}
|
|
||||||
|
|
||||||
// purely an asan test
|
|
||||||
static void signalDestroyedBeforeListener() {
|
|
||||||
CHyprSignalListener listener1;
|
|
||||||
CHyprSignalListener listener2;
|
|
||||||
|
|
||||||
CSignalT<> signal;
|
|
||||||
|
|
||||||
listener1 = signal.listen([] {});
|
|
||||||
listener2 = signal.listen([] {});
|
|
||||||
}
|
|
||||||
|
|
||||||
static void signalDestroyedWithAddedListener() {
|
|
||||||
int count = 0;
|
|
||||||
|
|
||||||
auto signal = std::make_unique<CSignalT<>>();
|
|
||||||
CHyprSignalListener shouldNotRun;
|
|
||||||
|
|
||||||
auto listener = signal->listen([&] {
|
|
||||||
shouldNotRun = signal->listen([&] { count += 2; });
|
|
||||||
signal.reset();
|
|
||||||
});
|
|
||||||
|
|
||||||
signal->emit();
|
|
||||||
EXPECT_EQ(count, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void signalDestroyedWithRemovedAndAddedListener() {
|
|
||||||
int count = 0;
|
|
||||||
|
|
||||||
auto signal = std::make_unique<CSignalT<>>();
|
|
||||||
CHyprSignalListener removed;
|
|
||||||
CHyprSignalListener shouldNotRun;
|
|
||||||
|
|
||||||
auto listener = signal->listen([&] {
|
|
||||||
removed.reset();
|
|
||||||
shouldNotRun = signal->listen([&] { count += 2; });
|
|
||||||
signal.reset();
|
|
||||||
});
|
|
||||||
|
|
||||||
removed = signal->listen([&] { count += 1; });
|
|
||||||
|
|
||||||
signal->emit();
|
|
||||||
EXPECT_EQ(count, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void staticListener() {
|
|
||||||
int data = 0;
|
|
||||||
|
|
||||||
CSignalT<int> signal;
|
|
||||||
signal.listenStatic([&](int newData) { data = newData; });
|
|
||||||
|
|
||||||
signal.emit(1);
|
|
||||||
EXPECT_EQ(data, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void staticListenerDestroy() {
|
|
||||||
int count = 0;
|
|
||||||
|
|
||||||
auto signal = makeShared<CSignalT<>>();
|
|
||||||
signal->listenStatic([&] { count += 1; });
|
|
||||||
|
|
||||||
signal->listenStatic([&] {
|
|
||||||
// should not fire but SHOULD be freed
|
|
||||||
signal->listenStatic([&] { count += 3; });
|
|
||||||
|
|
||||||
signal.reset();
|
|
||||||
});
|
|
||||||
|
|
||||||
signal->listenStatic([&] { count += 1; });
|
|
||||||
|
|
||||||
signal->emit();
|
|
||||||
EXPECT_EQ(count, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// purely an asan test
|
|
||||||
static void listenerDestroysSelf() {
|
|
||||||
CSignalT<> signal;
|
|
||||||
|
|
||||||
CHyprSignalListener listener;
|
|
||||||
listener = signal.listen([&] { listener.reset(); });
|
|
||||||
|
|
||||||
// the static signal case is taken care of above
|
|
||||||
|
|
||||||
signal.emit();
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Signal, signal) {
|
|
||||||
legacy();
|
|
||||||
legacyListenerEmit();
|
|
||||||
legacyListeners();
|
|
||||||
empty();
|
|
||||||
typed();
|
|
||||||
ignoreParams();
|
|
||||||
typedMany();
|
|
||||||
ref();
|
|
||||||
refMany();
|
|
||||||
autoRefTypes();
|
|
||||||
forward();
|
|
||||||
listenerAdded();
|
|
||||||
lastListenerSwapped();
|
|
||||||
signalDestroyed();
|
|
||||||
signalDestroyedBeforeListener();
|
|
||||||
signalDestroyedWithAddedListener();
|
|
||||||
signalDestroyedWithRemovedAndAddedListener();
|
|
||||||
staticListener();
|
|
||||||
staticListenerDestroy();
|
|
||||||
signalDestroyed();
|
|
||||||
listenerDestroysSelf();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
|
||||||
|
|
@ -36,19 +36,3 @@ std::string CConstVarList::join(const std::string& joiner, size_t from, size_t t
|
||||||
|
|
||||||
return rolling;
|
return rolling;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HU_UNIT_TESTS
|
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
|
||||||
|
|
||||||
TEST(String, constvarlist) {
|
|
||||||
CConstVarList listConst("hello world!", 0, 's', true);
|
|
||||||
EXPECT_EQ(listConst[0], "hello");
|
|
||||||
EXPECT_EQ(listConst[1], "world!");
|
|
||||||
|
|
||||||
CConstVarList listConst2("0 set", 2, ' ');
|
|
||||||
EXPECT_EQ(listConst2[0], "0");
|
|
||||||
EXPECT_EQ(listConst2[1], "set");
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
|
||||||
|
|
@ -89,37 +89,15 @@ void Hyprutils::String::replaceInString(std::string& string, const std::string&
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HU_UNIT_TESTS
|
bool Hyprutils::String::truthy(const std::string_view& in) {
|
||||||
|
if (in == "1")
|
||||||
|
return true;
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
if (in == "0")
|
||||||
|
return false;
|
||||||
|
|
||||||
TEST(String, string) {
|
std::string lower = std::string{in};
|
||||||
EXPECT_EQ(trim(" a "), "a");
|
std::ranges::transform(lower, lower.begin(), ::tolower);
|
||||||
EXPECT_EQ(trim(" a a "), "a a");
|
|
||||||
EXPECT_EQ(trim("a"), "a");
|
|
||||||
EXPECT_EQ(trim(" "), "");
|
|
||||||
|
|
||||||
EXPECT_EQ(isNumber("99214123434"), true);
|
return lower.starts_with("true") || lower.starts_with("yes") || lower.starts_with("on");
|
||||||
EXPECT_EQ(isNumber("-35252345234"), true);
|
|
||||||
EXPECT_EQ(isNumber("---3423--432"), false);
|
|
||||||
EXPECT_EQ(isNumber("s---3423--432"), false);
|
|
||||||
EXPECT_EQ(isNumber("---3423--432s"), false);
|
|
||||||
EXPECT_EQ(isNumber("1s"), false);
|
|
||||||
EXPECT_EQ(isNumber(""), false);
|
|
||||||
EXPECT_EQ(isNumber("-"), false);
|
|
||||||
EXPECT_EQ(isNumber("--0"), false);
|
|
||||||
EXPECT_EQ(isNumber("abc"), false);
|
|
||||||
EXPECT_EQ(isNumber("0.0", true), true);
|
|
||||||
EXPECT_EQ(isNumber("0.2", true), true);
|
|
||||||
EXPECT_EQ(isNumber("0.", true), false);
|
|
||||||
EXPECT_EQ(isNumber(".0", true), false);
|
|
||||||
EXPECT_EQ(isNumber("", true), false);
|
|
||||||
EXPECT_EQ(isNumber("vvss", true), false);
|
|
||||||
EXPECT_EQ(isNumber("0.9999s", true), false);
|
|
||||||
EXPECT_EQ(isNumber("s0.9999", true), false);
|
|
||||||
EXPECT_EQ(isNumber("-1.0", true), true);
|
|
||||||
EXPECT_EQ(isNumber("-1..0", true), false);
|
|
||||||
EXPECT_EQ(isNumber("-10.0000000001", true), true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -36,15 +36,3 @@ std::string Hyprutils::String::CVarList::join(const std::string& joiner, size_t
|
||||||
|
|
||||||
return rolling;
|
return rolling;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HU_UNIT_TESTS
|
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
|
||||||
|
|
||||||
TEST(String, varlist) {
|
|
||||||
CVarList list("hello world!", 0, 's', true);
|
|
||||||
EXPECT_EQ(list[0], "hello");
|
|
||||||
EXPECT_EQ(list[1], "world!");
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
|
||||||
|
|
@ -111,71 +111,3 @@ void CVarList2::append(std::string&& arg) {
|
||||||
bool CVarList2::contains(const std::string& el) {
|
bool CVarList2::contains(const std::string& el) {
|
||||||
return std::ranges::any_of(m_args, [&el](const auto& e) { return e == el; });
|
return std::ranges::any_of(m_args, [&el](const auto& e) { return e == el; });
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HU_UNIT_TESTS
|
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
|
||||||
|
|
||||||
TEST(String, varlist2) {
|
|
||||||
CVarList2 varList2("0 set", 2, ' ');
|
|
||||||
EXPECT_EQ(varList2[0], "0");
|
|
||||||
EXPECT_EQ(varList2[1], "set");
|
|
||||||
|
|
||||||
varList2.append("Hello");
|
|
||||||
|
|
||||||
EXPECT_EQ(varList2[1], "set");
|
|
||||||
EXPECT_EQ(varList2[2], "Hello");
|
|
||||||
EXPECT_EQ(varList2[3], "");
|
|
||||||
EXPECT_EQ(varList2.contains("set"), true);
|
|
||||||
EXPECT_EQ(varList2.contains("sett"), false);
|
|
||||||
EXPECT_EQ(varList2.contains(""), false);
|
|
||||||
EXPECT_EQ(varList2.size(), 3);
|
|
||||||
|
|
||||||
CVarList2 varList2B("hello, world\\, ok?", 0, ',', true, true);
|
|
||||||
EXPECT_EQ(varList2B[0], "hello");
|
|
||||||
EXPECT_EQ(varList2B[1], "world, ok?");
|
|
||||||
|
|
||||||
CVarList2 varList2C("hello, , ok?", 0, ',', true, true);
|
|
||||||
EXPECT_EQ(varList2C[0], "hello");
|
|
||||||
EXPECT_EQ(varList2C[1], "ok?");
|
|
||||||
|
|
||||||
CVarList2 varList2D("\\\\, , ok?", 0, ',', true, true);
|
|
||||||
EXPECT_EQ(varList2D[0], "\\");
|
|
||||||
EXPECT_EQ(varList2D[1], "ok?");
|
|
||||||
|
|
||||||
CVarList2 varList2E("\\, , ok?", 0, ',', true, true);
|
|
||||||
EXPECT_EQ(varList2E[0], ",");
|
|
||||||
EXPECT_EQ(varList2E[1], "ok?");
|
|
||||||
|
|
||||||
CVarList2 varList2F("Hello, world\\\\, ok?", 0, ',', true, true);
|
|
||||||
EXPECT_EQ(varList2F[0], "Hello");
|
|
||||||
EXPECT_EQ(varList2F[1], "world\\");
|
|
||||||
EXPECT_EQ(varList2F[2], "ok?");
|
|
||||||
|
|
||||||
CVarList2 varList2G("Hello,\\, ok?", 0, ',', true, true);
|
|
||||||
EXPECT_EQ(varList2G[0], "Hello");
|
|
||||||
EXPECT_EQ(varList2G[1], ", ok?");
|
|
||||||
|
|
||||||
CVarList2 varList2H("Hello,\\\\, ok?", 0, ',', true, true);
|
|
||||||
EXPECT_EQ(varList2H[0], "Hello");
|
|
||||||
EXPECT_EQ(varList2H[1], "\\");
|
|
||||||
EXPECT_EQ(varList2H[2], "ok?");
|
|
||||||
|
|
||||||
CVarList2 varList2I("Hello,\\, ok?", 0, ',', true, false);
|
|
||||||
EXPECT_EQ(varList2I[0], "Hello");
|
|
||||||
EXPECT_EQ(varList2I[1], "\\");
|
|
||||||
EXPECT_EQ(varList2I[2], "ok?");
|
|
||||||
|
|
||||||
CVarList2 varList2J("", 0, ',', true, false);
|
|
||||||
EXPECT_EQ(varList2J[0], "");
|
|
||||||
|
|
||||||
CVarList2 varList2K(",\\, ok?", 0, ',', true);
|
|
||||||
EXPECT_EQ(varList2K[0], ", ok?");
|
|
||||||
|
|
||||||
CVarList2 varList2L("Hello, world", 0, ',', true);
|
|
||||||
EXPECT_EQ(varList2L.join(" "), "Hello world");
|
|
||||||
EXPECT_EQ(varList2L.join(" ", 0, 1000), "Hello world");
|
|
||||||
EXPECT_EQ(varList2L.join(" ", 0, 1), "Hello");
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
|
||||||
395
tests/animation/Animation.cpp
Normal file
395
tests/animation/Animation.cpp
Normal file
|
|
@ -0,0 +1,395 @@
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <hyprutils/animation/AnimationConfig.hpp>
|
||||||
|
#include <hyprutils/animation/AnimationManager.hpp>
|
||||||
|
#include <hyprutils/animation/AnimatedVariable.hpp>
|
||||||
|
#include <hyprutils/memory/WeakPtr.hpp>
|
||||||
|
#include <hyprutils/memory/UniquePtr.hpp>
|
||||||
|
|
||||||
|
#define SP CSharedPointer
|
||||||
|
#define WP CWeakPointer
|
||||||
|
#define UP CUniquePointer
|
||||||
|
|
||||||
|
using namespace Hyprutils::Animation;
|
||||||
|
using namespace Hyprutils::Math;
|
||||||
|
using namespace Hyprutils::Memory;
|
||||||
|
|
||||||
|
class EmtpyContext {};
|
||||||
|
|
||||||
|
template <typename VarType>
|
||||||
|
using CAnimatedVariable = CGenericAnimatedVariable<VarType, EmtpyContext>;
|
||||||
|
|
||||||
|
template <typename VarType>
|
||||||
|
using PANIMVAR = UP<CAnimatedVariable<VarType>>;
|
||||||
|
|
||||||
|
template <typename VarType>
|
||||||
|
using PANIMVARREF = WP<CAnimatedVariable<VarType>>;
|
||||||
|
|
||||||
|
enum eAVTypes {
|
||||||
|
INT = 1,
|
||||||
|
TEST,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SomeTestType {
|
||||||
|
bool done = false;
|
||||||
|
bool operator==(const SomeTestType& other) const {
|
||||||
|
return done == other.done;
|
||||||
|
}
|
||||||
|
SomeTestType& operator=(const SomeTestType& other) {
|
||||||
|
done = other.done;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
CAnimationConfigTree animationTree;
|
||||||
|
|
||||||
|
class CMyAnimationManager : public CAnimationManager {
|
||||||
|
public:
|
||||||
|
void tick() {
|
||||||
|
for (const auto& PAV : m_vActiveAnimatedVariables) {
|
||||||
|
if (!PAV || !PAV->ok() || !PAV->isBeingAnimated())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const auto SPENT = PAV->getPercent();
|
||||||
|
const auto PBEZIER = getBezier(PAV->getBezierName());
|
||||||
|
|
||||||
|
if (SPENT >= 1.f || !PAV->enabled()) {
|
||||||
|
PAV->warp(true, false);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto POINTY = PBEZIER->getYForPoint(SPENT);
|
||||||
|
|
||||||
|
switch (PAV->m_Type) {
|
||||||
|
case eAVTypes::INT: {
|
||||||
|
auto avInt = dc<CAnimatedVariable<int>*>(PAV.get());
|
||||||
|
if (!avInt)
|
||||||
|
std::cout << "Dynamic cast upcast failed\n";
|
||||||
|
|
||||||
|
const auto DELTA = avInt->goal() - avInt->begun();
|
||||||
|
avInt->value() = avInt->begun() + (DELTA * POINTY);
|
||||||
|
} break;
|
||||||
|
case eAVTypes::TEST: {
|
||||||
|
auto avCustom = dc<CAnimatedVariable<SomeTestType>*>(PAV.get());
|
||||||
|
if (!avCustom)
|
||||||
|
std::cout << "Dynamic cast upcast failed\n";
|
||||||
|
|
||||||
|
if (SPENT >= 1.f)
|
||||||
|
avCustom->value().done = true;
|
||||||
|
} break;
|
||||||
|
default: {
|
||||||
|
std::cout << "What are we even doing?\n";
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
|
||||||
|
PAV->onUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
tickDone();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename VarType>
|
||||||
|
void createAnimation(const VarType& v, PANIMVAR<VarType>& av, const std::string& animationConfigName) {
|
||||||
|
constexpr const eAVTypes EAVTYPE = std::is_same_v<VarType, int> ? eAVTypes::INT : eAVTypes::TEST;
|
||||||
|
av = makeUnique<CGenericAnimatedVariable<VarType, EmtpyContext>>();
|
||||||
|
|
||||||
|
av->create2(EAVTYPE, sc<CAnimationManager*>(this), av, v);
|
||||||
|
av->setConfig(animationTree.getConfig(animationConfigName));
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void scheduleTick() {
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void onTicked() {
|
||||||
|
;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
UP<CMyAnimationManager> pAnimationManager;
|
||||||
|
|
||||||
|
class Subject {
|
||||||
|
public:
|
||||||
|
Subject(const int& a, const int& b) {
|
||||||
|
pAnimationManager->createAnimation(a, m_iA, "default");
|
||||||
|
pAnimationManager->createAnimation(b, m_iB, "internal");
|
||||||
|
pAnimationManager->createAnimation({}, m_iC, "default");
|
||||||
|
}
|
||||||
|
PANIMVAR<int> m_iA;
|
||||||
|
PANIMVAR<int> m_iB;
|
||||||
|
PANIMVAR<SomeTestType> m_iC;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int config() {
|
||||||
|
pAnimationManager = makeUnique<CMyAnimationManager>();
|
||||||
|
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
animationTree.createNode("global");
|
||||||
|
animationTree.createNode("internal");
|
||||||
|
|
||||||
|
animationTree.createNode("foo", "internal");
|
||||||
|
animationTree.createNode("default", "global");
|
||||||
|
animationTree.createNode("bar", "default");
|
||||||
|
|
||||||
|
/*
|
||||||
|
internal
|
||||||
|
↳ foo
|
||||||
|
global
|
||||||
|
↳ default
|
||||||
|
↳ bar
|
||||||
|
*/
|
||||||
|
|
||||||
|
auto barCfg = animationTree.getConfig("bar");
|
||||||
|
auto internalCfg = animationTree.getConfig("internal");
|
||||||
|
|
||||||
|
// internal is a root node and should point to itself
|
||||||
|
EXPECT_EQ(internalCfg->pParentAnimation.get(), internalCfg.get());
|
||||||
|
EXPECT_EQ(internalCfg->pValues.get(), internalCfg.get());
|
||||||
|
|
||||||
|
animationTree.setConfigForNode("global", 1, 4.0, "default", "asdf");
|
||||||
|
|
||||||
|
EXPECT_EQ(barCfg->internalEnabled, -1);
|
||||||
|
{
|
||||||
|
const auto PVALUES = barCfg->pValues.lock();
|
||||||
|
EXPECT_EQ(PVALUES->internalEnabled, 1);
|
||||||
|
EXPECT_EQ(PVALUES->internalBezier, "default");
|
||||||
|
EXPECT_EQ(PVALUES->internalStyle, "asdf");
|
||||||
|
EXPECT_EQ(PVALUES->internalSpeed, 4.0);
|
||||||
|
}
|
||||||
|
EXPECT_EQ(barCfg->pParentAnimation.get(), animationTree.getConfig("default").get());
|
||||||
|
|
||||||
|
// Overwrite our own values
|
||||||
|
animationTree.setConfigForNode("bar", 1, 4.2, "test", "qwer");
|
||||||
|
|
||||||
|
{
|
||||||
|
const auto PVALUES = barCfg->pValues.lock();
|
||||||
|
EXPECT_EQ(PVALUES->internalEnabled, 1);
|
||||||
|
EXPECT_EQ(PVALUES->internalBezier, "test");
|
||||||
|
EXPECT_EQ(PVALUES->internalStyle, "qwer");
|
||||||
|
EXPECT_EQ(PVALUES->internalSpeed, 4.2f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now overwrite the parent
|
||||||
|
animationTree.setConfigForNode("default", 0, 0.0, "zxcv", "foo");
|
||||||
|
|
||||||
|
{
|
||||||
|
// Expecting no change
|
||||||
|
const auto PVALUES = barCfg->pValues.lock();
|
||||||
|
EXPECT_EQ(PVALUES->internalEnabled, 1);
|
||||||
|
EXPECT_EQ(PVALUES->internalBezier, "test");
|
||||||
|
EXPECT_EQ(PVALUES->internalStyle, "qwer");
|
||||||
|
EXPECT_EQ(PVALUES->internalSpeed, 4.2f);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Animation, animation) {
|
||||||
|
config();
|
||||||
|
|
||||||
|
animationTree.createNode("global");
|
||||||
|
animationTree.createNode("internal");
|
||||||
|
|
||||||
|
animationTree.createNode("default", "global");
|
||||||
|
animationTree.setConfigForNode("global", 1, 4.0, "default", "asdf");
|
||||||
|
|
||||||
|
Subject s(0, 0);
|
||||||
|
|
||||||
|
EXPECT_EQ(s.m_iA->value(), 0);
|
||||||
|
EXPECT_EQ(s.m_iB->value(), 0);
|
||||||
|
|
||||||
|
// Test destruction of a CAnimatedVariable
|
||||||
|
{
|
||||||
|
Subject s2(10, 10);
|
||||||
|
// Adds them to active
|
||||||
|
*s2.m_iA = 1;
|
||||||
|
*s2.m_iB = 2;
|
||||||
|
// We deliberately do not tick here, to make sure the destructor removes active animated variables
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_EQ(pAnimationManager->shouldTickForNext(), false);
|
||||||
|
EXPECT_EQ(s.m_iC->value().done, false);
|
||||||
|
|
||||||
|
*s.m_iA = 10;
|
||||||
|
*s.m_iB = 100;
|
||||||
|
*s.m_iC = SomeTestType(true);
|
||||||
|
|
||||||
|
EXPECT_EQ(s.m_iC->value().done, false);
|
||||||
|
|
||||||
|
while (pAnimationManager->shouldTickForNext()) {
|
||||||
|
pAnimationManager->tick();
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_EQ(s.m_iA->value(), 10);
|
||||||
|
EXPECT_EQ(s.m_iB->value(), 100);
|
||||||
|
EXPECT_EQ(s.m_iC->value().done, true);
|
||||||
|
|
||||||
|
s.m_iA->setValue(0);
|
||||||
|
s.m_iB->setValue(0);
|
||||||
|
|
||||||
|
while (pAnimationManager->shouldTickForNext()) {
|
||||||
|
pAnimationManager->tick();
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_EQ(s.m_iA->value(), 10);
|
||||||
|
EXPECT_EQ(s.m_iB->value(), 100);
|
||||||
|
|
||||||
|
// Test config stuff
|
||||||
|
EXPECT_EQ(s.m_iA->getBezierName(), "default");
|
||||||
|
EXPECT_EQ(s.m_iA->getStyle(), "asdf");
|
||||||
|
EXPECT_EQ(s.m_iA->enabled(), true);
|
||||||
|
|
||||||
|
animationTree.getConfig("global")->internalEnabled = 0;
|
||||||
|
|
||||||
|
EXPECT_EQ(s.m_iA->enabled(), false);
|
||||||
|
|
||||||
|
*s.m_iA = 50;
|
||||||
|
pAnimationManager->tick(); // Expecting a warp
|
||||||
|
EXPECT_EQ(s.m_iA->value(), 50);
|
||||||
|
|
||||||
|
// Test missing pValues
|
||||||
|
animationTree.getConfig("global")->internalEnabled = 0;
|
||||||
|
animationTree.getConfig("default")->pValues.reset();
|
||||||
|
|
||||||
|
EXPECT_EQ(s.m_iA->enabled(), false);
|
||||||
|
EXPECT_EQ(s.m_iA->getBezierName(), "default");
|
||||||
|
EXPECT_EQ(s.m_iA->getStyle(), "");
|
||||||
|
EXPECT_EQ(s.m_iA->getPercent(), 1.f);
|
||||||
|
|
||||||
|
// Reset
|
||||||
|
animationTree.setConfigForNode("default", 1, 1, "default");
|
||||||
|
|
||||||
|
//
|
||||||
|
// Test callbacks
|
||||||
|
//
|
||||||
|
int beginCallbackRan = 0;
|
||||||
|
int updateCallbackRan = 0;
|
||||||
|
int endCallbackRan = 0;
|
||||||
|
s.m_iA->setCallbackOnBegin([&beginCallbackRan](WP<CBaseAnimatedVariable> pav) { beginCallbackRan++; });
|
||||||
|
s.m_iA->setUpdateCallback([&updateCallbackRan](WP<CBaseAnimatedVariable> pav) { updateCallbackRan++; });
|
||||||
|
s.m_iA->setCallbackOnEnd([&endCallbackRan](WP<CBaseAnimatedVariable> pav) { endCallbackRan++; }, false);
|
||||||
|
|
||||||
|
s.m_iA->setValueAndWarp(42);
|
||||||
|
|
||||||
|
EXPECT_EQ(beginCallbackRan, 0);
|
||||||
|
EXPECT_EQ(updateCallbackRan, 1);
|
||||||
|
EXPECT_EQ(endCallbackRan, 2); // first called when setting the callback, then when warping.
|
||||||
|
|
||||||
|
*s.m_iA = 1337;
|
||||||
|
while (pAnimationManager->shouldTickForNext()) {
|
||||||
|
pAnimationManager->tick();
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_EQ(beginCallbackRan, 1);
|
||||||
|
EXPECT_EQ(updateCallbackRan > 2, true);
|
||||||
|
EXPECT_EQ(endCallbackRan, 3);
|
||||||
|
|
||||||
|
std::vector<PANIMVAR<int>> vars;
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
vars.resize(vars.size() + 1);
|
||||||
|
pAnimationManager->createAnimation(1, vars.back(), "default");
|
||||||
|
*vars.back() = 1337;
|
||||||
|
}
|
||||||
|
|
||||||
|
// test adding / removing vars during a tick
|
||||||
|
s.m_iA->resetAllCallbacks();
|
||||||
|
s.m_iA->setUpdateCallback([&vars](WP<CBaseAnimatedVariable> v) {
|
||||||
|
if (v.get() != vars.back().get())
|
||||||
|
vars.back()->warp();
|
||||||
|
});
|
||||||
|
s.m_iA->setCallbackOnEnd([&s, &vars](auto) {
|
||||||
|
vars.resize(vars.size() + 1);
|
||||||
|
pAnimationManager->createAnimation(1, vars.back(), "default");
|
||||||
|
*vars.back() = 1337;
|
||||||
|
});
|
||||||
|
|
||||||
|
*s.m_iA = 1000000;
|
||||||
|
|
||||||
|
while (pAnimationManager->shouldTickForNext()) {
|
||||||
|
pAnimationManager->tick();
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_EQ(s.m_iA->value(), 1000000);
|
||||||
|
// all vars should be set to 1337
|
||||||
|
EXPECT_EQ(std::find_if(vars.begin(), vars.end(), [](const auto& v) { return v->value() != 1337; }) == vars.end(), true);
|
||||||
|
|
||||||
|
// test one-time callbacks
|
||||||
|
s.m_iA->resetAllCallbacks();
|
||||||
|
s.m_iA->setCallbackOnEnd([&endCallbackRan](auto) { endCallbackRan++; }, true);
|
||||||
|
|
||||||
|
EXPECT_EQ(endCallbackRan, 4);
|
||||||
|
|
||||||
|
s.m_iA->setValueAndWarp(10);
|
||||||
|
|
||||||
|
EXPECT_EQ(endCallbackRan, 4);
|
||||||
|
EXPECT_EQ(s.m_iA->value(), 10);
|
||||||
|
|
||||||
|
// test warp
|
||||||
|
*s.m_iA = 3;
|
||||||
|
s.m_iA->setCallbackOnEnd([&endCallbackRan](auto) { endCallbackRan++; }, false);
|
||||||
|
|
||||||
|
s.m_iA->warp(false);
|
||||||
|
EXPECT_EQ(endCallbackRan, 4);
|
||||||
|
|
||||||
|
*s.m_iA = 4;
|
||||||
|
s.m_iA->warp(true);
|
||||||
|
EXPECT_EQ(endCallbackRan, 5);
|
||||||
|
|
||||||
|
// test getCurveValue
|
||||||
|
*s.m_iA = 0;
|
||||||
|
EXPECT_EQ(s.m_iA->getCurveValue(), 0.f);
|
||||||
|
s.m_iA->warp();
|
||||||
|
EXPECT_EQ(s.m_iA->getCurveValue(), 1.f);
|
||||||
|
EXPECT_EQ(endCallbackRan, 6);
|
||||||
|
|
||||||
|
// test end callback readding the var
|
||||||
|
*s.m_iA = 5;
|
||||||
|
s.m_iA->setCallbackOnEnd([&endCallbackRan](WP<CBaseAnimatedVariable> v) {
|
||||||
|
endCallbackRan++;
|
||||||
|
const auto PAV = dc<CAnimatedVariable<int>*>(v.get());
|
||||||
|
|
||||||
|
*PAV = 10;
|
||||||
|
PAV->setCallbackOnEnd([&endCallbackRan](WP<CBaseAnimatedVariable> v) { endCallbackRan++; });
|
||||||
|
});
|
||||||
|
|
||||||
|
while (pAnimationManager->shouldTickForNext()) {
|
||||||
|
pAnimationManager->tick();
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_EQ(endCallbackRan, 8);
|
||||||
|
EXPECT_EQ(s.m_iA->value(), 10);
|
||||||
|
|
||||||
|
// Test duplicate active anim vars are not allowed
|
||||||
|
{
|
||||||
|
EXPECT_EQ(pAnimationManager->m_vActiveAnimatedVariables.size(), 0);
|
||||||
|
PANIMVAR<int> a;
|
||||||
|
pAnimationManager->createAnimation(1, a, "default");
|
||||||
|
EXPECT_EQ(pAnimationManager->m_vActiveAnimatedVariables.size(), 0);
|
||||||
|
*a = 10;
|
||||||
|
EXPECT_EQ(pAnimationManager->m_vActiveAnimatedVariables.size(), 1);
|
||||||
|
*a = 20;
|
||||||
|
EXPECT_EQ(pAnimationManager->m_vActiveAnimatedVariables.size(), 1);
|
||||||
|
a->warp();
|
||||||
|
EXPECT_EQ(pAnimationManager->m_vActiveAnimatedVariables.size(), 0);
|
||||||
|
EXPECT_EQ(a->value(), 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test no crash when animation manager gets destroyed
|
||||||
|
{
|
||||||
|
PANIMVAR<int> a;
|
||||||
|
pAnimationManager->createAnimation(1, a, "default");
|
||||||
|
*a = 10;
|
||||||
|
pAnimationManager.reset();
|
||||||
|
EXPECT_EQ(a->isAnimationManagerDead(), true);
|
||||||
|
a->setValueAndWarp(11);
|
||||||
|
EXPECT_EQ(a->value(), 11);
|
||||||
|
*a = 12;
|
||||||
|
a->warp();
|
||||||
|
EXPECT_EQ(a->value(), 12);
|
||||||
|
*a = 13;
|
||||||
|
} // a gets destroyed
|
||||||
|
|
||||||
|
EXPECT_EQ(pAnimationManager.get(), nullptr);
|
||||||
|
}
|
||||||
79
tests/animation/Bezier.cpp
Normal file
79
tests/animation/Bezier.cpp
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
#include <cmath>
|
||||||
|
#include <hyprutils/animation/BezierCurve.hpp>
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
using Hyprutils::Animation::CBezierCurve;
|
||||||
|
using Hyprutils::Math::Vector2D;
|
||||||
|
|
||||||
|
static void test_nonmonotonic4_clamps_out_of_range() {
|
||||||
|
// Non-monotonic curve in X
|
||||||
|
// This used to drive the step-halving search to OOB. It should now clamp
|
||||||
|
CBezierCurve curve;
|
||||||
|
std::array<Vector2D, 4> pts = {
|
||||||
|
Vector2D{0.5f, 1.0f}, // P0
|
||||||
|
Vector2D{1.0f, 1.0f}, // P1
|
||||||
|
Vector2D{0.0f, 0.0f}, // P2
|
||||||
|
Vector2D{0.5f, 0.0f} // P3
|
||||||
|
};
|
||||||
|
curve.setup4(pts);
|
||||||
|
|
||||||
|
// x > last baked x
|
||||||
|
EXPECT_EQ(std::isfinite(curve.getYForPoint(0.6f)), true);
|
||||||
|
// Far beyond range
|
||||||
|
EXPECT_EQ(std::isfinite(curve.getYForPoint(std::numeric_limits<float>::max())), true);
|
||||||
|
EXPECT_EQ(std::isfinite(curve.getYForPoint(-std::numeric_limits<float>::max())), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_adjacent_baked_x_equal() {
|
||||||
|
// Curve with flat tail (X=1, Y=1)
|
||||||
|
CBezierCurve curve;
|
||||||
|
std::array<Vector2D, 4> pts = {
|
||||||
|
Vector2D{0.0f, 0.0f}, // P0
|
||||||
|
Vector2D{0.2f, 0.2f}, // P1
|
||||||
|
Vector2D{1.0f, 1.0f}, // P2
|
||||||
|
Vector2D{1.0f, 1.0f} // P3
|
||||||
|
};
|
||||||
|
curve.setup4(pts);
|
||||||
|
|
||||||
|
// Exactly at last baked X
|
||||||
|
const float y_at_end = curve.getYForPoint(1.0f);
|
||||||
|
// Slightly beyond last baked X
|
||||||
|
const float y_past_end = curve.getYForPoint(1.0001f);
|
||||||
|
|
||||||
|
EXPECT_EQ(y_at_end, 1.0f);
|
||||||
|
EXPECT_EQ(y_past_end, y_at_end);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_all_baked_x_equal() {
|
||||||
|
// Extreme case: X is constant along the whole curve
|
||||||
|
CBezierCurve curve;
|
||||||
|
std::array<Vector2D, 4> pts = {
|
||||||
|
Vector2D{0.0f, 0.0f}, // P0
|
||||||
|
Vector2D{0.0f, 0.3f}, // P1
|
||||||
|
Vector2D{0.0f, 0.7f}, // P2
|
||||||
|
Vector2D{0.0f, 1.0f} // P3
|
||||||
|
};
|
||||||
|
curve.setup4(pts);
|
||||||
|
|
||||||
|
// Below any baked X
|
||||||
|
const float y_lo = curve.getYForPoint(-100.0f);
|
||||||
|
const float y_0 = curve.getYForPoint(0.0f);
|
||||||
|
// Above any baked X
|
||||||
|
const float y_hi = curve.getYForPoint(100.0f);
|
||||||
|
|
||||||
|
EXPECT_EQ(std::isfinite(y_lo), true);
|
||||||
|
EXPECT_EQ(std::isfinite(y_0), true);
|
||||||
|
EXPECT_EQ(std::isfinite(y_hi), true);
|
||||||
|
|
||||||
|
// For this curve Y should stay within [0,1]
|
||||||
|
EXPECT_EQ((y_lo >= 0.0f && y_lo <= 1.0f), true);
|
||||||
|
EXPECT_EQ((y_0 >= 0.0f && y_0 <= 1.0f), true);
|
||||||
|
EXPECT_EQ((y_hi >= 0.0f && y_hi <= 1.0f), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Animation, beziercurve) {
|
||||||
|
test_nonmonotonic4_clamps_out_of_range();
|
||||||
|
test_adjacent_baked_x_equal();
|
||||||
|
test_all_baked_x_equal();
|
||||||
|
}
|
||||||
105
tests/cli/ArgumentParser.cpp
Normal file
105
tests/cli/ArgumentParser.cpp
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
#include <cli/ArgumentParser.hpp>
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <print>
|
||||||
|
|
||||||
|
using namespace Hyprutils::CLI;
|
||||||
|
using namespace Hyprutils;
|
||||||
|
|
||||||
|
constexpr const char* DESC_TEST = R"#(┏ My description
|
||||||
|
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||||
|
┣ --hello -h | Says hello ┃
|
||||||
|
┣ --hello2 | Says hello 2 ┃
|
||||||
|
┣ --value -v [float] | Sets a valueeeeeee ┃
|
||||||
|
┣ --longlonglonglongintopt -l [int] | Long long ┃
|
||||||
|
┃ maaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa┃
|
||||||
|
┃ aaaaaaaaaaan maaan man maaan man maaan ┃
|
||||||
|
┃ man maaan man ┃
|
||||||
|
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||||
|
)#";
|
||||||
|
|
||||||
|
TEST(CLI, ArgumentParser) {
|
||||||
|
std::vector<const char*> argv = {"app", "--hello", "--value", "0.2"};
|
||||||
|
|
||||||
|
CArgumentParser parser(argv);
|
||||||
|
|
||||||
|
EXPECT_TRUE(parser.registerBoolOption("hello", "h", "Says hello"));
|
||||||
|
EXPECT_TRUE(parser.registerBoolOption("hello2", "", "Says hello 2"));
|
||||||
|
EXPECT_TRUE(parser.registerFloatOption("value", "v", "Sets a valueeeeeee"));
|
||||||
|
EXPECT_TRUE(parser.registerIntOption("longlonglonglongintopt", "l", "Long long maaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaan maaan man maaan man maaan man maaan man"));
|
||||||
|
|
||||||
|
auto result = parser.parse();
|
||||||
|
|
||||||
|
EXPECT_TRUE(result.has_value());
|
||||||
|
|
||||||
|
std::println("{}", parser.getDescription("My description"));
|
||||||
|
|
||||||
|
if (!result.has_value())
|
||||||
|
std::println("Error: {}", result.error());
|
||||||
|
|
||||||
|
EXPECT_EQ(parser.getBool("hello").value_or(false), true);
|
||||||
|
EXPECT_EQ(parser.getBool("hello2").value_or(false), false);
|
||||||
|
EXPECT_EQ(parser.getFloat("value").value_or(0.F), 0.2F);
|
||||||
|
|
||||||
|
EXPECT_EQ(parser.getDescription("My description"), DESC_TEST);
|
||||||
|
|
||||||
|
CArgumentParser parser2(argv);
|
||||||
|
|
||||||
|
EXPECT_TRUE(parser2.registerBoolOption("hello2", "e", "Says hello 2"));
|
||||||
|
EXPECT_TRUE(parser2.registerFloatOption("value", "v", "Sets a valueeeeeee"));
|
||||||
|
EXPECT_TRUE(parser2.registerIntOption("longlonglonglongintopt", "l", "Long long maaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaan maaan man maaan man maaan man maaan man"));
|
||||||
|
EXPECT_TRUE(parser2.registerFloatOption("value2", "", "Sets a valueeeeeee 2"));
|
||||||
|
EXPECT_TRUE(!parser2.registerFloatOption("", "a", "Sets a valueeeeeee 2"));
|
||||||
|
|
||||||
|
auto result2 = parser2.parse();
|
||||||
|
|
||||||
|
EXPECT_TRUE(!result2.has_value());
|
||||||
|
|
||||||
|
std::vector<const char*> argv3 = {"app", "--hello", "--value"};
|
||||||
|
|
||||||
|
CArgumentParser parser3(argv3);
|
||||||
|
|
||||||
|
EXPECT_TRUE(parser3.registerBoolOption("hello2", "e", "Says hello 2"));
|
||||||
|
EXPECT_TRUE(parser3.registerFloatOption("value", "v", "Sets a valueeeeeee"));
|
||||||
|
EXPECT_TRUE(parser3.registerIntOption("longlonglonglongintopt", "l", "Long long maaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaan maaan man maaan man maaan man maaan man"));
|
||||||
|
|
||||||
|
auto result3 = parser3.parse();
|
||||||
|
|
||||||
|
EXPECT_TRUE(!result3.has_value());
|
||||||
|
|
||||||
|
std::vector<const char*> argv4 = {"app", "--value", "hi", "-w", "2"};
|
||||||
|
|
||||||
|
CArgumentParser parser4(argv4);
|
||||||
|
|
||||||
|
EXPECT_TRUE(parser4.registerStringOption("value", "v", "Sets a valueeeeeee"));
|
||||||
|
EXPECT_TRUE(parser4.registerIntOption("value2", "w", "Sets a valueeeeeee 2"));
|
||||||
|
|
||||||
|
auto result4 = parser4.parse();
|
||||||
|
|
||||||
|
EXPECT_TRUE(result4.has_value());
|
||||||
|
|
||||||
|
EXPECT_EQ(parser4.getString("value").value_or(""), "hi");
|
||||||
|
EXPECT_EQ(parser4.getInt("value2").value_or(0), 2);
|
||||||
|
|
||||||
|
std::vector<const char*> argv5 = {
|
||||||
|
"app",
|
||||||
|
"e",
|
||||||
|
};
|
||||||
|
|
||||||
|
CArgumentParser parser5(argv5);
|
||||||
|
|
||||||
|
EXPECT_TRUE(parser5.registerStringOption("value", "v", "Sets a valueeeeeee"));
|
||||||
|
EXPECT_TRUE(parser5.registerStringOption("value2", "w", "Sets a valueeeeeee 2"));
|
||||||
|
|
||||||
|
auto result5 = parser5.parse();
|
||||||
|
|
||||||
|
EXPECT_TRUE(!result5.has_value());
|
||||||
|
|
||||||
|
CArgumentParser parser6(argv5);
|
||||||
|
|
||||||
|
EXPECT_TRUE(parser6.registerStringOption("aa", "v", "Sets a valueeeeeee"));
|
||||||
|
EXPECT_TRUE(!parser6.registerStringOption("aa", "w", "Sets a valueeeeeee 2"));
|
||||||
|
EXPECT_TRUE(parser6.registerStringOption("bb", "b", "Sets a valueeeeeee"));
|
||||||
|
EXPECT_TRUE(!parser6.registerStringOption("cc", "b", "Sets a valueeeeeee 2"));
|
||||||
|
}
|
||||||
104
tests/cli/Logger.cpp
Normal file
104
tests/cli/Logger.cpp
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
#include <cli/Logger.hpp>
|
||||||
|
#include <hyprutils/os/File.hpp>
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
using namespace Hyprutils::CLI;
|
||||||
|
using namespace Hyprutils;
|
||||||
|
|
||||||
|
TEST(CLI, Logger) {
|
||||||
|
CLogger logger;
|
||||||
|
|
||||||
|
logger.setEnableRolling(true);
|
||||||
|
|
||||||
|
logger.log(Hyprutils::CLI::LOG_DEBUG, "Hello!");
|
||||||
|
|
||||||
|
EXPECT_EQ(logger.rollingLog(), "DEBUG ]: Hello!");
|
||||||
|
|
||||||
|
logger.log(Hyprutils::CLI::LOG_TRACE, "Hello!");
|
||||||
|
|
||||||
|
EXPECT_EQ(logger.rollingLog(), "DEBUG ]: Hello!");
|
||||||
|
|
||||||
|
logger.setLogLevel(LOG_TRACE);
|
||||||
|
|
||||||
|
logger.log(Hyprutils::CLI::LOG_TRACE, "Hello, {}!", "Trace");
|
||||||
|
|
||||||
|
EXPECT_EQ(logger.rollingLog(), "DEBUG ]: Hello!\nTRACE ]: Hello, Trace!");
|
||||||
|
|
||||||
|
CLoggerConnection connection(logger);
|
||||||
|
connection.setName("conn");
|
||||||
|
|
||||||
|
connection.log(Hyprutils::CLI::LOG_TRACE, "Hello from connection!");
|
||||||
|
|
||||||
|
EXPECT_EQ(logger.rollingLog(), "DEBUG ]: Hello!\nTRACE ]: Hello, Trace!\nTRACE from conn ]: Hello from connection!");
|
||||||
|
|
||||||
|
connection.setLogLevel(Hyprutils::CLI::LOG_WARN);
|
||||||
|
|
||||||
|
connection.log(Hyprutils::CLI::LOG_DEBUG, "Hello from connection!");
|
||||||
|
|
||||||
|
EXPECT_EQ(logger.rollingLog(), "DEBUG ]: Hello!\nTRACE ]: Hello, Trace!\nTRACE from conn ]: Hello from connection!");
|
||||||
|
|
||||||
|
logger.setEnableRolling(false);
|
||||||
|
|
||||||
|
connection.log(Hyprutils::CLI::LOG_ERR, "Err!");
|
||||||
|
|
||||||
|
EXPECT_EQ(logger.rollingLog(), "DEBUG ]: Hello!\nTRACE ]: Hello, Trace!\nTRACE from conn ]: Hello from connection!");
|
||||||
|
|
||||||
|
logger.setEnableStdout(false);
|
||||||
|
|
||||||
|
logger.log(Hyprutils::CLI::LOG_ERR, "Error");
|
||||||
|
|
||||||
|
EXPECT_EQ(logger.rollingLog(), "DEBUG ]: Hello!\nTRACE ]: Hello, Trace!\nTRACE from conn ]: Hello from connection!");
|
||||||
|
|
||||||
|
auto res = logger.setOutputFile("./loggerFile.log");
|
||||||
|
EXPECT_TRUE(res);
|
||||||
|
|
||||||
|
logger.log(LOG_DEBUG, "Hi file!");
|
||||||
|
|
||||||
|
res = logger.setOutputFile(""); // clear
|
||||||
|
EXPECT_TRUE(res);
|
||||||
|
|
||||||
|
auto fileRead = File::readFileAsString("./loggerFile.log");
|
||||||
|
EXPECT_TRUE(fileRead);
|
||||||
|
|
||||||
|
EXPECT_EQ(fileRead.value_or(""), "DEBUG ]: Hi file!\n");
|
||||||
|
|
||||||
|
std::error_code ec;
|
||||||
|
std::filesystem::remove("./loggerFile.log", ec);
|
||||||
|
|
||||||
|
// TODO: maybe find a way to test the times and color?
|
||||||
|
logger.setEnableStdout(true);
|
||||||
|
logger.setTime(true);
|
||||||
|
|
||||||
|
logger.log(Hyprutils::CLI::LOG_WARN, "Timed warning!");
|
||||||
|
|
||||||
|
logger.setEnableColor(false);
|
||||||
|
|
||||||
|
logger.log(Hyprutils::CLI::LOG_CRIT, "rip");
|
||||||
|
|
||||||
|
logger.setEnableRolling(true);
|
||||||
|
|
||||||
|
// spam some logs to check rolling
|
||||||
|
for (size_t i = 0; i < 200; ++i) {
|
||||||
|
logger.log(LOG_ERR, "Oh noes!!!");
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_TRUE(logger.rollingLog().size() < 4096);
|
||||||
|
EXPECT_TRUE(logger.rollingLog().starts_with("ERR")); // test the breaking is done correctly
|
||||||
|
|
||||||
|
// test scoping
|
||||||
|
CLogger* pLogger = new CLogger();
|
||||||
|
CLoggerConnection* pConnection = new CLoggerConnection(*pLogger);
|
||||||
|
|
||||||
|
pLogger->setEnableStdout(false);
|
||||||
|
|
||||||
|
pConnection->log(LOG_DEBUG, "This shouldn't log anything.");
|
||||||
|
|
||||||
|
EXPECT_TRUE(pLogger->rollingLog().empty());
|
||||||
|
|
||||||
|
delete pLogger;
|
||||||
|
|
||||||
|
pConnection->log(LOG_DEBUG, "This shouldn't do anything, or crash.");
|
||||||
|
}
|
||||||
80
tests/i18n/Engine.cpp
Normal file
80
tests/i18n/Engine.cpp
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
|
||||||
|
#include <i18n/I18nEngine.hpp>
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
using namespace Hyprutils::I18n;
|
||||||
|
|
||||||
|
enum eTxtKeys : uint64_t {
|
||||||
|
TXT_KEY_HELLO,
|
||||||
|
TXT_KEY_I_HAVE_APPLES,
|
||||||
|
TXT_KEY_FALLBACK,
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST(I18n, Engine) {
|
||||||
|
CI18nEngine engine;
|
||||||
|
|
||||||
|
engine.setFallbackLocale("en_US");
|
||||||
|
|
||||||
|
engine.registerEntry("en_US", TXT_KEY_HELLO, "Hello World!");
|
||||||
|
engine.registerEntry("en_US", TXT_KEY_I_HAVE_APPLES, [](const translationVarMap& m) {
|
||||||
|
if (std::stoi(m.at("count")) == 1)
|
||||||
|
return "I have {count} apple.";
|
||||||
|
else
|
||||||
|
return "I have {count} apples.";
|
||||||
|
});
|
||||||
|
engine.registerEntry("en_US", TXT_KEY_FALLBACK, "Fallback string!");
|
||||||
|
|
||||||
|
engine.registerEntry("pl_PL", TXT_KEY_HELLO, "Witaj świecie!");
|
||||||
|
engine.registerEntry("pl_PL", TXT_KEY_I_HAVE_APPLES, [](const translationVarMap& m) {
|
||||||
|
const auto COUNT = std::stoi(m.at("count"));
|
||||||
|
if (COUNT == 1)
|
||||||
|
return "Mam {count} jabłko.";
|
||||||
|
else if (COUNT < 5)
|
||||||
|
return "Mam {count} jabłka.";
|
||||||
|
else
|
||||||
|
return "Mam {count} jabłek.";
|
||||||
|
});
|
||||||
|
|
||||||
|
engine.registerEntry("es_XX", TXT_KEY_FALLBACK, "I don't speak spanish");
|
||||||
|
engine.registerEntry("es_ES", TXT_KEY_FALLBACK, "I don't speak spanish here either");
|
||||||
|
engine.registerEntry("ts_TST", TXT_KEY_FALLBACK, "Hello {var1} world {var2}");
|
||||||
|
|
||||||
|
engine.registerEntry("am", TXT_KEY_FALLBACK, "Amongus!");
|
||||||
|
|
||||||
|
EXPECT_EQ(engine.localizeEntry("en_US", TXT_KEY_HELLO, {}), "Hello World!");
|
||||||
|
EXPECT_EQ(engine.localizeEntry("pl_PL", TXT_KEY_HELLO, {}), "Witaj świecie!");
|
||||||
|
EXPECT_EQ(engine.localizeEntry("de_DE", TXT_KEY_HELLO, {}), "Hello World!");
|
||||||
|
|
||||||
|
EXPECT_EQ(engine.localizeEntry("en_US", TXT_KEY_I_HAVE_APPLES, {{"count", "1"}}), "I have 1 apple.");
|
||||||
|
EXPECT_EQ(engine.localizeEntry("en_US", TXT_KEY_I_HAVE_APPLES, {{"count", "2"}}), "I have 2 apples.");
|
||||||
|
|
||||||
|
EXPECT_EQ(engine.localizeEntry("pl_PL", TXT_KEY_I_HAVE_APPLES, {{"count", "1"}}), "Mam 1 jabłko.");
|
||||||
|
EXPECT_EQ(engine.localizeEntry("pl_PL", TXT_KEY_I_HAVE_APPLES, {{"count", "2"}}), "Mam 2 jabłka.");
|
||||||
|
EXPECT_EQ(engine.localizeEntry("pl_PL", TXT_KEY_I_HAVE_APPLES, {{"count", "5"}}), "Mam 5 jabłek.");
|
||||||
|
|
||||||
|
EXPECT_EQ(engine.localizeEntry("pl", TXT_KEY_I_HAVE_APPLES, {{"count", "5"}}), "Mam 5 jabłek.");
|
||||||
|
|
||||||
|
EXPECT_EQ(engine.localizeEntry("pl_XX", TXT_KEY_I_HAVE_APPLES, {{"count", "5"}}), "Mam 5 jabłek.");
|
||||||
|
EXPECT_EQ(engine.localizeEntry("en_XX", TXT_KEY_I_HAVE_APPLES, {{"count", "2"}}), "I have 2 apples.");
|
||||||
|
|
||||||
|
EXPECT_EQ(engine.localizeEntry("es_YY", TXT_KEY_FALLBACK, {}), "I don't speak spanish here either");
|
||||||
|
EXPECT_EQ(engine.localizeEntry("es_XX", TXT_KEY_FALLBACK, {}), "I don't speak spanish");
|
||||||
|
|
||||||
|
EXPECT_EQ(engine.localizeEntry("pl_PL", TXT_KEY_FALLBACK, {}), "Fallback string!");
|
||||||
|
|
||||||
|
EXPECT_EQ(engine.localizeEntry("am_AM", TXT_KEY_FALLBACK, {}), "Amongus!");
|
||||||
|
|
||||||
|
// test weird translations
|
||||||
|
engine.registerEntry("ts", TXT_KEY_HELLO, "count}");
|
||||||
|
EXPECT_EQ(engine.localizeEntry("ts", TXT_KEY_HELLO, {{"count", "1"}}), "count}");
|
||||||
|
|
||||||
|
engine.registerEntry("ts", TXT_KEY_HELLO, "{count");
|
||||||
|
EXPECT_EQ(engine.localizeEntry("ts", TXT_KEY_HELLO, {{"count", "1"}}), "{count");
|
||||||
|
|
||||||
|
EXPECT_EQ(engine.localizeEntry("ts", 42069 /* invalid key */, {{"count", "1"}}), "");
|
||||||
|
|
||||||
|
EXPECT_EQ(engine.localizeEntry("ts_TST", TXT_KEY_FALLBACK, {{"var1", "hi"}, {"var2", "!"}}), "Hello hi world !");
|
||||||
|
// Order shouldn't matter
|
||||||
|
EXPECT_EQ(engine.localizeEntry("ts_TST", TXT_KEY_FALLBACK, {{"var2", "!"}, {"var1", "hi"}}), "Hello hi world !");
|
||||||
|
}
|
||||||
11
tests/i18n/Locale.cpp
Normal file
11
tests/i18n/Locale.cpp
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <i18n/I18nEngine.hpp>
|
||||||
|
|
||||||
|
using namespace Hyprutils::I18n;
|
||||||
|
|
||||||
|
TEST(I18n, Locale) {
|
||||||
|
EXPECT_EQ(extractLocale("pl_PL.UTF-8"), "pl_PL");
|
||||||
|
EXPECT_EQ(extractLocale("POSIX"), "en_US");
|
||||||
|
EXPECT_EQ(extractLocale("*"), "en_US");
|
||||||
|
EXPECT_EQ(extractLocale("LC_CTYPE=pl_PL.UTF-8"), "pl_PL");
|
||||||
|
}
|
||||||
73
tests/math/Box.cpp
Normal file
73
tests/math/Box.cpp
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
#include <hyprutils/math/Box.hpp>
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
using namespace Hyprutils::Math;
|
||||||
|
|
||||||
|
TEST(Math, box) {
|
||||||
|
// Test default constructor and accessors
|
||||||
|
{
|
||||||
|
CBox box1;
|
||||||
|
EXPECT_EQ(box1.x, 0);
|
||||||
|
EXPECT_EQ(box1.y, 0);
|
||||||
|
EXPECT_EQ(box1.width, 0);
|
||||||
|
EXPECT_EQ(box1.height, 0);
|
||||||
|
|
||||||
|
// Test parameterized constructor and accessors
|
||||||
|
CBox box2(10, 20, 30, 40);
|
||||||
|
EXPECT_EQ(box2.x, 10);
|
||||||
|
EXPECT_EQ(box2.y, 20);
|
||||||
|
EXPECT_EQ(box2.width, 30);
|
||||||
|
EXPECT_EQ(box2.height, 40);
|
||||||
|
|
||||||
|
// Test setters and getters
|
||||||
|
box2.translate(Vector2D(5, -5));
|
||||||
|
EXPECT_EQ(box2.pos(), Vector2D(15, 15));
|
||||||
|
}
|
||||||
|
|
||||||
|
//Test Scaling and Transformation
|
||||||
|
{
|
||||||
|
CBox box(10, 10, 20, 30);
|
||||||
|
|
||||||
|
// Test scaling
|
||||||
|
box.scale(2.0);
|
||||||
|
EXPECT_EQ(box.size(), Vector2D(40, 60));
|
||||||
|
EXPECT_EQ(box.pos(), Vector2D(20, 20));
|
||||||
|
|
||||||
|
// Test scaling from center
|
||||||
|
box.scaleFromCenter(0.5);
|
||||||
|
EXPECT_EQ(box.size(), Vector2D(20, 30));
|
||||||
|
EXPECT_EQ(box.pos(), Vector2D(30, 35));
|
||||||
|
|
||||||
|
// Test transformation
|
||||||
|
box.transform(HYPRUTILS_TRANSFORM_90, 100, 200);
|
||||||
|
EXPECT_EQ(box.pos(), Vector2D(135, 30));
|
||||||
|
EXPECT_EQ(box.size(), Vector2D(30, 20));
|
||||||
|
|
||||||
|
// Test Intersection and Extents
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
CBox box1(0, 0, 100, 100);
|
||||||
|
CBox box2(50, 50, 100, 100);
|
||||||
|
|
||||||
|
CBox intersection = box1.intersection(box2);
|
||||||
|
EXPECT_EQ(intersection.pos(), Vector2D(50, 50));
|
||||||
|
EXPECT_EQ(intersection.size(), Vector2D(50, 50));
|
||||||
|
|
||||||
|
SBoxExtents extents = box1.extentsFrom(box2);
|
||||||
|
EXPECT_EQ(extents.topLeft, Vector2D(50, 50));
|
||||||
|
EXPECT_EQ(extents.bottomRight, Vector2D(-50, -50));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Boundary Conditions and Special Cases
|
||||||
|
{
|
||||||
|
CBox box(0, 0, 50, 50);
|
||||||
|
|
||||||
|
EXPECT_EQ(box.empty(), false);
|
||||||
|
|
||||||
|
EXPECT_EQ(box.containsPoint(Vector2D(25, 25)), true);
|
||||||
|
EXPECT_EQ(box.containsPoint(Vector2D(60, 60)), false);
|
||||||
|
EXPECT_EQ(box.overlaps(CBox(25, 25, 50, 50)), true);
|
||||||
|
EXPECT_EQ(box.inside(CBox(0, 0, 100, 100)), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
23
tests/math/Mat3x3.cpp
Normal file
23
tests/math/Mat3x3.cpp
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
#include <hyprutils/math/Mat3x3.hpp>
|
||||||
|
#include <hyprutils/math/Box.hpp>
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
using namespace Hyprutils::Math;
|
||||||
|
|
||||||
|
TEST(Math, mat3x3) {
|
||||||
|
Mat3x3 jeremy = Mat3x3::outputProjection({1920, 1080}, HYPRUTILS_TRANSFORM_FLIPPED_90);
|
||||||
|
Mat3x3 matrixBox = jeremy.projectBox(CBox{10, 10, 200, 200}, HYPRUTILS_TRANSFORM_NORMAL).translate({100, 100}).scale({1.25F, 1.5F}).transpose();
|
||||||
|
|
||||||
|
Mat3x3 expected = std::array<float, 9>{0, 0.46296296, 0, 0.3125, 0, 0, 19.84375, 36.055557, 1};
|
||||||
|
// we need to do this to avoid precision errors on 32-bit archs
|
||||||
|
EXPECT_EQ(std::abs(expected.getMatrix().at(0) - matrixBox.getMatrix().at(0)) < 0.1, true);
|
||||||
|
EXPECT_EQ(std::abs(expected.getMatrix().at(1) - matrixBox.getMatrix().at(1)) < 0.1, true);
|
||||||
|
EXPECT_EQ(std::abs(expected.getMatrix().at(2) - matrixBox.getMatrix().at(2)) < 0.1, true);
|
||||||
|
EXPECT_EQ(std::abs(expected.getMatrix().at(3) - matrixBox.getMatrix().at(3)) < 0.1, true);
|
||||||
|
EXPECT_EQ(std::abs(expected.getMatrix().at(4) - matrixBox.getMatrix().at(4)) < 0.1, true);
|
||||||
|
EXPECT_EQ(std::abs(expected.getMatrix().at(5) - matrixBox.getMatrix().at(5)) < 0.1, true);
|
||||||
|
EXPECT_EQ(std::abs(expected.getMatrix().at(6) - matrixBox.getMatrix().at(6)) < 0.1, true);
|
||||||
|
EXPECT_EQ(std::abs(expected.getMatrix().at(7) - matrixBox.getMatrix().at(7)) < 0.1, true);
|
||||||
|
EXPECT_EQ(std::abs(expected.getMatrix().at(8) - matrixBox.getMatrix().at(8)) < 0.1, true);
|
||||||
|
}
|
||||||
18
tests/math/Region.cpp
Normal file
18
tests/math/Region.cpp
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
#include <hyprutils/math/Region.hpp>
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
using namespace Hyprutils::Math;
|
||||||
|
|
||||||
|
TEST(Math, region) {
|
||||||
|
CRegion rg(CBox{{20, 20}, {40, 40}});
|
||||||
|
|
||||||
|
auto extents = rg.getExtents();
|
||||||
|
EXPECT_EQ(extents.pos(), Vector2D(20, 20));
|
||||||
|
EXPECT_EQ(extents.size(), Vector2D(40, 40));
|
||||||
|
|
||||||
|
rg.scale(2);
|
||||||
|
extents = rg.getExtents();
|
||||||
|
EXPECT_EQ(extents.pos(), Vector2D(40, 40));
|
||||||
|
EXPECT_EQ(extents.size(), Vector2D(80, 80));
|
||||||
|
}
|
||||||
19
tests/math/Vector2D.cpp
Normal file
19
tests/math/Vector2D.cpp
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
#include <hyprutils/math/Vector2D.hpp>
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
using namespace Hyprutils::Math;
|
||||||
|
|
||||||
|
TEST(Math, vector2d) {
|
||||||
|
Vector2D original(30, 40);
|
||||||
|
Vector2D monitorSize(100, 200);
|
||||||
|
|
||||||
|
EXPECT_EQ(original.transform(HYPRUTILS_TRANSFORM_NORMAL, monitorSize), Vector2D(30, 40));
|
||||||
|
EXPECT_EQ(original.transform(HYPRUTILS_TRANSFORM_90, monitorSize), Vector2D(40, 200 - 30));
|
||||||
|
EXPECT_EQ(original.transform(HYPRUTILS_TRANSFORM_180, monitorSize), Vector2D(100 - 30, 200 - 40));
|
||||||
|
EXPECT_EQ(original.transform(HYPRUTILS_TRANSFORM_270, monitorSize), Vector2D(100 - 40, 30));
|
||||||
|
EXPECT_EQ(original.transform(HYPRUTILS_TRANSFORM_FLIPPED, monitorSize), Vector2D(100 - 30, 40));
|
||||||
|
EXPECT_EQ(original.transform(HYPRUTILS_TRANSFORM_FLIPPED_90, monitorSize), Vector2D(40, 30));
|
||||||
|
EXPECT_EQ(original.transform(HYPRUTILS_TRANSFORM_FLIPPED_180, monitorSize), Vector2D(30, 200 - 40));
|
||||||
|
EXPECT_EQ(original.transform(HYPRUTILS_TRANSFORM_FLIPPED_270, monitorSize), Vector2D(100 - 40, 200 - 30));
|
||||||
|
}
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
#include <hyprutils/memory/WeakPtr.hpp>
|
|
||||||
#include <hyprutils/memory/Atomic.hpp>
|
#include <hyprutils/memory/Atomic.hpp>
|
||||||
#include <hyprutils/memory/Casts.hpp>
|
#include <hyprutils/memory/SharedPtr.hpp>
|
||||||
|
#include <hyprutils/memory/WeakPtr.hpp>
|
||||||
using namespace Hyprutils::Memory;
|
|
||||||
|
|
||||||
#ifdef HU_UNIT_TESTS
|
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
#include <chrono>
|
||||||
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
using namespace Hyprutils::Memory;
|
||||||
|
|
||||||
#define SP CSharedPointer
|
#define SP CSharedPointer
|
||||||
#define WP CWeakPointer
|
#define WP CWeakPointer
|
||||||
|
|
@ -121,6 +123,119 @@ static void testAtomicImpl() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class InterfaceA {
|
||||||
|
public:
|
||||||
|
virtual ~InterfaceA() = default;
|
||||||
|
int m_ifaceAInt = 69;
|
||||||
|
int m_ifaceAShit = 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
class InterfaceB {
|
||||||
|
public:
|
||||||
|
virtual ~InterfaceB() = default;
|
||||||
|
int m_ifaceBInt = 2;
|
||||||
|
int m_ifaceBShit = 3;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CChild : public InterfaceA, public InterfaceB {
|
||||||
|
public:
|
||||||
|
virtual ~CChild() = default;
|
||||||
|
int m_childInt = 4;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CChildA : public InterfaceA {
|
||||||
|
public:
|
||||||
|
int m_childAInt = 4;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void testHierarchy() {
|
||||||
|
// Same test for atomic and non-atomic
|
||||||
|
{
|
||||||
|
SP<CChildA> childA = makeShared<CChildA>();
|
||||||
|
auto ifaceA = SP<InterfaceA>(childA);
|
||||||
|
EXPECT_TRUE(ifaceA);
|
||||||
|
EXPECT_EQ(ifaceA->m_ifaceAInt, 69);
|
||||||
|
|
||||||
|
auto ifaceB = dynamicPointerCast<InterfaceA>(SP<CChildA>{});
|
||||||
|
EXPECT_TRUE(!ifaceB);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
SP<CChild> child = makeShared<CChild>();
|
||||||
|
SP<InterfaceA> ifaceA = dynamicPointerCast<InterfaceA>(child);
|
||||||
|
SP<InterfaceB> ifaceB = dynamicPointerCast<InterfaceB>(child);
|
||||||
|
EXPECT_TRUE(ifaceA);
|
||||||
|
EXPECT_TRUE(ifaceB);
|
||||||
|
|
||||||
|
EXPECT_EQ(ifaceA->m_ifaceAInt, 69);
|
||||||
|
EXPECT_EQ(ifaceB->m_ifaceBInt, 2);
|
||||||
|
|
||||||
|
WP<InterfaceA> ifaceAWeak = ifaceA;
|
||||||
|
|
||||||
|
child.reset();
|
||||||
|
EXPECT_TRUE(ifaceAWeak);
|
||||||
|
EXPECT_TRUE(ifaceA);
|
||||||
|
EXPECT_EQ(ifaceAWeak->m_ifaceAInt, 69);
|
||||||
|
EXPECT_EQ(ifaceA->m_ifaceAInt, 69);
|
||||||
|
ifaceA.reset();
|
||||||
|
EXPECT_TRUE(ifaceAWeak);
|
||||||
|
EXPECT_EQ(ifaceAWeak->m_ifaceAInt, 69);
|
||||||
|
EXPECT_TRUE(ifaceB);
|
||||||
|
EXPECT_EQ(ifaceB->m_ifaceBInt, 2);
|
||||||
|
ifaceB.reset();
|
||||||
|
EXPECT_TRUE(!ifaceAWeak);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
{
|
||||||
|
ASP<CChildA> childA = makeAtomicShared<CChildA>();
|
||||||
|
auto ifaceA = ASP<InterfaceA>(childA);
|
||||||
|
EXPECT_TRUE(ifaceA);
|
||||||
|
EXPECT_EQ(ifaceA->m_ifaceAInt, 69);
|
||||||
|
|
||||||
|
auto ifaceB = dynamicPointerCast<InterfaceA>(ASP<CChildA>{});
|
||||||
|
EXPECT_TRUE(!ifaceB);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
ASP<CChild> child = makeAtomicShared<CChild>();
|
||||||
|
ASP<InterfaceA> ifaceA = dynamicPointerCast<InterfaceA>(child);
|
||||||
|
ASP<InterfaceB> ifaceB = dynamicPointerCast<InterfaceB>(child);
|
||||||
|
EXPECT_TRUE(ifaceA);
|
||||||
|
EXPECT_TRUE(ifaceB);
|
||||||
|
|
||||||
|
EXPECT_EQ(ifaceA->m_ifaceAInt, 69);
|
||||||
|
EXPECT_EQ(ifaceB->m_ifaceBInt, 2);
|
||||||
|
|
||||||
|
AWP<InterfaceA> ifaceAWeak = ifaceA;
|
||||||
|
AWP<InterfaceB> ifaceBWeak = dynamicPointerCast<InterfaceB>(ifaceA);
|
||||||
|
|
||||||
|
child.reset();
|
||||||
|
EXPECT_TRUE(ifaceAWeak);
|
||||||
|
EXPECT_TRUE(ifaceBWeak);
|
||||||
|
EXPECT_TRUE(ifaceA);
|
||||||
|
EXPECT_EQ(ifaceAWeak->m_ifaceAInt, 69);
|
||||||
|
EXPECT_EQ(ifaceA->m_ifaceAInt, 69);
|
||||||
|
EXPECT_EQ(ifaceBWeak->m_ifaceBInt, 2);
|
||||||
|
ifaceA.reset();
|
||||||
|
EXPECT_TRUE(ifaceAWeak);
|
||||||
|
EXPECT_EQ(ifaceAWeak->m_ifaceAInt, 69);
|
||||||
|
EXPECT_TRUE(ifaceB);
|
||||||
|
EXPECT_EQ(ifaceB->m_ifaceBInt, 2);
|
||||||
|
EXPECT_EQ(ifaceBWeak->m_ifaceBInt, 2);
|
||||||
|
ifaceB.reset();
|
||||||
|
EXPECT_TRUE(!ifaceAWeak);
|
||||||
|
EXPECT_TRUE(!ifaceBWeak);
|
||||||
|
}
|
||||||
|
|
||||||
|
// test for leaks
|
||||||
|
for (size_t i = 0; i < 10000; ++i) {
|
||||||
|
auto child = makeAtomicShared<CChild>();
|
||||||
|
auto child2 = makeShared<CChild>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST(Memory, memory) {
|
TEST(Memory, memory) {
|
||||||
SP<int> intPtr = makeShared<int>(10);
|
SP<int> intPtr = makeShared<int>(10);
|
||||||
SP<int> intPtr2 = makeShared<int>(-1337);
|
SP<int> intPtr2 = makeShared<int>(-1337);
|
||||||
|
|
@ -174,6 +289,6 @@ TEST(Memory, memory) {
|
||||||
EXPECT_EQ(*intPtr2, 10);
|
EXPECT_EQ(*intPtr2, 10);
|
||||||
|
|
||||||
testAtomicImpl();
|
testAtomicImpl();
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
testHierarchy();
|
||||||
|
}
|
||||||
45
tests/os/Fd.cpp
Normal file
45
tests/os/Fd.cpp
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
#include <hyprutils/os/FileDescriptor.hpp>
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
|
||||||
|
using namespace Hyprutils::OS;
|
||||||
|
|
||||||
|
TEST(OS, fd) {
|
||||||
|
std::string name = "/test_filedescriptors";
|
||||||
|
CFileDescriptor fd(shm_open(name.c_str(), O_RDWR | O_CREAT | O_EXCL, 0600));
|
||||||
|
|
||||||
|
EXPECT_EQ(fd.isValid(), true);
|
||||||
|
EXPECT_EQ(fd.isReadable(), true);
|
||||||
|
|
||||||
|
int flags = fd.getFlags();
|
||||||
|
EXPECT_EQ(fd.getFlags(), FD_CLOEXEC);
|
||||||
|
flags &= ~FD_CLOEXEC;
|
||||||
|
fd.setFlags(flags);
|
||||||
|
EXPECT_EQ(fd.getFlags(), !FD_CLOEXEC);
|
||||||
|
|
||||||
|
CFileDescriptor fd2 = fd.duplicate();
|
||||||
|
EXPECT_EQ(fd.isValid(), true);
|
||||||
|
EXPECT_EQ(fd.isReadable(), true);
|
||||||
|
EXPECT_EQ(fd2.isValid(), true);
|
||||||
|
EXPECT_EQ(fd2.isReadable(), true);
|
||||||
|
|
||||||
|
CFileDescriptor fd3(fd2.take());
|
||||||
|
EXPECT_EQ(fd.isValid(), true);
|
||||||
|
EXPECT_EQ(fd.isReadable(), true);
|
||||||
|
EXPECT_EQ(fd2.isValid(), false);
|
||||||
|
EXPECT_EQ(fd2.isReadable(), false);
|
||||||
|
|
||||||
|
// .duplicate default flags is FD_CLOEXEC
|
||||||
|
EXPECT_EQ(fd3.getFlags(), FD_CLOEXEC);
|
||||||
|
|
||||||
|
fd.reset();
|
||||||
|
fd2.reset();
|
||||||
|
fd3.reset();
|
||||||
|
|
||||||
|
EXPECT_EQ(fd.isReadable(), false);
|
||||||
|
EXPECT_EQ(fd2.isReadable(), false);
|
||||||
|
EXPECT_EQ(fd3.isReadable(), false);
|
||||||
|
|
||||||
|
shm_unlink(name.c_str());
|
||||||
|
}
|
||||||
28
tests/os/Process.cpp
Normal file
28
tests/os/Process.cpp
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
#include <hyprutils/os/Process.hpp>
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
using namespace Hyprutils::OS;
|
||||||
|
|
||||||
|
TEST(OS, process) {
|
||||||
|
CProcess process("sh", {"-c", "echo \"Hello $WORLD!\""});
|
||||||
|
process.addEnv("WORLD", "World");
|
||||||
|
|
||||||
|
EXPECT_EQ(process.runAsync(), true);
|
||||||
|
EXPECT_EQ(process.runSync(), true);
|
||||||
|
|
||||||
|
EXPECT_EQ(process.stdOut(), std::string{"Hello World!\n"});
|
||||||
|
EXPECT_EQ(process.stdErr(), std::string{""});
|
||||||
|
EXPECT_EQ(process.exitCode(), 0);
|
||||||
|
|
||||||
|
CProcess process2("sh", {"-c", "while true; do sleep 1; done;"});
|
||||||
|
|
||||||
|
EXPECT_EQ(process2.runAsync(), true);
|
||||||
|
EXPECT_EQ(getpgid(process2.pid()) >= 0, true);
|
||||||
|
|
||||||
|
kill(process2.pid(), SIGKILL);
|
||||||
|
|
||||||
|
CProcess process3("sh", {"-c", "cat /geryueruggbuergheruger/reugiheruygyuerghuryeghyer/eruihgyuerguyerghyuerghuyergerguyer/NON_EXISTENT"});
|
||||||
|
EXPECT_EQ(process3.runSync(), true);
|
||||||
|
EXPECT_EQ(process3.exitCode(), 1);
|
||||||
|
}
|
||||||
369
tests/signal/Signal.cpp
Normal file
369
tests/signal/Signal.cpp
Normal file
|
|
@ -0,0 +1,369 @@
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <any>
|
||||||
|
#include <hyprutils/signal/Signal.hpp>
|
||||||
|
#include <hyprutils/signal/Listener.hpp>
|
||||||
|
#include <hyprutils/memory/WeakPtr.hpp>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
using namespace Hyprutils::Signal;
|
||||||
|
using namespace Hyprutils::Memory;
|
||||||
|
|
||||||
|
#pragma GCC diagnostic push
|
||||||
|
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||||
|
//
|
||||||
|
|
||||||
|
static void legacy() {
|
||||||
|
CSignal signal;
|
||||||
|
int data = 0;
|
||||||
|
auto listener = signal.registerListener([&]([[maybe_unused]] std::any d) { data = 1; });
|
||||||
|
|
||||||
|
signal.emit();
|
||||||
|
|
||||||
|
EXPECT_EQ(data, 1);
|
||||||
|
|
||||||
|
data = 0;
|
||||||
|
|
||||||
|
listener.reset();
|
||||||
|
|
||||||
|
signal.emit();
|
||||||
|
|
||||||
|
EXPECT_EQ(data, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void legacyListenerEmit() {
|
||||||
|
int data = 0;
|
||||||
|
CSignal signal;
|
||||||
|
auto listener = signal.registerListener([&](std::any d) { data = std::any_cast<int>(d); });
|
||||||
|
|
||||||
|
listener->emit(1); // not a typo
|
||||||
|
EXPECT_EQ(data, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void legacyListeners() {
|
||||||
|
int data = 0;
|
||||||
|
|
||||||
|
CSignalT<> signal0;
|
||||||
|
CSignalT<int> signal1;
|
||||||
|
|
||||||
|
auto listener0 = signal0.registerListener([&](std::any d) { data += 1; });
|
||||||
|
auto listener1 = signal1.registerListener([&](std::any d) { data += std::any_cast<int>(d); });
|
||||||
|
|
||||||
|
signal0.registerStaticListener([&](void* o, std::any d) { data += 10; }, nullptr);
|
||||||
|
signal1.registerStaticListener([&](void* o, std::any d) { data += std::any_cast<int>(d) * 10; }, nullptr);
|
||||||
|
|
||||||
|
signal0.emit();
|
||||||
|
signal1.emit(2);
|
||||||
|
|
||||||
|
EXPECT_EQ(data, 33);
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma GCC diagnostic pop
|
||||||
|
//
|
||||||
|
|
||||||
|
static void empty() {
|
||||||
|
int data = 0;
|
||||||
|
|
||||||
|
CSignalT<> signal;
|
||||||
|
auto listener = signal.listen([&] { data = 1; });
|
||||||
|
|
||||||
|
signal.emit();
|
||||||
|
EXPECT_EQ(data, 1);
|
||||||
|
|
||||||
|
data = 0;
|
||||||
|
listener.reset();
|
||||||
|
signal.emit();
|
||||||
|
EXPECT_EQ(data, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void typed() {
|
||||||
|
int data = 0;
|
||||||
|
|
||||||
|
CSignalT<int> signal;
|
||||||
|
auto listener = signal.listen([&](int newData) { data = newData; });
|
||||||
|
|
||||||
|
signal.emit(1);
|
||||||
|
EXPECT_EQ(data, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ignoreParams() {
|
||||||
|
int data = 0;
|
||||||
|
|
||||||
|
CSignalT<int> signal;
|
||||||
|
auto listener = signal.listen([&] { data += 1; });
|
||||||
|
|
||||||
|
signal.listenStatic([&] { data += 1; });
|
||||||
|
|
||||||
|
signal.emit(2);
|
||||||
|
EXPECT_EQ(data, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void typedMany() {
|
||||||
|
int data1 = 0;
|
||||||
|
int data2 = 0;
|
||||||
|
int data3 = 0;
|
||||||
|
|
||||||
|
CSignalT<int, int, int> signal;
|
||||||
|
auto listener = signal.listen([&](int d1, int d2, int d3) {
|
||||||
|
data1 = d1;
|
||||||
|
data2 = d2;
|
||||||
|
data3 = d3;
|
||||||
|
});
|
||||||
|
|
||||||
|
signal.emit(1, 2, 3);
|
||||||
|
EXPECT_EQ(data1, 1);
|
||||||
|
EXPECT_EQ(data2, 2);
|
||||||
|
EXPECT_EQ(data3, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ref() {
|
||||||
|
int count = 0;
|
||||||
|
int data = 0;
|
||||||
|
|
||||||
|
CSignalT<int&> signal;
|
||||||
|
auto l1 = signal.listen([&](int& v) { v += 1; });
|
||||||
|
auto l2 = signal.listen([&](int v) { count += v; });
|
||||||
|
signal.emit(data);
|
||||||
|
|
||||||
|
CSignalT<const int&> constSignal;
|
||||||
|
auto l3 = constSignal.listen([&](const int& v) { count += v; });
|
||||||
|
auto l4 = constSignal.listen([&](int v) { count += v; });
|
||||||
|
constSignal.emit(data);
|
||||||
|
|
||||||
|
EXPECT_EQ(data, 1);
|
||||||
|
EXPECT_EQ(count, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void refMany() {
|
||||||
|
int count = 0;
|
||||||
|
int data1 = 0;
|
||||||
|
int data2 = 10;
|
||||||
|
|
||||||
|
CSignalT<int&, const int&> signal;
|
||||||
|
auto l1 = signal.listen([&](int& v, const int&) { v += 1; });
|
||||||
|
auto l2 = signal.listen([&](int v1, int v2) { count += v1 + v2; });
|
||||||
|
|
||||||
|
signal.emit(data1, data2);
|
||||||
|
EXPECT_EQ(data1, 1);
|
||||||
|
EXPECT_EQ(count, 11);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void autoRefTypes() {
|
||||||
|
class CCopyCounter {
|
||||||
|
public:
|
||||||
|
CCopyCounter(int& createCount, int& destroyCount) : createCount(createCount), destroyCount(destroyCount) {
|
||||||
|
createCount += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
CCopyCounter(CCopyCounter&& other) noexcept : CCopyCounter(other.createCount, other.destroyCount) {}
|
||||||
|
CCopyCounter(const CCopyCounter& other) noexcept : CCopyCounter(other.createCount, other.destroyCount) {}
|
||||||
|
|
||||||
|
~CCopyCounter() {
|
||||||
|
destroyCount += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
int& createCount;
|
||||||
|
int& destroyCount;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto createCount = 0;
|
||||||
|
auto destroyCount = 0;
|
||||||
|
|
||||||
|
CSignalT<CCopyCounter> signal;
|
||||||
|
auto listener = signal.listen([](const CCopyCounter& counter) {});
|
||||||
|
|
||||||
|
signal.emit(CCopyCounter(createCount, destroyCount));
|
||||||
|
EXPECT_EQ(createCount, 1);
|
||||||
|
EXPECT_EQ(destroyCount, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void forward() {
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
CSignalT<int> sig;
|
||||||
|
CSignalT<int> connected1;
|
||||||
|
CSignalT<> connected2;
|
||||||
|
|
||||||
|
auto conn1 = sig.forward(connected1);
|
||||||
|
auto conn2 = sig.forward(connected2);
|
||||||
|
|
||||||
|
auto listener1 = connected1.listen([&](int v) { count += v; });
|
||||||
|
auto listener2 = connected2.listen([&] { count += 1; });
|
||||||
|
|
||||||
|
sig.emit(2);
|
||||||
|
|
||||||
|
EXPECT_EQ(count, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void listenerAdded() {
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
CSignalT<> signal;
|
||||||
|
CHyprSignalListener secondListener;
|
||||||
|
|
||||||
|
auto listener = signal.listen([&] {
|
||||||
|
count += 1;
|
||||||
|
|
||||||
|
if (!secondListener)
|
||||||
|
secondListener = signal.listen([&] { count += 1; });
|
||||||
|
});
|
||||||
|
|
||||||
|
signal.emit();
|
||||||
|
EXPECT_EQ(count, 1); // second should NOT be invoked as it was registed during emit
|
||||||
|
|
||||||
|
signal.emit();
|
||||||
|
EXPECT_EQ(count, 3); // second should be invoked
|
||||||
|
}
|
||||||
|
|
||||||
|
static void lastListenerSwapped() {
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
CSignalT<> signal;
|
||||||
|
CHyprSignalListener removedListener;
|
||||||
|
CHyprSignalListener addedListener;
|
||||||
|
|
||||||
|
auto firstListener = signal.listen([&] {
|
||||||
|
removedListener.reset(); // dropped and should NOT be invoked
|
||||||
|
|
||||||
|
if (!addedListener)
|
||||||
|
addedListener = signal.listen([&] { count += 2; });
|
||||||
|
});
|
||||||
|
|
||||||
|
removedListener = signal.listen([&] { count += 1; });
|
||||||
|
|
||||||
|
signal.emit();
|
||||||
|
EXPECT_EQ(count, 0); // neither the removed nor added listeners should fire
|
||||||
|
|
||||||
|
signal.emit();
|
||||||
|
EXPECT_EQ(count, 2); // only the new listener should fire
|
||||||
|
}
|
||||||
|
|
||||||
|
static void signalDestroyed() {
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
auto signal = std::make_unique<CSignalT<>>();
|
||||||
|
|
||||||
|
// This ensures a destructor of a listener called before signal reset is safe.
|
||||||
|
auto preListener = signal->listen([&] { count += 1; });
|
||||||
|
|
||||||
|
auto listener = signal->listen([&] { signal.reset(); });
|
||||||
|
|
||||||
|
// This ensures a destructor of a listener called after signal reset is safe
|
||||||
|
// and gets called.
|
||||||
|
auto postListener = signal->listen([&] { count += 1; });
|
||||||
|
|
||||||
|
signal->emit();
|
||||||
|
EXPECT_EQ(count, 2); // all listeners should fire regardless of signal deletion
|
||||||
|
}
|
||||||
|
|
||||||
|
// purely an asan test
|
||||||
|
static void signalDestroyedBeforeListener() {
|
||||||
|
CHyprSignalListener listener1;
|
||||||
|
CHyprSignalListener listener2;
|
||||||
|
|
||||||
|
CSignalT<> signal;
|
||||||
|
|
||||||
|
listener1 = signal.listen([] {});
|
||||||
|
listener2 = signal.listen([] {});
|
||||||
|
}
|
||||||
|
|
||||||
|
static void signalDestroyedWithAddedListener() {
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
auto signal = std::make_unique<CSignalT<>>();
|
||||||
|
CHyprSignalListener shouldNotRun;
|
||||||
|
|
||||||
|
auto listener = signal->listen([&] {
|
||||||
|
shouldNotRun = signal->listen([&] { count += 2; });
|
||||||
|
signal.reset();
|
||||||
|
});
|
||||||
|
|
||||||
|
signal->emit();
|
||||||
|
EXPECT_EQ(count, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void signalDestroyedWithRemovedAndAddedListener() {
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
auto signal = std::make_unique<CSignalT<>>();
|
||||||
|
CHyprSignalListener removed;
|
||||||
|
CHyprSignalListener shouldNotRun;
|
||||||
|
|
||||||
|
auto listener = signal->listen([&] {
|
||||||
|
removed.reset();
|
||||||
|
shouldNotRun = signal->listen([&] { count += 2; });
|
||||||
|
signal.reset();
|
||||||
|
});
|
||||||
|
|
||||||
|
removed = signal->listen([&] { count += 1; });
|
||||||
|
|
||||||
|
signal->emit();
|
||||||
|
EXPECT_EQ(count, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void staticListener() {
|
||||||
|
int data = 0;
|
||||||
|
|
||||||
|
CSignalT<int> signal;
|
||||||
|
signal.listenStatic([&](int newData) { data = newData; });
|
||||||
|
|
||||||
|
signal.emit(1);
|
||||||
|
EXPECT_EQ(data, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void staticListenerDestroy() {
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
auto signal = makeShared<CSignalT<>>();
|
||||||
|
signal->listenStatic([&] { count += 1; });
|
||||||
|
|
||||||
|
signal->listenStatic([&] {
|
||||||
|
// should not fire but SHOULD be freed
|
||||||
|
signal->listenStatic([&] { count += 3; });
|
||||||
|
|
||||||
|
signal.reset();
|
||||||
|
});
|
||||||
|
|
||||||
|
signal->listenStatic([&] { count += 1; });
|
||||||
|
|
||||||
|
signal->emit();
|
||||||
|
EXPECT_EQ(count, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// purely an asan test
|
||||||
|
static void listenerDestroysSelf() {
|
||||||
|
CSignalT<> signal;
|
||||||
|
|
||||||
|
CHyprSignalListener listener;
|
||||||
|
listener = signal.listen([&] { listener.reset(); });
|
||||||
|
|
||||||
|
// the static signal case is taken care of above
|
||||||
|
|
||||||
|
signal.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Signal, signal) {
|
||||||
|
legacy();
|
||||||
|
legacyListenerEmit();
|
||||||
|
legacyListeners();
|
||||||
|
empty();
|
||||||
|
typed();
|
||||||
|
ignoreParams();
|
||||||
|
typedMany();
|
||||||
|
ref();
|
||||||
|
refMany();
|
||||||
|
autoRefTypes();
|
||||||
|
forward();
|
||||||
|
listenerAdded();
|
||||||
|
lastListenerSwapped();
|
||||||
|
signalDestroyed();
|
||||||
|
signalDestroyedBeforeListener();
|
||||||
|
signalDestroyedWithAddedListener();
|
||||||
|
signalDestroyedWithRemovedAndAddedListener();
|
||||||
|
staticListener();
|
||||||
|
staticListenerDestroy();
|
||||||
|
signalDestroyed();
|
||||||
|
listenerDestroysSelf();
|
||||||
|
}
|
||||||
15
tests/string/ConstVarList.cpp
Normal file
15
tests/string/ConstVarList.cpp
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
#include <hyprutils/string/ConstVarList.hpp>
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
using namespace Hyprutils::String;
|
||||||
|
|
||||||
|
TEST(String, constvarlist) {
|
||||||
|
CConstVarList listConst("hello world!", 0, 's', true);
|
||||||
|
EXPECT_EQ(listConst[0], "hello");
|
||||||
|
EXPECT_EQ(listConst[1], "world!");
|
||||||
|
|
||||||
|
CConstVarList listConst2("0 set", 2, ' ');
|
||||||
|
EXPECT_EQ(listConst2[0], "0");
|
||||||
|
EXPECT_EQ(listConst2[1], "set");
|
||||||
|
}
|
||||||
47
tests/string/String.cpp
Normal file
47
tests/string/String.cpp
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
#include <hyprutils/string/String.hpp>
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
using namespace Hyprutils::String;
|
||||||
|
|
||||||
|
TEST(String, string) {
|
||||||
|
EXPECT_EQ(trim(" a "), "a");
|
||||||
|
EXPECT_EQ(trim(" a a "), "a a");
|
||||||
|
EXPECT_EQ(trim("a"), "a");
|
||||||
|
EXPECT_EQ(trim(" "), "");
|
||||||
|
|
||||||
|
EXPECT_EQ(isNumber("99214123434"), true);
|
||||||
|
EXPECT_EQ(isNumber("-35252345234"), true);
|
||||||
|
EXPECT_EQ(isNumber("---3423--432"), false);
|
||||||
|
EXPECT_EQ(isNumber("s---3423--432"), false);
|
||||||
|
EXPECT_EQ(isNumber("---3423--432s"), false);
|
||||||
|
EXPECT_EQ(isNumber("1s"), false);
|
||||||
|
EXPECT_EQ(isNumber(""), false);
|
||||||
|
EXPECT_EQ(isNumber("-"), false);
|
||||||
|
EXPECT_EQ(isNumber("--0"), false);
|
||||||
|
EXPECT_EQ(isNumber("abc"), false);
|
||||||
|
EXPECT_EQ(isNumber("0.0", true), true);
|
||||||
|
EXPECT_EQ(isNumber("0.2", true), true);
|
||||||
|
EXPECT_EQ(isNumber("0.", true), false);
|
||||||
|
EXPECT_EQ(isNumber(".0", true), false);
|
||||||
|
EXPECT_EQ(isNumber("", true), false);
|
||||||
|
EXPECT_EQ(isNumber("vvss", true), false);
|
||||||
|
EXPECT_EQ(isNumber("0.9999s", true), false);
|
||||||
|
EXPECT_EQ(isNumber("s0.9999", true), false);
|
||||||
|
EXPECT_EQ(isNumber("-1.0", true), true);
|
||||||
|
EXPECT_EQ(isNumber("-1..0", true), false);
|
||||||
|
EXPECT_EQ(isNumber("-10.0000000001", true), true);
|
||||||
|
|
||||||
|
EXPECT_EQ(truthy("frgeujgeruibger"), false);
|
||||||
|
EXPECT_EQ(truthy("false"), false);
|
||||||
|
EXPECT_EQ(truthy("0"), false);
|
||||||
|
EXPECT_EQ(truthy("yees"), false);
|
||||||
|
EXPECT_EQ(truthy("naa"), false);
|
||||||
|
EXPECT_EQ(truthy("-1"), false);
|
||||||
|
EXPECT_EQ(truthy("true"), true);
|
||||||
|
EXPECT_EQ(truthy("true eeee ee"), true);
|
||||||
|
EXPECT_EQ(truthy("yesss"), true);
|
||||||
|
EXPECT_EQ(truthy("1"), true);
|
||||||
|
EXPECT_EQ(truthy("on"), true);
|
||||||
|
EXPECT_EQ(truthy("onn"), true);
|
||||||
|
}
|
||||||
11
tests/string/VarList.cpp
Normal file
11
tests/string/VarList.cpp
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
#include <hyprutils/string/VarList.hpp>
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
using namespace Hyprutils::String;
|
||||||
|
|
||||||
|
TEST(String, varlist) {
|
||||||
|
CVarList list("hello world!", 0, 's', true);
|
||||||
|
EXPECT_EQ(list[0], "hello");
|
||||||
|
EXPECT_EQ(list[1], "world!");
|
||||||
|
}
|
||||||
67
tests/string/VarList2.cpp
Normal file
67
tests/string/VarList2.cpp
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
#include <hyprutils/string/VarList2.hpp>
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
using namespace Hyprutils::String;
|
||||||
|
|
||||||
|
TEST(String, varlist2) {
|
||||||
|
CVarList2 varList2("0 set", 2, ' ');
|
||||||
|
EXPECT_EQ(varList2[0], "0");
|
||||||
|
EXPECT_EQ(varList2[1], "set");
|
||||||
|
|
||||||
|
varList2.append("Hello");
|
||||||
|
|
||||||
|
EXPECT_EQ(varList2[1], "set");
|
||||||
|
EXPECT_EQ(varList2[2], "Hello");
|
||||||
|
EXPECT_EQ(varList2[3], "");
|
||||||
|
EXPECT_EQ(varList2.contains("set"), true);
|
||||||
|
EXPECT_EQ(varList2.contains("sett"), false);
|
||||||
|
EXPECT_EQ(varList2.contains(""), false);
|
||||||
|
EXPECT_EQ(varList2.size(), 3);
|
||||||
|
|
||||||
|
CVarList2 varList2B("hello, world\\, ok?", 0, ',', true, true);
|
||||||
|
EXPECT_EQ(varList2B[0], "hello");
|
||||||
|
EXPECT_EQ(varList2B[1], "world, ok?");
|
||||||
|
|
||||||
|
CVarList2 varList2C("hello, , ok?", 0, ',', true, true);
|
||||||
|
EXPECT_EQ(varList2C[0], "hello");
|
||||||
|
EXPECT_EQ(varList2C[1], "ok?");
|
||||||
|
|
||||||
|
CVarList2 varList2D("\\\\, , ok?", 0, ',', true, true);
|
||||||
|
EXPECT_EQ(varList2D[0], "\\");
|
||||||
|
EXPECT_EQ(varList2D[1], "ok?");
|
||||||
|
|
||||||
|
CVarList2 varList2E("\\, , ok?", 0, ',', true, true);
|
||||||
|
EXPECT_EQ(varList2E[0], ",");
|
||||||
|
EXPECT_EQ(varList2E[1], "ok?");
|
||||||
|
|
||||||
|
CVarList2 varList2F("Hello, world\\\\, ok?", 0, ',', true, true);
|
||||||
|
EXPECT_EQ(varList2F[0], "Hello");
|
||||||
|
EXPECT_EQ(varList2F[1], "world\\");
|
||||||
|
EXPECT_EQ(varList2F[2], "ok?");
|
||||||
|
|
||||||
|
CVarList2 varList2G("Hello,\\, ok?", 0, ',', true, true);
|
||||||
|
EXPECT_EQ(varList2G[0], "Hello");
|
||||||
|
EXPECT_EQ(varList2G[1], ", ok?");
|
||||||
|
|
||||||
|
CVarList2 varList2H("Hello,\\\\, ok?", 0, ',', true, true);
|
||||||
|
EXPECT_EQ(varList2H[0], "Hello");
|
||||||
|
EXPECT_EQ(varList2H[1], "\\");
|
||||||
|
EXPECT_EQ(varList2H[2], "ok?");
|
||||||
|
|
||||||
|
CVarList2 varList2I("Hello,\\, ok?", 0, ',', true, false);
|
||||||
|
EXPECT_EQ(varList2I[0], "Hello");
|
||||||
|
EXPECT_EQ(varList2I[1], "\\");
|
||||||
|
EXPECT_EQ(varList2I[2], "ok?");
|
||||||
|
|
||||||
|
CVarList2 varList2J("", 0, ',', true, false);
|
||||||
|
EXPECT_EQ(varList2J[0], "");
|
||||||
|
|
||||||
|
CVarList2 varList2K(",\\, ok?", 0, ',', true);
|
||||||
|
EXPECT_EQ(varList2K[0], ", ok?");
|
||||||
|
|
||||||
|
CVarList2 varList2L("Hello, world", 0, ',', true);
|
||||||
|
EXPECT_EQ(varList2L.join(" "), "Hello world");
|
||||||
|
EXPECT_EQ(varList2L.join(" ", 0, 1000), "Hello world");
|
||||||
|
EXPECT_EQ(varList2L.join(" ", 0, 1), "Hello");
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue