FlowArithmeticCalculator 1.0.0
Flow Arithmetic Calculator
Loading...
Searching...
No Matches
BlocksManager.cpp
Go to the documentation of this file.
1#include "BlocksManager.h"
2
3namespace business_logic {
4
6 : calculations::BlocksCalculator(this),
7 draggedBlock(nullptr),
8 mouseX(0),
9 mouseY(0),
10 dragOffset({.width = 0, .height = 0}),
11 windowDelegate(windowDelegate) {}
12
14 // do not handle mouse down if we have an active choices input
16 logger->info("Skipping mouse down because we have an active choices input");
17 return;
18 }
19
20 // check for double-click
21 auto maybeClickedBlock = getBlockAtMousePos();
22
23 if (std::chrono::steady_clock::now() - doubleClickCtLastMouseClickTime <=
25 // double click
26
27 if (maybeClickedBlock) {
28 const auto& clickedBlock = maybeClickedBlock.value();
29
30 auto* doubleClickableBlock =
32 clickedBlock.get());
33
34 // check if the block is IDoubleClickable
35 if (doubleClickableBlock == nullptr) {
36 logger->info("Double clicked block {} is not IDoubleClickable",
37 clickedBlock->getSelfId());
38 } else {
39 doubleClickableBlock->onDoubleClick(mouseX, mouseY);
40 }
41 } else {
42 logger->info("Double clicked outside any block");
43 }
44
45 // the next double click event should start counting now
46 doubleClickCtLastMouseClickTime = std::chrono::steady_clock::time_point();
47 } else {
48 // single click
49
50 if (maybeClickedBlock) {
51 const auto& clickedBlock = maybeClickedBlock.value();
52
53 auto maybeClickedPort =
54 clickedBlock->getPortAtCoordinates({.x = mouseX, .y = mouseY});
55
56 if (maybeClickedPort.value_or(nullptr) != nullptr) {
57 logger->info("Clicked port '{}' on block {}",
58 maybeClickedPort.value()->name,
59 clickedBlock->getSelfId());
60
62 clickedBlock.get(), maybeClickedPort.value(), windowDelegate, this);
63
64 // this event should not count as a possible double-click interaction part
65 doubleClickCtLastMouseClickTime = std::chrono::steady_clock::time_point();
66 } else {
67 logger->info("Clicked block {} for dragging", clickedBlock->getSelfId());
68 draggedBlock = clickedBlock.get();
69
70 dragOffset = {.width = mouseX - clickedBlock->getCx(),
71 .height = mouseY - clickedBlock->getCy()};
72
74
75 // update the last mouse click time for double-click
76 doubleClickCtLastMouseClickTime = std::chrono::steady_clock::now();
77 }
78 } else {
79 logger->info("Clicked outside any block");
80
81 // update the last mouse click time for double-click
82 doubleClickCtLastMouseClickTime = std::chrono::steady_clock::now();
83 }
84 }
85 }
86
88 if (draggedBlock != nullptr) {
89 logger->info("Finished dragging block {}", draggedBlock->getSelfId());
91 draggedBlock = nullptr;
92 }
93 }
94
96 logger->info("Escape key pressed");
97
100 }
101
104 }
105 }
106
108 if (inputChoiceInteraction.has_value()) {
109 if (number >= 1 &&
110 static_cast<size_t>(number) <= inputChoiceInteraction->choices.size()) {
111 logger->info("Selected choice: {}", number);
112
113 auto choice = inputChoiceInteraction->choices[number - 1];
114 onNewBlockChoice(choice.value);
116 } else {
117 logger->error("Invalid choice number: {}", number);
119 "Invalid choice",
120 "You entered: " + std::to_string(number) +
121 " which is out of range. Pick a "
122 "number between 1 and " +
123 std::to_string(inputChoiceInteraction->choices.size()));
124 }
125 }
126 }
127
129 mouseX = x;
130 mouseY = y;
131
132 if (draggedBlock != nullptr) {
134 }
135 }
136
137 std::optional<std::shared_ptr<business_logic::elements::blocks::BaseBlock>>
141
142 std::optional<std::shared_ptr<business_logic::elements::blocks::BaseBlock>>
144 // find the block that is being hovered over; reverse order to render newer blocks on top
145 // (as if they had the highest z-index)
146 auto maybeHoveredBlockIt =
147 std::find_if(blocks.rbegin(), blocks.rend(), [x, y](const auto& block) {
148 return block->isHovered(x, y);
149 });
150
151 std::optional<std::shared_ptr<business_logic::elements::blocks::BaseBlock>>
152 maybeHoveredBlock = std::nullopt;
153
154 if (maybeHoveredBlockIt != blocks.rend()) {
155 maybeHoveredBlock = *maybeHoveredBlockIt;
156 }
157
158 return maybeHoveredBlock;
159 }
160
163 switch (blockType) {
165 blocks.push_back(std::make_shared<business_logic::elements::blocks::ConstantBlock>(
166 mouseX, mouseY, this, this, windowDelegate));
167 } break;
168
170 blocks.push_back(std::make_shared<business_logic::elements::blocks::AddBlock>(
171 mouseX, mouseY, this, this, windowDelegate));
172 } break;
173
175 blocks.push_back(std::make_shared<business_logic::elements::blocks::SubtractBlock>(
176 mouseX, mouseY, this, this, windowDelegate));
177 } break;
178
180 blocks.push_back(std::make_shared<business_logic::elements::blocks::MultiplyBlock>(
181 mouseX, mouseY, this, this, windowDelegate));
182 } break;
183
185 blocks.push_back(std::make_shared<business_logic::elements::blocks::DivideBlock>(
186 mouseX, mouseY, this, this, windowDelegate));
187 } break;
188
190 blocks.push_back(std::make_shared<business_logic::elements::blocks::PowerBlock>(
191 mouseX, mouseY, this, this, windowDelegate));
192 } break;
193
195 blocks.push_back(std::make_shared<business_logic::elements::blocks::InvertBlock>(
196 mouseX, mouseY, this, this, windowDelegate));
197 } break;
198
200 blocks.push_back(std::make_shared<business_logic::elements::blocks::MonitorBlock>(
201 mouseX, mouseY, this, this, windowDelegate));
202 } break;
203
204 default: {
205 auto name = magic_enum::enum_name(blockType);
206
207 logger->error("Unknown user-selected block type: {}", name);
208
210 "Invalid choice",
211 "You selected an invalid block type: '" + std::string(name) + "'");
212 } break;
213 }
214 }
215
218 inputChoiceInteraction) {
219 auto choices = inputChoiceInteraction.choices;
220
221 std::vector<std::string> labels;
222 labels.reserve(choices.size());
223 int index = 0;
224 std::for_each(
225 choices.begin(),
226 choices.end(),
227 [&index, &labels](
229 labels.push_back(std::to_string(++index) + " " + choice.displayName);
230 });
231
232 index = 0; // reset for re-use
233
235 1);
236 {
237 std::vector<components::UIText> rowBuff;
238 size_t number = 1;
239 for (; number <= choices.size(); number++) {
240 rowBuff.emplace_back(std::to_string(number) + " " + choices[number - 1].displayName,
242
243 if (number % constants::MAX_INPUT_CHOICES_PER_ROW == 0) {
244 inputChoicesUiTextsRows.emplace_back(rowBuff);
245 rowBuff.clear();
246 }
247 }
248
249 if (!rowBuff.empty()) {
250 inputChoicesUiTextsRows.emplace_back(rowBuff);
251 }
252
253 logger->info("Prepared {} input choices for rendering", number);
254 }
255
256 this->inputChoiceInteraction.emplace(std::move(inputChoiceInteraction));
257 }
258
260
262 logger->info("Clearing active choices input");
263
265
267 inputChoicesUiTextsRows.shrink_to_fit();
268 }
269
274 .port = source.port};
276 .port = dest.port};
277
278 return std::any_of(connectionsRegistry.begin(),
280 [variant1, variant2](const auto& connection) {
281 return connection.first == variant1 &&
282 connection.second.contains(variant2);
283 });
284 }
285
288 return std::any_of(
289 connectionsRegistry.begin(), connectionsRegistry.end(), [side](const auto& connection) {
290 return connection.first == side || connection.second.contains(side);
291 });
292 }
293
298
300 // IMPORTANT: this method is called from base (BaseBlock) destructor, so the child
301 // class (block implementation) is already destroyed; therefore, its methods are no use
302 // here and will result in errors
303
304 // erase entries where the block was the key or the value
305 for (auto& [source, destinations] : connectionsRegistry) {
306 if (source.block == block) {
307 // the deleted block is the source of the connection
308 connectionsRegistry.erase(source);
309 } else {
310 // the deleted block is the destination of the connection
311 std::erase_if(destinations,
312 [block](const auto& dest) { return dest.block == block; });
313 }
314 }
315
316 // ensure draggedBlock is not referencing the deleted block
317 if (draggedBlock == block) {
318 draggedBlock = nullptr;
319 }
320 }
321
323 const std::shared_ptr<business_logic::elements::blocks::BaseBlock>& block) {
324 // check if a port was right-clicked
325 auto maybeClickedPort = block->getPortAtCoordinates({.x = mouseX, .y = mouseY});
326
327 if (maybeClickedPort.has_value()) {
329 .block = block.get(), .port = maybeClickedPort.value()};
330
331 // remove all connections from the right-clicked port (if an entry exists, otherwise
332 // call does nothing)
333 size_t erasedCount = 0;
334
335 for (auto& [source, destinations] : connectionsRegistry) {
336 if (source == sideToDelete) {
337 // the right-clicked port is the source of the connection
338 erasedCount += connectionsRegistry.erase(source);
339 } else {
340 // the right-clicked port is the destination of the connection
341 erasedCount += std::erase_if(destinations, [sideToDelete](const auto& dest) {
342 return dest == sideToDelete;
343 });
344 }
345 }
346
347 logger->info("Right clicked on port '{}' - removed its {} connections",
348 maybeClickedPort.value()->name,
349 erasedCount);
350 } else {
351 logger->info("Right clicked on block '{}' - asking for confirmation about removal",
352 block->getSelfId());
353
355 "Remove Block",
356 "Are you sure you want to remove block '" +
357 std::string(magic_enum::enum_name(block->getBlockType())) + "'?")) {
358 logger->info("User confirmed removal of block '{}'", block->getSelfId());
359
360 blocks.erase(std::remove(blocks.begin(), blocks.end(), block), blocks.end());
361
364 }
365 } else {
366 logger->info("User cancelled removal of block '{}'", block->getSelfId());
367 }
368 }
369 }
370
371 const std::vector<std::shared_ptr<business_logic::elements::blocks::BaseBlock>>&
373 return blocks;
374 }
375
376 const std::unordered_map<elements::structures::BlocksConnectionSide,
377 std::unordered_set<elements::structures::BlocksConnectionSide>>&
381} // namespace business_logic
bool hasActiveChoicesInput() const
Checks if the input callback is active.
std::unordered_map< business_logic::elements::structures::BlocksConnectionSide, std::unordered_set< business_logic::elements::structures::BlocksConnectionSide > > connectionsRegistry
void handleMouseUp()
Handles the mouse up event.
bool hasConnectionBetween(const business_logic::elements::structures::BlocksConnectionSide &source, const business_logic::elements::structures::BlocksConnectionSide &dest) const override
Checks if there is a connection between two entities.
void clearActiveChoicesInput()
Clears the active input choices and the corresponding generated UI texts rows.
void handleNumericKeyPress(int number)
Handles the numeric key press event.
void setActiveChoicesInput(business_logic::input::InputChoiceInteraction< business_logic::elements::blocks::BlockType > &&inputChoiceInteraction)
Sets the input callback; this will override rendering other entities.
void onNewBlockChoice(const business_logic::elements::blocks::BlockType &blockType) override
Called when a new block is chosen to be added to the canvas.
business_logic::geometry::Size2D dragOffset
std::chrono::steady_clock::time_point doubleClickCtLastMouseClickTime
void onPortsConnected(const business_logic::elements::structures::BlocksConnectionSide &source, const business_logic::elements::structures::BlocksConnectionSide &dest) override
Invoked when a connection is made between two ports + blocks (sides)
std::optional< std::shared_ptr< business_logic::elements::blocks::BaseBlock > > getBlockAtMousePos()
Gets the block at the mouse position.
void handleRightClickOnBlock(const std::shared_ptr< business_logic::elements::blocks::BaseBlock > &block)
Handles right click event on a block.
business_logic::delegate::IWindowDelegate * windowDelegate
void handleEscapeKeyPress()
Handles the ESC key press event.
const std::vector< std::shared_ptr< business_logic::elements::blocks::BaseBlock > > & getBlocks() const override
Gets the blocks.
const std::unordered_map< business_logic::elements::structures::BlocksConnectionSide, std::unordered_set< business_logic::elements::structures::BlocksConnectionSide > > & getConnectionsRegistry() const override
Gets the connections registry.
business_logic::input::ConnectPortsInteraction connectPortsInteraction
bool isInputConnected(const business_logic::elements::structures::BlocksConnectionSide &side) const override
Checks if the input port is connected to anything.
void handleMouseDown()
Handles the mouse down event.
std::optional< business_logic::input::InputChoiceInteraction< business_logic::elements::blocks::BlockType > > inputChoiceInteraction
void handleMouseMove(int x, int y)
Handles the mouse move event.
business_logic::elements::blocks::BaseBlock * draggedBlock
void onBlockDeleted(const business_logic::elements::blocks::BaseBlock *block) override
Called when a block is deleted.
std::vector< components::UITextsRow > inputChoicesUiTextsRows
std::vector< std::shared_ptr< business_logic::elements::blocks::BaseBlock > > blocks
BlocksManager(business_logic::delegate::IWindowDelegate *windowDelegate)
std::optional< std::shared_ptr< business_logic::elements::blocks::BaseBlock > > getBlockAt(int x, int y)
Gets the block at the given coordinates.
std::shared_ptr< spdlog::logger > logger
Definition Loggable.h:75
virtual bool promptConfirmation(const std::string &title, const std::string &message)=0
Prompt for user confirmation.
virtual void showWarning(const std::string &title, const std::string &message)=0
Show a warning message.
The base class for all blocks, containing common functionality and members.
Definition BaseBlock.h:36
void onDragStart() override
Called when dragging starts.
Definition BaseBlock.cpp:54
void onDragEnd() override
Called when dragging ends.
Definition BaseBlock.cpp:56
std::optional< const structures::Port * > getPortAtCoordinates(const geometry::Point2D &point) const
Gets the port at given coordinates.
void onDragProgress(int x, int y) override
Called during dragging.
Definition BaseBlock.cpp:45
virtual std::string getSelfId() const =0
Interface for elements that can be double-clicked.
virtual void onDoubleClick(int x, int y)=0
Called when the element is double-clicked.
bool isStarted() const
Returns whether the interaction has started (i.e., is pending and a dragging line should be rendered)
void resetInteraction()
Resets the interaction, setting both sides to std::nullopt
void handleUserInteractedWith(business_logic::elements::blocks::BaseBlock *block, const business_logic::elements::structures::Port *port, business_logic::delegate::IWindowDelegate *windowDelegate, business_logic::delegate::IBlockLifecycleManagerDelegate *blockLifecycleManagerDelegate)
constexpr std::chrono::milliseconds DOUBLE_CLICK_TIME_THRESHOLD_MS(200)
constexpr int MAX_INPUT_CHOICES_PER_ROW
Definition constants.h:38
BlockType
The available block types.
Definition BlockType.h:10
The business logic module.
business_logic::elements::blocks::BaseBlock * block
The block.
const business_logic::elements::structures::Port * port
The port.