120 lines
3.3 KiB
C++
120 lines
3.3 KiB
C++
/*
|
|
* SPDX-License-Identifier: LGPL-2.0-or-later
|
|
*/
|
|
#include "radialmodel.h"
|
|
|
|
#include <QtMath>
|
|
|
|
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<RadialNode> &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<double>(sliceCount);
|
|
startAngle = (static_cast<double>(index) - 0.5) * step;
|
|
endAngle = (static_cast<double>(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<double>(count);
|
|
// Shift by half a step so slice 0 is centred on "up".
|
|
int index = static_cast<int>(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<double>(count);
|
|
const double angle = static_cast<double>(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<RadialNode> &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<RadialNode> &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
|