222 lines
7.2 KiB
C++
222 lines
7.2 KiB
C++
#include <algorithm>
|
|
#include <expected>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <vector>
|
|
|
|
#include <hyprland/src/helpers/Color.hpp>
|
|
#include <hyprland/src/layout/algorithm/TiledAlgorithm.hpp>
|
|
#include <hyprland/src/layout/algorithm/Algorithm.hpp>
|
|
#include <hyprland/src/layout/space/Space.hpp>
|
|
#include <hyprland/src/layout/target/Target.hpp>
|
|
#include <hyprland/src/plugins/PluginAPI.hpp>
|
|
|
|
static HANDLE g_pluginHandle = nullptr;
|
|
|
|
namespace Layout::Tiled {
|
|
class CColumnsAlgorithm final : public ITiledAlgorithm {
|
|
public:
|
|
void newTarget(SP<ITarget> target) override {
|
|
addTarget(std::move(target));
|
|
recalculate();
|
|
}
|
|
|
|
void movedTarget(SP<ITarget> target, std::optional<Vector2D> /*focalPoint*/ = std::nullopt) override {
|
|
addTarget(std::move(target));
|
|
recalculate();
|
|
}
|
|
|
|
void removeTarget(SP<ITarget> target) override {
|
|
cleanup();
|
|
std::erase_if(m_targets, [&](const WP<ITarget>& weak) {
|
|
const auto locked = weak.lock();
|
|
return !locked || locked == target;
|
|
});
|
|
recalculate();
|
|
}
|
|
|
|
void resizeTarget(const Vector2D& /*delta*/, SP<ITarget> /*target*/, eRectCorner /*corner*/ = CORNER_NONE) override {
|
|
// Intentionally ignored: this layout always keeps equal-width columns.
|
|
}
|
|
|
|
void recalculate(eRecalculateReason /*reason*/ = RECALCULATE_REASON_UNKNOWN) override {
|
|
cleanup();
|
|
|
|
const auto parent = m_parent.lock();
|
|
if (!parent)
|
|
return;
|
|
|
|
const auto space = parent->space();
|
|
if (!space)
|
|
return;
|
|
|
|
std::vector<SP<ITarget>> tiledTargets;
|
|
tiledTargets.reserve(m_targets.size());
|
|
|
|
for (auto const& weak : m_targets) {
|
|
const auto target = weak.lock();
|
|
if (target)
|
|
tiledTargets.push_back(target);
|
|
}
|
|
|
|
if (tiledTargets.empty())
|
|
return;
|
|
|
|
const auto area = space->workArea(false);
|
|
const auto count = tiledTargets.size();
|
|
|
|
for (size_t i = 0; i < count; ++i) {
|
|
const double left = area.x + (area.w * static_cast<double>(i) / static_cast<double>(count));
|
|
const double right = area.x + (area.w * static_cast<double>(i + 1) / static_cast<double>(count));
|
|
const CBox box(left, area.y, right - left, area.h);
|
|
|
|
tiledTargets[i]->setPositionGlobal(box);
|
|
tiledTargets[i]->recalc();
|
|
}
|
|
}
|
|
|
|
SP<ITarget> getNextCandidate(SP<ITarget> old) override {
|
|
cleanup();
|
|
auto targets = liveTargets();
|
|
|
|
if (targets.empty())
|
|
return nullptr;
|
|
|
|
if (!old)
|
|
return targets.front();
|
|
|
|
const auto it = std::ranges::find(targets, old);
|
|
if (it == targets.end())
|
|
return targets.front();
|
|
|
|
const auto idx = static_cast<size_t>(std::distance(targets.begin(), it));
|
|
return targets[(idx + 1) % targets.size()];
|
|
}
|
|
|
|
Config::ErrorResult layoutMsg(const std::string_view& sv) override {
|
|
if (sv == "togglesplit" || sv == "swapsplit")
|
|
return {};
|
|
|
|
if (sv == "recalculate") {
|
|
recalculate();
|
|
return {};
|
|
}
|
|
|
|
return Config::configError(std::string{"columns: unsupported layout message: "} + std::string{sv},
|
|
Config::eConfigErrorLevel::ERROR, Config::eConfigErrorCode::INVALID_ARGUMENT);
|
|
}
|
|
|
|
std::optional<Vector2D> predictSizeForNewTarget() override {
|
|
const auto parent = m_parent.lock();
|
|
if (!parent)
|
|
return std::nullopt;
|
|
|
|
const auto space = parent->space();
|
|
if (!space)
|
|
return std::nullopt;
|
|
|
|
const auto area = space->workArea(false);
|
|
const auto n = std::max<size_t>(1, liveTargets().size() + 1);
|
|
return Vector2D(area.w / static_cast<double>(n), area.h);
|
|
}
|
|
|
|
void swapTargets(SP<ITarget> a, SP<ITarget> b) override {
|
|
cleanup();
|
|
|
|
auto ia = indexOf(a);
|
|
auto ib = indexOf(b);
|
|
if (!ia.has_value() || !ib.has_value() || ia == ib)
|
|
return;
|
|
|
|
std::swap(m_targets[*ia], m_targets[*ib]);
|
|
recalculate();
|
|
}
|
|
|
|
void moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool /*silent*/) override {
|
|
cleanup();
|
|
|
|
const auto idx = indexOf(t);
|
|
if (!idx.has_value())
|
|
return;
|
|
|
|
if (dir == Math::DIRECTION_LEFT && *idx > 0) {
|
|
std::swap(m_targets[*idx], m_targets[*idx - 1]);
|
|
recalculate();
|
|
return;
|
|
}
|
|
|
|
if (dir == Math::DIRECTION_RIGHT && *idx + 1 < m_targets.size()) {
|
|
std::swap(m_targets[*idx], m_targets[*idx + 1]);
|
|
recalculate();
|
|
return;
|
|
}
|
|
}
|
|
|
|
private:
|
|
std::vector<WP<ITarget>> m_targets;
|
|
|
|
void addTarget(SP<ITarget> target) {
|
|
cleanup();
|
|
|
|
const auto exists = std::ranges::any_of(m_targets, [&](const WP<ITarget>& weak) {
|
|
const auto locked = weak.lock();
|
|
return locked && locked == target;
|
|
});
|
|
|
|
if (!exists)
|
|
m_targets.emplace_back(target);
|
|
}
|
|
|
|
void cleanup() {
|
|
std::erase_if(m_targets, [](const WP<ITarget>& weak) { return weak.expired(); });
|
|
}
|
|
|
|
std::vector<SP<ITarget>> liveTargets() const {
|
|
std::vector<SP<ITarget>> out;
|
|
out.reserve(m_targets.size());
|
|
|
|
for (auto const& weak : m_targets) {
|
|
const auto locked = weak.lock();
|
|
if (locked)
|
|
out.push_back(locked);
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
std::optional<size_t> indexOf(SP<ITarget> target) const {
|
|
for (size_t i = 0; i < m_targets.size(); ++i) {
|
|
const auto locked = m_targets[i].lock();
|
|
if (locked && locked == target)
|
|
return i;
|
|
}
|
|
|
|
return std::nullopt;
|
|
}
|
|
};
|
|
}
|
|
|
|
APICALL EXPORT std::string PLUGIN_API_VERSION() {
|
|
return HYPRLAND_API_VERSION;
|
|
}
|
|
|
|
APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
|
|
g_pluginHandle = handle;
|
|
|
|
const bool ok = HyprlandAPI::addTiledAlgo(handle, "columns", &typeid(Layout::Tiled::CColumnsAlgorithm), []() -> UP<Layout::ITiledAlgorithm> {
|
|
return makeUnique<Layout::Tiled::CColumnsAlgorithm>();
|
|
});
|
|
|
|
if (!ok)
|
|
HyprlandAPI::addNotification(handle, "hyprcolumns: failed to register columns layout", CHyprColor{1.0, 0.2, 0.2, 1.0}, 5000);
|
|
else
|
|
HyprlandAPI::addNotification(handle, "hyprcolumns: registered columns layout", CHyprColor{0.2, 1.0, 0.6, 1.0}, 3000);
|
|
|
|
return {.name = "hyprcolumns", .description = "Equal-width tiled columns layout", .author = "OpenAI", .version = "0.1.0"};
|
|
}
|
|
|
|
APICALL EXPORT void PLUGIN_EXIT() {
|
|
if (g_pluginHandle)
|
|
HyprlandAPI::removeAlgo(g_pluginHandle, "columns");
|
|
}
|