Reorder the chain by dragging a row

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Si
2026-09-24 00:43:51 +02:00
co-authored by Claude Opus 5.5
parent 81ca17463e
commit 536b8620ea
4 changed files with 159 additions and 9 deletions
+61
View File
@@ -122,6 +122,30 @@ bool addFromMenu(Gtk::MenuButton& add, const std::string& title) {
return true;
}
// The drop target the chain list reorders its rows with.
GtkDropTarget* chainDropTarget(Gtk::Widget& list) {
GListModel* controllers = gtk_widget_observe_controllers(list.gobj());
GtkDropTarget* found = nullptr;
for (guint i = 0; i < g_list_model_get_n_items(controllers) && found == nullptr; ++i) {
auto* controller = g_list_model_get_item(controllers, i);
if (GTK_IS_DROP_TARGET(controller)) found = GTK_DROP_TARGET(controller);
g_object_unref(controller);
}
g_object_unref(controllers);
return found;
}
// What dropping the row of stage `index` at `y` does; a real drag needs a
// pointer, which the tests do not have.
void dropStage(GtkDropTarget* drop, int index, double y) {
GValue value = G_VALUE_INIT;
g_value_init(&value, G_TYPE_INT);
g_value_set_int(&value, index);
gboolean handled = FALSE;
g_signal_emit_by_name(drop, "drop", &value, 1.0, y, &handled);
g_value_unset(&value);
}
void runTests(Gtk::Window& window) {
const auto& effects = effector::effects();
@@ -239,6 +263,43 @@ void runTests(Gtk::Window& window) {
"Remove drops the stage and renumbers the rest");
check(!sources->get_visible(), "and the sources hide again");
std::cout << "drag and drop\n";
addFromMenu(*add, "Scanlines");
for (const auto& effect : effects) {
if (effect->title() != "Blur" && effect->title() != "Scanlines") {
addFromMenu(*add, effect->title());
break;
}
}
GtkDropTarget* drop = chainDropTarget(*chainList);
check(drop != nullptr, "the chain list takes drops");
if (drop != nullptr && rowCount(*chainList) == 3) {
const std::string first = rowTitle(*chainList, 0).substr(3);
const std::string second = rowTitle(*chainList, 1).substr(3);
const std::string third = rowTitle(*chainList, 2).substr(3);
const double bottom = chainList->get_row_at_index(2)->get_height() * 3.0;
GdkDragAction action = GDK_ACTION_NONE;
g_signal_emit_by_name(drop, "motion", 1.0, bottom, &action);
check(chainList->get_row_at_index(2)->has_css_class("drop-after"),
"dragging past the last row marks its bottom edge");
dropStage(drop, 0, bottom);
pump();
check(rowTitle(*chainList, 0) == "1. " + second && rowTitle(*chainList, 1) == "2. " + third &&
rowTitle(*chainList, 2) == "3. " + first,
"dropping the first stage past the last moves it to the end");
check(chainList->get_selected_row() == chainList->get_row_at_index(2),
"and selects it");
check(!chainList->get_row_at_index(2)->has_css_class("drop-after"),
"and clears the mark");
dropStage(drop, 2, 1.0);
pump();
check(rowTitle(*chainList, 0) == "1. " + first && rowTitle(*chainList, 1) == "2. " + second,
"dropping on the top edge of the first row moves it to the front");
}
for (int i = rowCount(*chainList) - 1; i > 0; --i) {
if (auto* removeRow = withTooltip(*chainList->get_row_at_index(i), "Remove")) press(*removeRow);
}
std::cout << "many stages\n";
for (int i = 0; i < 4; ++i) addFromMenu(*add, effects[static_cast<std::size_t>(i) % effects.size()]->title());
pump();