14 constexpr
float FlashPeriod = 0.7f;
18 const std::string n =
"#" + std::to_string(i);
20 const unsigned int sc = t.size() > n.size() ? (t.size() - n.size()) / 2 : 0;
21 for (
unsigned int i = 0; i < sc; ++i) { ss <<
" "; }
28 using namespace bl::gui;
34 ConversationTree::ConversationTree(
const ClickCb& ccb)
41 , highlightSelected(true) {
43 "Click and drag to move. Scroll to zoom. Click node to edit. Right click to center view.");
44 getSignal(Event::Dragged)
45 .willAlwaysCall(std::bind(&ConversationTree::onDrag,
this, std::placeholders::_1));
46 getSignal(Event::LeftClicked)
47 .willAlwaysCall(std::bind(&ConversationTree::onClick,
this, std::placeholders::_1));
48 getSignal(Event::RightClicked).willAlwaysCall(std::bind(&ConversationTree::centerView,
this));
51 bool ConversationTree::handleScroll(
const bl::gui::Event& e) {
52 if (getAcquisition().contains(e.mousePosition())) {
53 camZoom /= 1.f + e.scrollDelta() * -0.1f;
54 if (getComponent()) { getComponent()->onElementUpdated(); }
60 void ConversationTree::onClick(
const bl::gui::Event& e) {
61 const sf::Vector2f mpos = transformToTreeCoord(e.mousePosition());
62 for (
unsigned int i = 0; i < renderNodes.size(); ++i) {
63 const Node& n = renderNodes[i];
64 const float dx = n.center.x - mpos.x;
65 const float dy = n.center.y - mpos.y;
66 const float dsqrd = dx * dx + dy * dy;
77 camCenter = renderNodes[i].center;
79 highlightSelected =
true;
80 if (getComponent()) { getComponent()->onElementUpdated(); }
83 void ConversationTree::onDrag(
const bl::gui::Event& e) {
84 const sf::Vector2f dragStart = transformToTreeCoord(e.dragStart());
85 const sf::Vector2f mpos = transformToTreeCoord(e.mousePosition());
86 const sf::Vector2f diff(dragStart - mpos);
87 camCenter += glm::vec2(diff.x, diff.y);
88 if (getComponent()) { getComponent()->onElementUpdated(); }
91 sf::FloatRect ConversationTree::getVirtualBounds()
const {
93 for (
const auto& node : renderNodes) {
94 bounds.left = std::min(bounds.left, node.center.x -
NodeRadius);
95 bounds.top = std::min(bounds.top, node.center.y -
NodeRadius);
96 bounds.width = std::max(bounds.width, node.center.x +
NodeRadius);
97 bounds.height = std::max(bounds.height, node.center.y +
NodeRadius);
102 void ConversationTree::centerView() {
103 const sf::FloatRect bounds = getVirtualBounds();
104 camCenter = glm::vec2((bounds.left + bounds.width) * 0.5f, (bounds.top + bounds.height) * 0.5f);
105 if (getComponent()) { getComponent()->onElementUpdated(); }
108 sf::Vector2f ConversationTree::transformToTreeCoord(
const sf::Vector2f& p)
const {
109 sf::Transform transform;
110 transform.translate(camCenter.x, camCenter.y);
111 transform.scale(1.f / camZoom, 1.f / camZoom);
112 transform.translate(-sf::Vector2f(getAcquisition().width, getAcquisition().height) * 0.5f);
113 return transform.transformPoint(p - getPosition());
120 if (nodes.empty())
return;
123 std::queue<unsigned int> toVisit;
124 std::vector<bool> visited(nodes.size(),
false);
125 std::vector<unsigned int> levelCounts(nodes.size(), 0);
126 std::vector<unsigned int> nodeLevels(nodes.size(), 0);
127 std::vector<bool> terminators(nodes.size(),
false);
131 edges.reserve(nodes.size() * 2);
137 std::vector<unsigned int> next;
139 while (!toVisit.empty()) {
140 const unsigned i = toVisit.front();
141 const unsigned int dist = nodeLevels[i];
142 const unsigned int newDist = dist + 1;
148 for (
unsigned int jump : next) {
149 if (jump >= nodes.size()) {
150 terminators[i] =
true;
153 edges.emplace_back(i, jump);
154 if (visited[jump])
continue;
156 visited[jump] =
true;
157 levelCounts[newDist] += 1;
158 nodeLevels[jump] = newDist;
164 renderNodes.resize(nodes.size());
167 std::vector<sf::Vector2f> nodePositions(nodes.size());
168 std::vector<float> levelLefts(levelCounts.size());
169 for (
unsigned int i = 0; i < levelCounts.size(); ++i) {
170 const float width =
static_cast<float>(levelCounts[i]) *
Spacing;
171 levelLefts[i] = -width * 0.5f;
173 const unsigned int maxLevelCount =
174 *std::max_element(std::begin(levelCounts), std::end(levelCounts));
175 const float maxwidth =
static_cast<float>(maxLevelCount) *
Spacing;
176 const float maxLeft = -maxwidth * 0.5f;
179 sf::Vector2f unreachablePos(maxLeft, -
Spacing);
180 for (
unsigned int i = 0; i < nodes.size(); ++i) {
182 nodePositions[i].x = levelLefts[nodeLevels[i]];
183 nodePositions[i].y =
static_cast<float>(nodeLevels[i]) *
Spacing;
184 levelLefts[nodeLevels[i]] +=
Spacing;
187 nodePositions[i] = unreachablePos;
191 renderNodes[i].setup(nodePositions[i], nodes[i], i, terminators[i]);
199 if (getComponent()) { getComponent()->onElementUpdated(); }
203 bl::gui::Element::update(dt);
205 if (selected < renderNodes.size()) {
206 std::optional<sf::Color> color;
207 if (flashTime > 0.f) {
209 if (flashTime > FlashPeriod) {
210 flashTime = -flashTime + FlashPeriod;
211 highlightSelected =
false;
212 if (getComponent()) { getComponent()->onElementUpdated(); }
217 if (flashTime < -FlashPeriod) {
218 flashTime = -flashTime - FlashPeriod;
219 highlightSelected =
true;
220 if (getComponent()) { getComponent()->onElementUpdated(); }
226 sf::Vector2f ConversationTree::minimumRequisition()
const {
return {500.f, 500.f}; }
228 bl::gui::rdr::Component* ConversationTree::doPrepareRender(bl::gui::rdr::Renderer& renderer) {
229 return renderer.createComponent<ConversationTree>(*this);
238 label = nodeToString(i, src);
All classes and functionality used for implementing the game editor.
static void getNextJumps(const Node &node, std::vector< unsigned int > &jumps)
Populates the jumps vector with the indices reachable from the given node.
Building block of conversations. A conversation is a tree of nodes and each node is an action that ha...
static std::string typeToString(Type type)
Converts a node type to a human readable string.
Type getType() const
Returns the type of this node.
Special component that renders a clickable tree view of a conversation.
void setSelected(unsigned int i)
Set the selected node index.
static constexpr float NodeRadiusSquared
std::function< void(unsigned int)> ClickCb
Called when a node is clicked.
static constexpr float Spacing
void update(const std::vector< core::file::Conversation::Node > &nodes)
Updates the rendered tree with the new nodes.
static Ptr create(const ClickCb &clickCb)
Construct a new Conversation Tree component.
std::shared_ptr< ConversationTree > Ptr
static constexpr float NodePadding
static constexpr float NodeRadius
void setup(const sf::Vector2f &pos, const core::file::Conversation::Node &src, unsigned int i, bool terminator)