New upstream version 0.1.5

This commit is contained in:
alan (NyxTrail) 2024-07-01 05:29:41 +00:00
parent 2e14f5bcf3
commit d322427bc5
13 changed files with 1047 additions and 7 deletions

View File

@ -17,7 +17,7 @@ jobs:
run: |
sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf
pacman --noconfirm --noprogressbar -Syyu
pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang libc++
pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang libc++ pixman
- name: Build hyprutils with gcc
run: |
@ -44,7 +44,7 @@ jobs:
run: |
sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf
pacman --noconfirm --noprogressbar -Syyu
pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang libc++
pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang libc++ pixman
- name: Build hyprutils with clang
run: |

View File

@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.19)
set(HYPRUTILS_VERSION "0.1.3")
set(HYPRUTILS_VERSION "0.1.5")
add_compile_definitions(HYPRUTILS_VERSION="${HYPRUTILS_VERSION}")
project(hyprutils
@ -30,6 +30,9 @@ endif()
file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp" "include/*.hpp")
file(GLOB_RECURSE PUBLIC_HEADERS CONFIGURE_DEPENDS "include/*.hpp")
find_package(PkgConfig REQUIRED)
pkg_check_modules(deps REQUIRED IMPORTED_TARGET pixman-1)
add_library(hyprutils SHARED ${SRCFILES})
target_include_directories( hyprutils
PUBLIC "./include"
@ -39,25 +42,31 @@ set_target_properties(hyprutils PROPERTIES
VERSION ${hyprutils_VERSION}
SOVERSION 0
)
target_link_libraries(hyprutils PkgConfig::deps)
# tests
add_custom_target(tests)
add_executable(hyprutils_memory "tests/memory.cpp")
target_link_libraries(hyprutils_memory PRIVATE hyprutils)
target_link_libraries(hyprutils_memory PRIVATE hyprutils PkgConfig::deps)
add_test(NAME "Memory" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND hyprutils_memory "memory")
add_dependencies(tests hyprutils_memory)
add_executable(hyprutils_string "tests/string.cpp")
target_link_libraries(hyprutils_string PRIVATE hyprutils)
target_link_libraries(hyprutils_string PRIVATE hyprutils PkgConfig::deps)
add_test(NAME "String" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND hyprutils_string "string")
add_dependencies(tests hyprutils_string)
add_executable(hyprutils_signal "tests/signal.cpp")
target_link_libraries(hyprutils_signal PRIVATE hyprutils)
target_link_libraries(hyprutils_signal PRIVATE hyprutils PkgConfig::deps)
add_test(NAME "Signal" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND hyprutils_signal "signal")
add_dependencies(tests hyprutils_signal)
add_executable(hyprutils_math "tests/math.cpp")
target_link_libraries(hyprutils_math PRIVATE hyprutils PkgConfig::deps)
add_test(NAME "Math" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND hyprutils_math "math")
add_dependencies(tests hyprutils_math)
# Installation
install(TARGETS hyprutils)
install(DIRECTORY "include/hyprutils" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})

View File

@ -9,6 +9,9 @@ Hyprutils depends on the ABI stability of the stdlib implementation of your comp
## Building
```sh
git clone https://github.com/hyprwm/hyprutils.git
cd hyprutils/
cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -S . -B ./build
cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf _NPROCESSORS_CONF`
sudo cmake --install build
```

View File

@ -0,0 +1,182 @@
#pragma once
#include "./Vector2D.hpp"
#include "./Misc.hpp"
namespace Hyprutils::Math {
/**
* @brief Represents the extents of a bounding box.
*/
struct SBoxExtents {
Vector2D topLeft;
Vector2D bottomRight;
/**
* @brief Scales the extents by a given factor.
* @param scale The scaling factor.
* @return Scaled SBoxExtents.
*/
SBoxExtents operator*(const double& scale) const {
return SBoxExtents{topLeft * scale, bottomRight * scale};
}
/**
* @brief Rounds the coordinates of the extents.
* @return Rounded SBoxExtents.
*/
SBoxExtents round() {
return {topLeft.round(), bottomRight.round()};
}
/**
* @brief Checks equality between two SBoxExtents objects.
* @param other Another SBoxExtents object to compare.
* @return True if both SBoxExtents are equal, false otherwise.
*/
bool operator==(const SBoxExtents& other) const {
return topLeft == other.topLeft && bottomRight == other.bottomRight;
}
/**
* @brief Adjusts the extents to encompass another SBoxExtents.
* @param other Another SBoxExtents to add to this one.
*/
void addExtents(const SBoxExtents& other) {
topLeft = topLeft.getComponentMax(other.topLeft);
bottomRight = bottomRight.getComponentMax(other.bottomRight);
}
};
/**
* @brief Represents a 2D bounding box.
*/
class CBox {
public:
/**
* @brief Constructs a CBox with specified position and dimensions.
* @param x_ X-coordinate of the top-left corner.
* @param y_ Y-coordinate of the top-left corner.
* @param w_ Width of the box.
* @param h_ Height of the box.
*/
CBox(double x_, double y_, double w_, double h_) {
x = x_;
y = y_;
w = w_;
h = h_;
}
/**
* @brief Default constructor. Initializes an empty box (0 width, 0 height).
*/
CBox() {
w = 0;
h = 0;
}
/**
* @brief Constructs a CBox with uniform dimensions.
* @param d Dimensions to apply uniformly (x, y, width, height).
*/
CBox(const double d) {
x = d;
y = d;
w = d;
h = d;
}
/**
* @brief Constructs a CBox from a position and size vector.
* @param pos Position vector representing the top-left corner.
* @param size Size vector representing width and height.
*/
CBox(const Vector2D& pos, const Vector2D& size) {
x = pos.x;
y = pos.y;
w = size.x;
h = size.y;
}
// Geometric operations
CBox& applyFromWlr();
CBox& scale(double scale);
CBox& scaleFromCenter(double scale);
CBox& scale(const Vector2D& scale);
CBox& translate(const Vector2D& vec);
CBox& round();
CBox& transform(const eTransform t, double w, double h);
CBox& addExtents(const SBoxExtents& e);
CBox& expand(const double& value);
CBox& noNegativeSize();
CBox copy() const;
CBox intersection(const CBox& other) const;
bool overlaps(const CBox& other) const;
bool inside(const CBox& bound) const;
/**
* @brief Computes the extents of the box relative to another box.
* @param small Another CBox to compare against.
* @return SBoxExtents representing the extents of the box relative to 'small'.
*/
SBoxExtents extentsFrom(const CBox&); // this is the big box
/**
* @brief Calculates the middle point of the box.
* @return Vector2D representing the middle point.
*/
Vector2D middle() const;
/**
* @brief Retrieves the position of the top-left corner of the box.
* @return Vector2D representing the position.
*/
Vector2D pos() const;
/**
* @brief Retrieves the size (width and height) of the box.
* @return Vector2D representing the size.
*/
Vector2D size() const;
/**
* @brief Finds the closest point within the box to a given vector.
* @param vec Vector from which to find the closest point.
* @return Vector2D representing the closest point within the box.
*/
Vector2D closestPoint(const Vector2D& vec) const;
/**
* @brief Checks if a given point is inside the box.
* @param vec Vector representing the point to check.
* @return True if the point is inside the box, false otherwise.
*/
bool containsPoint(const Vector2D& vec) const;
/**
* @brief Checks if the box is empty (zero width or height).
* @return True if the box is empty, false otherwise.
*/
bool empty() const;
double x = 0, y = 0; // Position of the top-left corner of the box.
union {
double w;
double width;
};
union {
double h;
double height;
};
double rot = 0; //< Rotation angle of the box in radians (counterclockwise).
/**
* @brief Checks equality between two CBox objects.
* @param rhs Another CBox object to compare.
* @return True if both CBox objects are equal, false otherwise.
*/
bool operator==(const CBox& rhs) const {
return x == rhs.x && y == rhs.y && w == rhs.w && h == rhs.h;
}
private:
CBox roundInternal();
};
}

View File

@ -0,0 +1,16 @@
#pragma once
namespace Hyprutils {
namespace Math {
enum eTransform {
HYPRUTILS_TRANSFORM_NORMAL = 0,
HYPRUTILS_TRANSFORM_90 = 1,
HYPRUTILS_TRANSFORM_180 = 2,
HYPRUTILS_TRANSFORM_270 = 3,
HYPRUTILS_TRANSFORM_FLIPPED = 4,
HYPRUTILS_TRANSFORM_FLIPPED_90 = 5,
HYPRUTILS_TRANSFORM_FLIPPED_180 = 6,
HYPRUTILS_TRANSFORM_FLIPPED_270 = 7,
};
}
}

View File

@ -0,0 +1,70 @@
#pragma once
#include <pixman.h>
#include <vector>
#include "Vector2D.hpp"
#include "Box.hpp"
namespace Hyprutils {
namespace Math {
class CRegion {
public:
/* Create an empty region */
CRegion();
/* Create from a reference. Copies, does not own. */
CRegion(const pixman_region32_t* const ref);
/* Create from a box */
CRegion(double x, double y, double w, double h);
/* Create from a CBox */
CRegion(const CBox& box);
/* Create from a pixman_box32_t */
CRegion(pixman_box32_t* box);
CRegion(const CRegion&);
CRegion(CRegion&&);
~CRegion();
CRegion& operator=(CRegion&& other) {
pixman_region32_copy(&m_rRegion, other.pixman());
return *this;
}
CRegion& operator=(CRegion& other) {
pixman_region32_copy(&m_rRegion, other.pixman());
return *this;
}
CRegion& clear();
CRegion& set(const CRegion& other);
CRegion& add(const CRegion& other);
CRegion& add(double x, double y, double w, double h);
CRegion& add(const CBox& other);
CRegion& subtract(const CRegion& other);
CRegion& intersect(const CRegion& other);
CRegion& intersect(double x, double y, double w, double h);
CRegion& translate(const Vector2D& vec);
CRegion& transform(const eTransform t, double w, double h);
CRegion& invert(pixman_box32_t* box);
CRegion& invert(const CBox& box);
CRegion& scale(float scale);
CRegion& scale(const Vector2D& scale);
CRegion& rationalize();
CBox getExtents();
bool containsPoint(const Vector2D& vec) const;
bool empty() const;
Vector2D closestPoint(const Vector2D& vec) const;
CRegion copy() const;
std::vector<pixman_box32_t> getRects() const;
//
pixman_region32_t* pixman() {
return &m_rRegion;
}
private:
pixman_region32_t m_rRegion;
};
}
}

View File

@ -0,0 +1,161 @@
#pragma once
#include <format>
#include <string>
namespace Hyprutils {
namespace Math {
class Vector2D {
public:
Vector2D(double, double);
Vector2D(int, int);
Vector2D();
~Vector2D();
double x = 0;
double y = 0;
// returns the scale
double normalize();
Vector2D operator+(const Vector2D& a) const {
return Vector2D(this->x + a.x, this->y + a.y);
}
Vector2D operator-(const Vector2D& a) const {
return Vector2D(this->x - a.x, this->y - a.y);
}
Vector2D operator-() const {
return Vector2D(-this->x, -this->y);
}
Vector2D operator*(const double& a) const {
return Vector2D(this->x * a, this->y * a);
}
Vector2D operator/(const double& a) const {
return Vector2D(this->x / a, this->y / a);
}
bool operator==(const Vector2D& a) const {
return a.x == x && a.y == y;
}
bool operator!=(const Vector2D& a) const {
return a.x != x || a.y != y;
}
Vector2D operator*(const Vector2D& a) const {
return Vector2D(this->x * a.x, this->y * a.y);
}
Vector2D operator/(const Vector2D& a) const {
return Vector2D(this->x / a.x, this->y / a.y);
}
bool operator>(const Vector2D& a) const {
return this->x > a.x && this->y > a.y;
}
bool operator<(const Vector2D& a) const {
return this->x < a.x && this->y < a.y;
}
Vector2D& operator+=(const Vector2D& a) {
this->x += a.x;
this->y += a.y;
return *this;
}
Vector2D& operator-=(const Vector2D& a) {
this->x -= a.x;
this->y -= a.y;
return *this;
}
Vector2D& operator*=(const Vector2D& a) {
this->x *= a.x;
this->y *= a.y;
return *this;
}
Vector2D& operator/=(const Vector2D& a) {
this->x /= a.x;
this->y /= a.y;
return *this;
}
Vector2D& operator*=(const double& a) {
this->x *= a;
this->y *= a;
return *this;
}
Vector2D& operator/=(const double& a) {
this->x /= a;
this->y /= a;
return *this;
}
double distance(const Vector2D& other) const;
double distanceSq(const Vector2D& other) const;
double size() const;
Vector2D clamp(const Vector2D& min, const Vector2D& max = Vector2D{-1, -1}) const;
Vector2D floor() const;
Vector2D round() const;
Vector2D getComponentMax(const Vector2D& other) const;
};
}
}
// absolutely ridiculous formatter spec parsing
#define AQ_FORMAT_PARSE(specs__, type__) \
template <typename FormatContext> \
constexpr auto parse(FormatContext& ctx) { \
auto it = ctx.begin(); \
for (; it != ctx.end() && *it != '}'; it++) { \
switch (*it) { specs__ default : throw std::format_error("invalid format specification"); } \
} \
return it; \
}
#define AQ_FORMAT_FLAG(spec__, flag__) \
case spec__: (flag__) = true; break;
#define AQ_FORMAT_NUMBER(buf__) \
case '0': \
case '1': \
case '2': \
case '3': \
case '4': \
case '5': \
case '6': \
case '7': \
case '8': \
case '9': (buf__).push_back(*it); break;
/**
format specification
- 'j', as json array
- 'X', same as std::format("{}x{}", vec.x, vec.y)
- number, floating point precision, use `0` to format as integer
*/
template <typename CharT>
struct std::formatter<Hyprutils::Math::Vector2D, CharT> : std::formatter<CharT> {
bool formatJson = false;
bool formatX = false;
std::string precision = "";
AQ_FORMAT_PARSE(AQ_FORMAT_FLAG('j', formatJson) //
AQ_FORMAT_FLAG('X', formatX) //
AQ_FORMAT_NUMBER(precision),
Hyprutils::Math::Vector2D)
template <typename FormatContext>
auto format(const Hyprutils::Math::Vector2D& vec, FormatContext& ctx) const {
std::string formatString = precision.empty() ? "{}" : std::format("{{:.{}f}}", precision);
if (formatJson)
formatString = std::format("[{0}, {0}]", formatString);
else if (formatX)
formatString = std::format("{0}x{0}", formatString);
else
formatString = std::format("[Vector2D: x: {0}, y: {0}]", formatString);
try {
string buf = std::vformat(formatString, std::make_format_args(vec.x, vec.y));
return std::format_to(ctx.out(), "{}", buf);
} catch (std::format_error& e) { return std::format_to(ctx.out(), "[{}, {}]", vec.x, vec.y); }
}
};

View File

@ -2,6 +2,8 @@
lib,
stdenv,
cmake,
pkg-config,
pixman,
version ? "git",
doCheck ? false,
}:
@ -10,7 +12,14 @@ stdenv.mkDerivation {
inherit version doCheck;
src = ../.;
nativeBuildInputs = [cmake];
nativeBuildInputs = [
cmake
pkg-config
];
buildInputs = [
pixman
];
outputs = ["out" "dev"];

225
src/math/Box.cpp Normal file
View File

@ -0,0 +1,225 @@
#include <hyprutils/math/Box.hpp>
#include <limits>
#include <algorithm>
#include <cmath>
#define VECINRECT(vec, x1, y1, x2, y2) ((vec).x >= (x1) && (vec).x < (x2) && (vec).y >= (y1) && (vec).y < (y2))
using namespace Hyprutils::Math;
constexpr double HALF = 0.5;
constexpr double DOUBLE = 2.0;
constexpr double EPSILON = 1e-9;
CBox& Hyprutils::Math::CBox::scale(double scale) {
x *= scale;
y *= scale;
w *= scale;
h *= scale;
return *this;
}
CBox& Hyprutils::Math::CBox::scale(const Vector2D& scale) {
x *= scale.x;
y *= scale.y;
w *= scale.x;
h *= scale.y;
return *this;
}
CBox& Hyprutils::Math::CBox::translate(const Vector2D& vec) {
x += vec.x;
y += vec.y;
return *this;
}
Vector2D Hyprutils::Math::CBox::middle() const {
return Vector2D{x + w * HALF, y + h * HALF};
}
bool Hyprutils::Math::CBox::containsPoint(const Vector2D& vec) const {
return VECINRECT(vec, x, y, x + w, y + h);
}
bool Hyprutils::Math::CBox::empty() const {
return std::fabs(w) < EPSILON || std::fabs(h) < EPSILON;
}
CBox& Hyprutils::Math::CBox::round() {
double roundedX = std::round(x);
double roundedY = std::round(y);
double newW = x + w - roundedX;
double newH = y + h - roundedY;
x = roundedX;
y = roundedY;
w = std::round(newW);
h = std::round(newH);
return *this;
}
CBox& Hyprutils::Math::CBox::transform(const eTransform t, double w, double h) {
CBox temp = *this;
if (t % 2 == 0) {
width = temp.width;
height = temp.height;
} else {
width = temp.height;
height = temp.width;
}
switch (t) {
case HYPRUTILS_TRANSFORM_NORMAL:
x = temp.x;
y = temp.y;
break;
case HYPRUTILS_TRANSFORM_90:
x = h - temp.y - temp.height;
y = temp.x;
break;
case HYPRUTILS_TRANSFORM_180:
x = w - temp.x - temp.width;
y = h - temp.y - temp.height;
break;
case HYPRUTILS_TRANSFORM_270:
x = temp.y;
y = w - temp.x - temp.width;
break;
case HYPRUTILS_TRANSFORM_FLIPPED:
x = w - temp.x - temp.width;
y = temp.y;
break;
case HYPRUTILS_TRANSFORM_FLIPPED_90:
x = temp.y;
y = temp.x;
break;
case HYPRUTILS_TRANSFORM_FLIPPED_180:
x = temp.x;
y = h - temp.y - temp.height;
break;
case HYPRUTILS_TRANSFORM_FLIPPED_270:
x = h - temp.y - temp.height;
y = w - temp.x - temp.width;
break;
}
return *this;
}
CBox& Hyprutils::Math::CBox::addExtents(const SBoxExtents& e) {
x -= e.topLeft.x;
y -= e.topLeft.y;
w += e.topLeft.x + e.bottomRight.x;
h += e.topLeft.y + e.bottomRight.y;
return *this;
}
CBox& Hyprutils::Math::CBox::scaleFromCenter(double scale) {
double oldW = w, oldH = h;
w *= scale;
h *= scale;
x -= (w - oldW) * HALF;
y -= (h - oldH) * HALF;
return *this;
}
CBox& Hyprutils::Math::CBox::expand(const double& value) {
x -= value;
y -= value;
w += value * DOUBLE;
h += value * DOUBLE;
if (w <= EPSILON || h <= EPSILON) {
w = 0;
h = 0;
}
return *this;
}
CBox& Hyprutils::Math::CBox::noNegativeSize() {
w = std::clamp(w, 0.0, std::numeric_limits<double>::infinity());
h = std::clamp(h, 0.0, std::numeric_limits<double>::infinity());
return *this;
}
CBox Hyprutils::Math::CBox::intersection(const CBox& other) const {
const double newX = std::max(x, other.x);
const double newY = std::max(y, other.y);
const double newBottom = std::min(y + h, other.y + other.h);
const double newRight = std::min(x + w, other.x + other.w);
double newW = newRight - newX;
double newH = newBottom - newY;
if (newW <= EPSILON || newH <= EPSILON) {
newW = 0;
newH = 0;
}
return {newX, newY, newW, newH};
}
bool Hyprutils::Math::CBox::overlaps(const CBox& other) const {
return (other.x + other.w >= x) && (x + w >= other.x) && (other.y + other.h >= y) && (y + h >= other.y);
}
bool Hyprutils::Math::CBox::inside(const CBox& bound) const {
return bound.x < x && bound.y < y && x + w < bound.x + bound.w && y + h < bound.y + bound.h;
}
CBox Hyprutils::Math::CBox::roundInternal() {
double flooredX = std::floor(x);
double flooredY = std::floor(y);
double newW = x + w - flooredX;
double newH = y + h - flooredY;
return CBox{flooredX, flooredY, std::floor(newW), std::floor(newH)};
}
CBox Hyprutils::Math::CBox::copy() const {
return CBox{*this};
}
Vector2D Hyprutils::Math::CBox::pos() const {
return {x, y};
}
Vector2D Hyprutils::Math::CBox::size() const {
return {w, h};
}
Vector2D Hyprutils::Math::CBox::closestPoint(const Vector2D& vec) const {
if (containsPoint(vec))
return vec;
Vector2D nv = vec;
nv.x = std::clamp(nv.x, x, x + w);
nv.y = std::clamp(nv.y, y, y + h);
if (std::fabs(nv.x - x) < EPSILON)
nv.x = x;
else if (std::fabs(nv.x - (x + w)) < EPSILON)
nv.x = x + w;
if (std::fabs(nv.y - y) < EPSILON)
nv.y = y;
else if (std::fabs(nv.y - (y + h)) < EPSILON)
nv.y = y + h;
return nv;
}
SBoxExtents Hyprutils::Math::CBox::extentsFrom(const CBox& small) {
return {{small.x - x, small.y - y}, {w - small.w - (small.x - x), h - small.h - (small.y - y)}};
}

204
src/math/Region.cpp Normal file
View File

@ -0,0 +1,204 @@
#include <hyprutils/math/Region.hpp>
#include <cmath>
using namespace Hyprutils::Math;
constexpr const int64_t MAX_REGION_SIDE = 10000000;
Hyprutils::Math::CRegion::CRegion() {
pixman_region32_init(&m_rRegion);
}
Hyprutils::Math::CRegion::CRegion(const pixman_region32_t* const ref) {
pixman_region32_init(&m_rRegion);
pixman_region32_copy(&m_rRegion, ref);
}
Hyprutils::Math::CRegion::CRegion(double x, double y, double w, double h) {
pixman_region32_init_rect(&m_rRegion, x, y, w, h);
}
Hyprutils::Math::CRegion::CRegion(const CBox& box) {
pixman_region32_init_rect(&m_rRegion, box.x, box.y, box.w, box.h);
}
Hyprutils::Math::CRegion::CRegion(pixman_box32_t* box) {
pixman_region32_init_rect(&m_rRegion, box->x1, box->y1, box->x2 - box->x1, box->y2 - box->y1);
}
Hyprutils::Math::CRegion::CRegion(const CRegion& other) {
pixman_region32_init(&m_rRegion);
pixman_region32_copy(&m_rRegion, const_cast<CRegion*>(&other)->pixman());
}
Hyprutils::Math::CRegion::CRegion(CRegion&& other) {
pixman_region32_init(&m_rRegion);
pixman_region32_copy(&m_rRegion, other.pixman());
}
Hyprutils::Math::CRegion::~CRegion() {
pixman_region32_fini(&m_rRegion);
}
CRegion& Hyprutils::Math::CRegion::clear() {
pixman_region32_clear(&m_rRegion);
return *this;
}
CRegion& Hyprutils::Math::CRegion::set(const CRegion& other) {
pixman_region32_copy(&m_rRegion, const_cast<CRegion*>(&other)->pixman());
return *this;
}
CRegion& Hyprutils::Math::CRegion::add(const CRegion& other) {
pixman_region32_union(&m_rRegion, &m_rRegion, const_cast<CRegion*>(&other)->pixman());
return *this;
}
CRegion& Hyprutils::Math::CRegion::add(double x, double y, double w, double h) {
pixman_region32_union_rect(&m_rRegion, &m_rRegion, x, y, w, h);
return *this;
}
CRegion& Hyprutils::Math::CRegion::add(const CBox& other) {
pixman_region32_union_rect(&m_rRegion, &m_rRegion, other.x, other.y, other.w, other.h);
return *this;
}
CRegion& Hyprutils::Math::CRegion::subtract(const CRegion& other) {
pixman_region32_subtract(&m_rRegion, &m_rRegion, const_cast<CRegion*>(&other)->pixman());
return *this;
}
CRegion& Hyprutils::Math::CRegion::intersect(const CRegion& other) {
pixman_region32_intersect(&m_rRegion, &m_rRegion, const_cast<CRegion*>(&other)->pixman());
return *this;
}
CRegion& Hyprutils::Math::CRegion::intersect(double x, double y, double w, double h) {
pixman_region32_intersect_rect(&m_rRegion, &m_rRegion, x, y, w, h);
return *this;
}
CRegion& Hyprutils::Math::CRegion::invert(pixman_box32_t* box) {
pixman_region32_inverse(&m_rRegion, &m_rRegion, box);
return *this;
}
CRegion& Hyprutils::Math::CRegion::invert(const CBox& box) {
pixman_box32 pixmanBox = {(int32_t)box.x, (int32_t)box.y, (int32_t)box.w + (int32_t)box.x, (int32_t)box.h + (int32_t)box.y};
return this->invert(&pixmanBox);
}
CRegion& Hyprutils::Math::CRegion::translate(const Vector2D& vec) {
pixman_region32_translate(&m_rRegion, vec.x, vec.y);
return *this;
}
CRegion& Hyprutils::Math::CRegion::transform(const eTransform t, double w, double h) {
if (t == HYPRUTILS_TRANSFORM_NORMAL)
return *this;
auto rects = getRects();
clear();
for (auto& r : rects) {
CBox xfmd{(double)r.x1, (double)r.y1, (double)r.x2 - r.x1, (double)r.y2 - r.y1};
xfmd.transform(t, w, h);
add(xfmd);
}
return *this;
}
CRegion& Hyprutils::Math::CRegion::rationalize() {
intersect(CBox{-MAX_REGION_SIDE, -MAX_REGION_SIDE, MAX_REGION_SIDE * 2, MAX_REGION_SIDE * 2});
return *this;
}
CRegion Hyprutils::Math::CRegion::copy() const {
return CRegion(*this);
}
CRegion& Hyprutils::Math::CRegion::scale(float scale_) {
scale({scale_, scale_});
return *this;
}
CRegion& Hyprutils::Math::CRegion::scale(const Vector2D& scale) {
if (scale == Vector2D{1, 1})
return *this;
auto rects = getRects();
clear();
for (auto& r : rects) {
r.x1 = std::floor(r.x1 * scale.x);
r.y1 = std::floor(r.y1 * scale.x);
r.x2 = std::ceil(r.x2 * scale.x);
r.y2 = std::ceil(r.y2 * scale.x);
add(&r);
}
return *this;
}
std::vector<pixman_box32_t> Hyprutils::Math::CRegion::getRects() const {
std::vector<pixman_box32_t> result;
int rectsNum = 0;
const auto RECTSARR = pixman_region32_rectangles(&m_rRegion, &rectsNum);
result.assign(RECTSARR, RECTSARR + rectsNum);
return result;
}
CBox Hyprutils::Math::CRegion::getExtents() {
pixman_box32_t* box = pixman_region32_extents(&m_rRegion);
return {(double)box->x1, (double)box->y1, (double)box->x2 - box->x1, (double)box->y2 - box->y1};
}
bool Hyprutils::Math::CRegion::containsPoint(const Vector2D& vec) const {
return pixman_region32_contains_point(&m_rRegion, vec.x, vec.y, nullptr);
}
bool Hyprutils::Math::CRegion::empty() const {
return !pixman_region32_not_empty(&m_rRegion);
}
Vector2D Hyprutils::Math::CRegion::closestPoint(const Vector2D& vec) const {
if (containsPoint(vec))
return vec;
double bestDist = __FLT_MAX__;
Vector2D leader = vec;
for (auto& box : getRects()) {
double x = 0, y = 0;
if (vec.x >= box.x2)
x = box.x2 - 1;
else if (vec.x < box.x1)
x = box.x1;
else
x = vec.x;
if (vec.y >= box.y2)
y = box.y2 - 1;
else if (vec.y < box.y1)
y = box.y1;
else
y = vec.y;
double distance = pow(x, 2) + pow(y, 2);
if (distance < bestDist) {
bestDist = distance;
leader = {x, y};
}
}
return leader;
}

60
src/math/Vector2D.cpp Normal file
View File

@ -0,0 +1,60 @@
#include <hyprutils/math/Vector2D.hpp>
#include <algorithm>
#include <cmath>
using namespace Hyprutils::Math;
Hyprutils::Math::Vector2D::Vector2D(double xx, double yy) {
x = xx;
y = yy;
}
Hyprutils::Math::Vector2D::Vector2D(int xx, int yy) {
x = (double)xx;
y = (double)yy;
}
Hyprutils::Math::Vector2D::Vector2D() {
x = 0;
y = 0;
}
Hyprutils::Math::Vector2D::~Vector2D() {}
double Hyprutils::Math::Vector2D::normalize() {
// get max abs
const auto max = std::abs(x) > std::abs(y) ? std::abs(x) : std::abs(y);
x /= max;
y /= max;
return max;
}
Vector2D Hyprutils::Math::Vector2D::floor() const {
return Vector2D(std::floor(x), std::floor(y));
}
Vector2D Hyprutils::Math::Vector2D::round() const {
return Vector2D(std::round(x), std::round(y));
}
Vector2D Hyprutils::Math::Vector2D::clamp(const Vector2D& min, const Vector2D& max) const {
return Vector2D(std::clamp(this->x, min.x, max.x < min.x ? INFINITY : max.x), std::clamp(this->y, min.y, max.y < min.y ? INFINITY : max.y));
}
double Hyprutils::Math::Vector2D::distance(const Vector2D& other) const {
return std::sqrt(distanceSq(other));
}
double Hyprutils::Math::Vector2D::distanceSq(const Vector2D& other) const {
return (x - other.x) * (x - other.x) + (y - other.y) * (y - other.y);
}
double Hyprutils::Math::Vector2D::size() const {
return std::sqrt(x * x + y * y);
}
Vector2D Hyprutils::Math::Vector2D::getComponentMax(const Vector2D& other) const {
return Vector2D(std::max(this->x, other.x), std::max(this->y, other.y));
}

89
tests/math.cpp Normal file
View File

@ -0,0 +1,89 @@
#include <hyprutils/math/Region.hpp>
#include "shared.hpp"
using namespace Hyprutils::Math;
int main(int argc, char** argv, char** envp) {
CRegion rg = {0, 0, 100, 100};
rg.add(CBox{{}, {20, 200}});
int ret = 0;
EXPECT(rg.getExtents().height, 200);
EXPECT(rg.getExtents().width, 100);
rg.intersect(CBox{10, 10, 300, 300});
EXPECT(rg.getExtents().width, 90);
EXPECT(rg.getExtents().height, 190);
/*Box.cpp test cases*/
// Test default constructor and accessors
{
CBox box1;
EXPECT(box1.x, 0);
EXPECT(box1.y, 0);
EXPECT(box1.width, 0);
EXPECT(box1.height, 0);
// Test parameterized constructor and accessors
CBox box2(10, 20, 30, 40);
EXPECT(box2.x, 10);
EXPECT(box2.y, 20);
EXPECT(box2.width, 30);
EXPECT(box2.height, 40);
// Test setters and getters
box2.translate(Vector2D(5, -5));
EXPECT_VECTOR2D(box2.pos(), Vector2D(15, 15));
}
//Test Scaling and Transformation
{
CBox box(10, 10, 20, 30);
// Test scaling
box.scale(2.0);
EXPECT_VECTOR2D(box.size(), Vector2D(40, 60));
EXPECT_VECTOR2D(box.pos(), Vector2D(20, 20));
// Test scaling from center
box.scaleFromCenter(0.5);
EXPECT_VECTOR2D(box.size(), Vector2D(20, 30));
EXPECT_VECTOR2D(box.pos(), Vector2D(30, 35));
// Test transformation
box.transform(HYPRUTILS_TRANSFORM_90, 100, 200);
EXPECT_VECTOR2D(box.pos(), Vector2D(135, 30));
EXPECT_VECTOR2D(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_VECTOR2D(intersection.pos(), Vector2D(50, 50));
EXPECT_VECTOR2D(intersection.size(), Vector2D(50, 50));
SBoxExtents extents = box1.extentsFrom(box2);
EXPECT_VECTOR2D(extents.topLeft, Vector2D(50, 50));
EXPECT_VECTOR2D(extents.bottomRight, Vector2D(-50, -50));
}
// Test Boundary Conditions and Special Cases
{
CBox box(0, 0, 50, 50);
EXPECT(box.empty(), false);
EXPECT(box.containsPoint(Vector2D(25, 25)), true);
EXPECT(box.containsPoint(Vector2D(60, 60)), false);
EXPECT(box.overlaps(CBox(25, 25, 50, 50)), true);
EXPECT(box.inside(CBox(0, 0, 100, 100)), false);
}
return ret;
}

View File

@ -18,3 +18,15 @@ namespace Colors {
} else { \
std::cout << Colors::GREEN << "Passed " << Colors::RESET << #expr << ". Got " << val << "\n"; \
}
#define EXPECT_VECTOR2D(expr, val) \
do { \
const auto& RESULT = expr; \
const auto& EXPECTED = val; \
if (!(std::abs(RESULT.x - EXPECTED.x) < 1e-6 && std::abs(RESULT.y - EXPECTED.y) < 1e-6)) { \
std::cout << Colors::RED << "Failed: " << Colors::RESET << #expr << ", expected (" << EXPECTED.x << ", " << EXPECTED.y << ") but got (" << RESULT.x << ", " \
<< RESULT.y << ")\n"; \
ret = 1; \
} else { \
std::cout << Colors::GREEN << "Passed " << Colors::RESET << #expr << ". Got (" << RESULT.x << ", " << RESULT.y << ")\n"; \
} \
} while (0)