Zrythm v2.0.0-DEV
a highly automated and intuitive digital audio workstation
Loading...
Searching...
No Matches
move_arranger_objects_command.h
1// SPDX-FileCopyrightText: © 2025 Alexandros Theodotou <alex@zrythm.org>
2// SPDX-License-Identifier: LicenseRef-ZrythmLicense
3
4#pragma once
5
6#include <algorithm>
7#include <ranges>
8#include <vector>
9
10#include "structure/arrangement/arranger_object_all.h"
11
12#include <QUndoCommand>
13
14namespace zrythm::commands
15{
16class MoveArrangerObjectsCommand : public QUndoCommand
17{
18public:
19 MoveArrangerObjectsCommand (
20 std::vector<structure::arrangement::ArrangerObjectUuidReference> objects,
21 units::precise_tick_t tick_delta,
22 double vertical_delta = 0.0)
23 : QUndoCommand (QObject::tr ("Move Objects")),
24 objects_ (std::move (objects)), tick_delta_ (tick_delta),
25 vertical_delta_ (vertical_delta)
26 {
27 // Store original positions for undo
28 original_positions_.reserve (objects_.size ());
29 for (const auto &obj_ref : objects_)
30 {
31 std::visit (
32 [&] (auto &&obj) {
33 using ObjectT = base_type<decltype (obj)>;
34 original_positions_.push_back (
35 units::ticks (obj->position ()->ticks ()));
36 if constexpr (
37 std::is_same_v<ObjectT, structure::arrangement::MidiNote>)
38 {
39 original_vertical_positions_.push_back (obj->pitch ());
40 }
41 else if constexpr (
42 std::is_same_v<ObjectT, structure::arrangement::AutomationPoint>)
43 {
44 original_vertical_positions_.push_back (obj->value ());
45 }
46 else
47 {
48 original_vertical_positions_.push_back (0);
49 }
50 },
51 obj_ref.get_object ());
52 }
53 }
54
55 int id () const override { return 894553188; }
56 bool mergeWith (const QUndoCommand * other) override
57 {
58 if (other->id () != id ())
59 return false;
60
61 // only merge if other command was made in quick succession of this
62 // command's redo() and operates on the same objects
63 const auto * other_cmd =
64 dynamic_cast<const MoveArrangerObjectsCommand *> (other);
65 const auto cur_time = std::chrono::steady_clock::now ();
66 const auto duration = cur_time - last_redo_timestamp_;
67 if (
68 std::chrono::duration_cast<std::chrono::milliseconds> (duration).count ()
69 > 1'000)
70 {
71 return false;
72 }
73
74 // Check if we're operating on the same set of objects
75 if (objects_.size () != other_cmd->objects_.size ())
76 return false;
77
78 if (
79 !std::ranges::equal (
80 objects_, other_cmd->objects_, {},
81 &structure::arrangement::ArrangerObjectUuidReference::id,
82 &structure::arrangement::ArrangerObjectUuidReference::id))
83 {
84 return false;
85 }
86
87 last_redo_timestamp_ = cur_time;
88 tick_delta_ += other_cmd->tick_delta_;
89 return true;
90 }
91
92 void undo () override
93 {
94 for (
95 const auto &[obj_ref, original_pos, original_vertical_pos] :
96 std::views::zip (
97 objects_, original_positions_, original_vertical_positions_))
98 {
99 std::visit (
100 [&] (auto &&obj) {
101 using ObjectT = base_type<decltype (obj)>;
102 obj->position ()->setTicks (original_pos.in (units::ticks));
103 if (vertical_delta_ != 0)
104 {
105 if constexpr (
106 std::is_same_v<ObjectT, structure::arrangement::MidiNote>)
107 {
108 obj->setPitch (static_cast<int> (original_vertical_pos));
109 }
110 else if constexpr (
111 std::is_same_v<ObjectT, structure::arrangement::AutomationPoint>)
112 {
113 obj->setValue (static_cast<float> (original_vertical_pos));
114 }
115 }
116 },
117 obj_ref.get_object ());
118 }
119 }
120
121 void redo () override
122 {
123 for (
124 const auto &[obj_ref, original_pos, original_vertical_pos] :
125 std::views::zip (
126 objects_, original_positions_, original_vertical_positions_))
127 {
128 std::visit (
129 [&] (auto &&obj) {
130 using ObjectT = base_type<decltype (obj)>;
131 obj->position ()->setTicks (
132 (original_pos + tick_delta_).in (units::ticks));
133 if (vertical_delta_ != 0)
134 {
135 if constexpr (
136 std::is_same_v<ObjectT, structure::arrangement::MidiNote>)
137 {
138 obj->setPitch (
139 static_cast<int> (original_vertical_pos + vertical_delta_));
140 }
141 else if constexpr (
142 std::is_same_v<ObjectT, structure::arrangement::AutomationPoint>)
143 {
144 obj->setValue (
145 static_cast<float> (
146 original_vertical_pos + vertical_delta_));
147 }
148 }
149 },
150 obj_ref.get_object ());
151 }
152 last_redo_timestamp_ = std::chrono::steady_clock::now ();
153 }
154
155private:
156 std::vector<structure::arrangement::ArrangerObjectUuidReference> objects_;
157 std::vector<units::precise_tick_t> original_positions_;
158 std::vector<double> original_vertical_positions_;
159 units::precise_tick_t tick_delta_;
160 double vertical_delta_{};
161 std::chrono::time_point<std::chrono::steady_clock> last_redo_timestamp_;
162};
163
165 : public MoveArrangerObjectsCommand
166{
167public:
168 static constexpr int CommandId = 1762956469;
169 using MoveArrangerObjectsCommand::MoveArrangerObjectsCommand;
170
171 int id () const override { return CommandId; }
172};
173
174} // namespace zrythm::commands