/* * SPDX-License-Identifier: LGPL-2.0-or-later */ #include "radialmodel.h" #include namespace katecustom { RadialModel::RadialModel(RadialNode root) : m_root(std::move(root)) { } const RadialNode &RadialModel::nodeAtPath() const { const RadialNode *node = &m_root; for (int idx : m_path) { node = &node->children.at(idx); } return *node; } const QList &RadialModel::currentSlices() const { return nodeAtPath().children; } void RadialModel::sliceAngles(int index, int sliceCount, double &startAngle, double &endAngle) const { // Even wedges starting at the top (0 = up), going clockwise. Each slice is // centred on its nominal direction, so wedge i spans [i-0.5, i+0.5) steps. const double step = (2.0 * M_PI) / static_cast(sliceCount); startAngle = (static_cast(index) - 0.5) * step; endAngle = (static_cast(index) + 0.5) * step; } int RadialModel::sliceAt(const QPointF &point, const RadialLayout &layout) const { const int count = currentSlices().size(); if (count == 0 || layout.sliceCount == 0) { return InvalidSlice; } const double dx = point.x() - layout.center.x(); const double dy = point.y() - layout.center.y(); const double dist = std::sqrt(dx * dx + dy * dy); // Inner dead zone: ambiguous direction, treat as "no slice". if (dist < layout.innerRadius) { return InvalidSlice; } // Angle measured from "up" (screen: -y), increasing clockwise (toward +x). double angle = std::atan2(dx, -dy); // 0 = up, +pi/2 = right if (angle < 0.0) { angle += 2.0 * M_PI; } const double step = (2.0 * M_PI) / static_cast(count); // Shift by half a step so slice 0 is centred on "up". int index = static_cast(std::floor((angle + step / 2.0) / step)); index %= count; return index; } QPointF RadialModel::sliceCenter(int index, const RadialLayout &layout) const { const int count = currentSlices().size(); if (count == 0) { return layout.center; } const double step = (2.0 * M_PI) / static_cast(count); const double angle = static_cast(index) * step; // 0 = up, CW const double r = (layout.innerRadius + layout.outerRadius) / 2.0; // Convert "up, clockwise" angle back to screen coordinates. const double x = layout.center.x() + r * std::sin(angle); const double y = layout.center.y() - r * std::cos(angle); return QPointF(x, y); } bool RadialModel::descend(int index) { const QList &slices = currentSlices(); if (index < 0 || index >= slices.size()) { return false; } if (!slices.at(index).isBranch()) { return false; } m_path.push_back(index); return true; } bool RadialModel::ascend() { if (m_path.isEmpty()) { return false; } m_path.removeLast(); return true; } RadialModel::Hit RadialModel::resolve(int index, QString &outActionId) const { const QList &slices = currentSlices(); if (index < 0 || index >= slices.size()) { return atRoot() ? Hit::Cancel : Hit::Ascend; } const RadialNode &node = slices.at(index); if (node.isBranch()) { return Hit::Descend; } outActionId = node.actionId; return Hit::Activate; } } // namespace katecustom