From 40050b3ac36bb013f6cae05ed314f89816cee36e Mon Sep 17 00:00:00 2001 From: Elias Projahn Date: Tue, 17 Nov 2020 16:48:21 +0100 Subject: [PATCH] Restore edit and delete menus --- musicus/res/ui/ensemble_screen.ui | 15 +- musicus/res/ui/person_screen.ui | 15 +- musicus/res/ui/recording_screen.ui | 25 ++- musicus/res/ui/work_screen.ui | 15 +- musicus/src/dialogs/ensemble_editor.rs | 2 +- .../recording/recording_editor_dialog.rs | 3 +- .../src/dialogs/work/work_editor_dialog.rs | 3 +- musicus/src/screens/ensemble_screen.rs | 49 ++++-- musicus/src/screens/person_screen.rs | 53 +++--- musicus/src/screens/recording_screen.rs | 164 ++++++++++-------- musicus/src/screens/work_screen.rs | 49 ++++-- musicus/src/window.rs | 4 +- 12 files changed, 253 insertions(+), 144 deletions(-) diff --git a/musicus/res/ui/ensemble_screen.ui b/musicus/res/ui/ensemble_screen.ui index 206097d..140af01 100644 --- a/musicus/res/ui/ensemble_screen.ui +++ b/musicus/res/ui/ensemble_screen.ui @@ -27,11 +27,12 @@ - + True True False True + menu True @@ -200,4 +201,16 @@ + +
+ + Edit ensemble + widget.edit + + + Delete ensemble + widget.delete + +
+
diff --git a/musicus/res/ui/person_screen.ui b/musicus/res/ui/person_screen.ui index f649a33..f80fece 100644 --- a/musicus/res/ui/person_screen.ui +++ b/musicus/res/ui/person_screen.ui @@ -27,11 +27,12 @@
- + True True False True + menu True @@ -258,4 +259,16 @@ + +
+ + Edit person + widget.edit + + + Delete person + widget.delete + +
+
diff --git a/musicus/res/ui/recording_screen.ui b/musicus/res/ui/recording_screen.ui index eaac798..a33986f 100644 --- a/musicus/res/ui/recording_screen.ui +++ b/musicus/res/ui/recording_screen.ui @@ -27,11 +27,12 @@
- + True True False True + menu True @@ -159,4 +160,26 @@ + +
+ + Edit recording + widget.edit + + + Delete recording + widget.delete + +
+
+ + Edit tracks + widget.edit-tracks + + + Delete tracks + widget.delete-tracks + +
+
diff --git a/musicus/res/ui/work_screen.ui b/musicus/res/ui/work_screen.ui index 206097d..2bc3ed6 100644 --- a/musicus/res/ui/work_screen.ui +++ b/musicus/res/ui/work_screen.ui @@ -27,11 +27,12 @@
- + True True False True + menu True @@ -200,4 +201,16 @@ + +
+ + Edit work + widget.edit + + + Delete work + widget.delete + +
+
diff --git a/musicus/src/dialogs/ensemble_editor.rs b/musicus/src/dialogs/ensemble_editor.rs index ba5ccf9..8b5e521 100644 --- a/musicus/src/dialogs/ensemble_editor.rs +++ b/musicus/src/dialogs/ensemble_editor.rs @@ -63,8 +63,8 @@ where let c = glib::MainContext::default(); c.spawn_local(async move { clone.backend.db().update_ensemble(ensemble.clone()).await.unwrap(); - clone.window.close(); (clone.callback)(ensemble.clone()); + clone.window.close(); }); })); diff --git a/musicus/src/dialogs/recording/recording_editor_dialog.rs b/musicus/src/dialogs/recording/recording_editor_dialog.rs index f831d98..2c0b8cd 100644 --- a/musicus/src/dialogs/recording/recording_editor_dialog.rs +++ b/musicus/src/dialogs/recording/recording_editor_dialog.rs @@ -44,8 +44,9 @@ impl RecordingEditorDialog { editor.set_selected_cb(clone!(@strong this => move |recording| { if let Some(cb) = &*this.selected_cb.borrow() { cb(recording); - this.window.close(); } + + this.window.close(); })); this diff --git a/musicus/src/dialogs/work/work_editor_dialog.rs b/musicus/src/dialogs/work/work_editor_dialog.rs index 83ec329..9f455b0 100644 --- a/musicus/src/dialogs/work/work_editor_dialog.rs +++ b/musicus/src/dialogs/work/work_editor_dialog.rs @@ -44,8 +44,9 @@ impl WorkEditorDialog { editor.set_saved_cb(clone!(@strong this => move |work| { if let Some(cb) = &*this.saved_cb.borrow() { cb(work); - this.window.close(); } + + this.window.close(); })); this diff --git a/musicus/src/screens/ensemble_screen.rs b/musicus/src/screens/ensemble_screen.rs index 0d90296..e9740b0 100644 --- a/musicus/src/screens/ensemble_screen.rs +++ b/musicus/src/screens/ensemble_screen.rs @@ -1,8 +1,10 @@ use super::*; use crate::backend::*; use crate::database::*; +use crate::dialogs::EnsembleEditor; use crate::widgets::*; use gettextrs::gettext; +use gio::prelude::*; use glib::clone; use gtk::prelude::*; use gtk_macros::get_widget; @@ -12,6 +14,8 @@ use std::rc::Rc; pub struct EnsembleScreen { backend: Rc, + window: gtk::Window, + ensemble: Ensemble, widget: gtk::Box, stack: gtk::Stack, recording_list: Rc>, @@ -19,36 +23,29 @@ pub struct EnsembleScreen { } impl EnsembleScreen { - pub fn new(backend: Rc, ensemble: Ensemble) -> Rc { + pub fn new(backend: Rc, window: &W, ensemble: Ensemble) -> Rc + where + W: IsA, + { let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/ensemble_screen.ui"); get_widget!(builder, gtk::Box, widget); get_widget!(builder, libhandy::HeaderBar, header); get_widget!(builder, gtk::Button, back_button); - get_widget!(builder, gtk::MenuButton, menu_button); get_widget!(builder, gtk::SearchEntry, search_entry); get_widget!(builder, gtk::Stack, stack); get_widget!(builder, gtk::Frame, recording_frame); header.set_title(Some(&ensemble.name)); - let edit_menu_item = gio::MenuItem::new(Some(&gettext("Edit ensemble")), None); - edit_menu_item.set_action_and_target_value( - Some("win.edit-ensemble"), - Some(&glib::Variant::from(ensemble.id)), - ); + let edit_action = gio::SimpleAction::new("edit", None); + let delete_action = gio::SimpleAction::new("delete", None); - let delete_menu_item = gio::MenuItem::new(Some(&gettext("Delete ensemble")), None); - delete_menu_item.set_action_and_target_value( - Some("win.delete-ensemble"), - Some(&glib::Variant::from(ensemble.id)), - ); + let actions = gio::SimpleActionGroup::new(); + actions.add_action(&edit_action); + actions.add_action(&delete_action); - let menu = gio::Menu::new(); - menu.append_item(&edit_menu_item); - menu.append_item(&delete_menu_item); - - menu_button.set_menu_model(Some(&menu)); + widget.insert_action_group("widget", Some(&actions)); let recording_list = List::new(&gettext("No recordings found.")); @@ -83,6 +80,8 @@ impl EnsembleScreen { let result = Rc::new(Self { backend, + window: window.clone().upcast(), + ensemble, widget, stack, recording_list, @@ -105,17 +104,29 @@ impl EnsembleScreen { .set_selected(clone!(@strong result => move |recording| { let navigator = result.navigator.borrow().clone(); if let Some(navigator) = navigator { - navigator.push(RecordingScreen::new(result.backend.clone(), recording.clone())); + navigator.push(RecordingScreen::new(result.backend.clone(), &result.window, recording.clone())); } })); + edit_action.connect_activate(clone!(@strong result => move |_, _| { + EnsembleEditor::new(result.backend.clone(), &result.window, Some(result.ensemble.clone()), |_| {}).show(); + })); + + delete_action.connect_activate(clone!(@strong result => move |_, _| { + let context = glib::MainContext::default(); + let clone = result.clone(); + context.spawn_local(async move { + clone.backend.db().delete_ensemble(clone.ensemble.id).await.unwrap(); + }); + })); + let context = glib::MainContext::default(); let clone = result.clone(); context.spawn_local(async move { let recordings = clone .backend .db() - .get_recordings_for_ensemble(ensemble.id as u32) + .get_recordings_for_ensemble(clone.ensemble.id) .await .unwrap(); diff --git a/musicus/src/screens/person_screen.rs b/musicus/src/screens/person_screen.rs index 77fcbe4..8caf247 100644 --- a/musicus/src/screens/person_screen.rs +++ b/musicus/src/screens/person_screen.rs @@ -1,8 +1,10 @@ use super::*; use crate::backend::*; use crate::database::*; +use crate::dialogs::PersonEditor; use crate::widgets::*; use gettextrs::gettext; +use gio::prelude::*; use glib::clone; use gtk::prelude::*; use gtk_macros::get_widget; @@ -12,6 +14,8 @@ use std::rc::Rc; pub struct PersonScreen { backend: Rc, + window: gtk::Window, + person: Person, widget: gtk::Box, stack: gtk::Stack, work_list: Rc>, @@ -20,13 +24,15 @@ pub struct PersonScreen { } impl PersonScreen { - pub fn new(backend: Rc, person: Person) -> Rc { + pub fn new(backend: Rc, window: &W, person: Person) -> Rc + where + W: IsA, + { let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/person_screen.ui"); get_widget!(builder, gtk::Box, widget); get_widget!(builder, libhandy::HeaderBar, header); get_widget!(builder, gtk::Button, back_button); - get_widget!(builder, gtk::MenuButton, menu_button); get_widget!(builder, gtk::SearchEntry, search_entry); get_widget!(builder, gtk::Stack, stack); get_widget!(builder, gtk::Box, work_box); @@ -36,23 +42,14 @@ impl PersonScreen { header.set_title(Some(&person.name_fl())); - let edit_menu_item = gio::MenuItem::new(Some(&gettext("Edit person")), None); - edit_menu_item.set_action_and_target_value( - Some("win.edit-person"), - Some(&glib::Variant::from(person.id)), - ); + let edit_action = gio::SimpleAction::new("edit", None); + let delete_action = gio::SimpleAction::new("delete", None); - let delete_menu_item = gio::MenuItem::new(Some(&gettext("Delete person")), None); - delete_menu_item.set_action_and_target_value( - Some("win.delete-person"), - Some(&glib::Variant::from(person.id)), - ); + let actions = gio::SimpleActionGroup::new(); + actions.add_action(&edit_action); + actions.add_action(&delete_action); - let menu = gio::Menu::new(); - menu.append_item(&edit_menu_item); - menu.append_item(&delete_menu_item); - - menu_button.set_menu_model(Some(&menu)); + widget.insert_action_group("widget", Some(&actions)); let work_list = List::new(&gettext("No works found.")); @@ -106,6 +103,8 @@ impl PersonScreen { let result = Rc::new(Self { backend, + window: window.clone().upcast(), + person, widget, stack, work_list, @@ -131,7 +130,7 @@ impl PersonScreen { result.recording_list.clear_selection(); let navigator = result.navigator.borrow().clone(); if let Some(navigator) = navigator { - navigator.push(WorkScreen::new(result.backend.clone(), work.clone())); + navigator.push(WorkScreen::new(result.backend.clone(), &result.window, work.clone())); } })); @@ -141,23 +140,35 @@ impl PersonScreen { result.work_list.clear_selection(); let navigator = result.navigator.borrow().clone(); if let Some(navigator) = navigator { - navigator.push(RecordingScreen::new(result.backend.clone(), recording.clone())); + navigator.push(RecordingScreen::new(result.backend.clone(), &result.window, recording.clone())); } })); + edit_action.connect_activate(clone!(@strong result => move |_, _| { + PersonEditor::new(result.backend.clone(), &result.window, Some(result.person.clone()), |_| {}).show(); + })); + + delete_action.connect_activate(clone!(@strong result => move |_, _| { + let context = glib::MainContext::default(); + let clone = result.clone(); + context.spawn_local(async move { + clone.backend.db().delete_person(clone.person.id).await.unwrap(); + }); + })); + let context = glib::MainContext::default(); let clone = result.clone(); context.spawn_local(async move { let works = clone .backend .db() - .get_works(person.id as u32) + .get_works(clone.person.id as u32) .await .unwrap(); let recordings = clone .backend .db() - .get_recordings_for_person(person.id as u32) + .get_recordings_for_person(clone.person.id as u32) .await .unwrap(); diff --git a/musicus/src/screens/recording_screen.rs b/musicus/src/screens/recording_screen.rs index ee2bca0..ada6ab5 100644 --- a/musicus/src/screens/recording_screen.rs +++ b/musicus/src/screens/recording_screen.rs @@ -1,8 +1,10 @@ use crate::backend::*; use crate::database::*; +use crate::dialogs::{RecordingEditorDialog, TracksEditor}; use crate::player::*; use crate::widgets::*; use gettextrs::gettext; +use gio::prelude::*; use glib::clone; use gtk::prelude::*; use gtk_macros::get_widget; @@ -12,6 +14,8 @@ use std::rc::Rc; pub struct RecordingScreen { backend: Rc, + window: gtk::Window, + recording: Recording, widget: gtk::Box, stack: gtk::Stack, tracks: RefCell>, @@ -19,13 +23,15 @@ pub struct RecordingScreen { } impl RecordingScreen { - pub fn new(backend: Rc, recording: Recording) -> Rc { + pub fn new(backend: Rc, window: &W, recording: Recording) -> Rc + where + W: IsA, + { let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/recording_screen.ui"); get_widget!(builder, gtk::Box, widget); get_widget!(builder, libhandy::HeaderBar, header); get_widget!(builder, gtk::Button, back_button); - get_widget!(builder, gtk::MenuButton, menu_button); get_widget!(builder, gtk::Stack, stack); get_widget!(builder, gtk::Frame, frame); get_widget!(builder, gtk::Button, add_to_playlist_button); @@ -33,82 +39,61 @@ impl RecordingScreen { header.set_title(Some(&recording.work.get_title())); header.set_subtitle(Some(&recording.get_performers())); - let edit_menu_item = gio::MenuItem::new(Some(&gettext("Edit recording")), None); - edit_menu_item.set_action_and_target_value( - Some("win.edit-recording"), - Some(&glib::Variant::from(recording.id)), - ); + let edit_action = gio::SimpleAction::new("edit", None); + let delete_action = gio::SimpleAction::new("delete", None); + let edit_tracks_action = gio::SimpleAction::new("edit-tracks", None); + let delete_tracks_action = gio::SimpleAction::new("delete-tracks", None); - let delete_menu_item = gio::MenuItem::new(Some(&gettext("Delete recording")), None); - delete_menu_item.set_action_and_target_value( - Some("win.delete-recording"), - Some(&glib::Variant::from(recording.id)), - ); + let actions = gio::SimpleActionGroup::new(); + actions.add_action(&edit_action); + actions.add_action(&delete_action); + actions.add_action(&edit_tracks_action); + actions.add_action(&delete_tracks_action); - let edit_tracks_menu_item = gio::MenuItem::new(Some(&gettext("Edit tracks")), None); - edit_tracks_menu_item.set_action_and_target_value( - Some("win.edit-tracks"), - Some(&glib::Variant::from(recording.id)), - ); + widget.insert_action_group("widget", Some(&actions)); - let delete_tracks_menu_item = gio::MenuItem::new(Some(&gettext("Delete tracks")), None); - delete_tracks_menu_item.set_action_and_target_value( - Some("win.delete-tracks"), - Some(&glib::Variant::from(recording.id)), - ); - - let menu = gio::Menu::new(); - menu.append_item(&edit_menu_item); - menu.append_item(&delete_menu_item); - menu.append_item(&edit_tracks_menu_item); - menu.append_item(&delete_tracks_menu_item); - - menu_button.set_menu_model(Some(&menu)); - - let recording = Rc::new(recording); let list = List::new(&gettext("No tracks found.")); - - list.set_make_widget( - clone!(@strong recording => move |track: &Track| { - let mut title_parts = Vec::::new(); - for part in &track.work_parts { - title_parts.push(recording.work.parts[*part].title.clone()); - } - - let title = if title_parts.is_empty() { - gettext("Unknown") - } else { - title_parts.join(", ") - }; - - let title_label = gtk::Label::new(Some(&title)); - title_label.set_ellipsize(pango::EllipsizeMode::End); - title_label.set_halign(gtk::Align::Start); - - let file_name_label = gtk::Label::new(Some(&track.file_name)); - file_name_label.set_ellipsize(pango::EllipsizeMode::End); - file_name_label.set_opacity(0.5); - file_name_label.set_halign(gtk::Align::Start); - - let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0); - vbox.set_border_width(6); - vbox.add(&title_label); - vbox.add(&file_name_label); - - vbox.upcast() - }), - ); - frame.add(&list.widget); let result = Rc::new(Self { backend, + window: window.clone().upcast(), + recording, widget, stack, tracks: RefCell::new(Vec::new()), navigator: RefCell::new(None), }); + list.set_make_widget(clone!(@strong result => move |track: &Track| { + let mut title_parts = Vec::::new(); + for part in &track.work_parts { + title_parts.push(result.recording.work.parts[*part].title.clone()); + } + + let title = if title_parts.is_empty() { + gettext("Unknown") + } else { + title_parts.join(", ") + }; + + let title_label = gtk::Label::new(Some(&title)); + title_label.set_ellipsize(pango::EllipsizeMode::End); + title_label.set_halign(gtk::Align::Start); + + let file_name_label = gtk::Label::new(Some(&track.file_name)); + file_name_label.set_ellipsize(pango::EllipsizeMode::End); + file_name_label.set_opacity(0.5); + file_name_label.set_halign(gtk::Align::Start); + + let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0); + vbox.set_border_width(6); + vbox.add(&title_label); + vbox.add(&file_name_label); + + vbox.upcast() + })); + back_button.connect_clicked(clone!(@strong result => move |_| { let navigator = result.navigator.borrow().clone(); if let Some(navigator) = navigator { @@ -116,22 +101,49 @@ impl RecordingScreen { } })); - add_to_playlist_button.connect_clicked( - clone!(@strong result, @strong recording => move |_| { - if let Some(player) = result.backend.get_player() { - player.add_item(PlaylistItem { - recording: (*recording).clone(), - tracks: result.tracks.borrow().clone(), - }).unwrap(); - } - }), - ); + add_to_playlist_button.connect_clicked(clone!(@strong result => move |_| { + if let Some(player) = result.backend.get_player() { + player.add_item(PlaylistItem { + recording: result.recording.clone(), + tracks: result.tracks.borrow().clone(), + }).unwrap(); + } + })); + + edit_action.connect_activate(clone!(@strong result => move |_, _| { + RecordingEditorDialog::new(result.backend.clone(), &result.window, Some(result.recording.clone())).show(); + })); + + delete_action.connect_activate(clone!(@strong result => move |_, _| { + let context = glib::MainContext::default(); + let clone = result.clone(); + context.spawn_local(async move { + clone.backend.db().delete_recording(clone.recording.id).await.unwrap(); + }); + })); + + edit_tracks_action.connect_activate(clone!(@strong result => move |_, _| { + TracksEditor::new(result.backend.clone(), &result.window, Some(result.recording.clone()), result.tracks.borrow().clone()).show(); + })); + + delete_tracks_action.connect_activate(clone!(@strong result => move |_, _| { + let context = glib::MainContext::default(); + let clone = result.clone(); + context.spawn_local(async move { + clone.backend.db().delete_tracks(clone.recording.id).await.unwrap(); + }); + })); let context = glib::MainContext::default(); let clone = result.clone(); - let id = recording.id; context.spawn_local(async move { - let tracks = clone.backend.db().get_tracks(id as u32).await.unwrap(); + let tracks = clone + .backend + .db() + .get_tracks(clone.recording.id) + .await + .unwrap(); + list.show_items(tracks.clone()); clone.stack.set_visible_child_name("content"); clone.tracks.replace(tracks); diff --git a/musicus/src/screens/work_screen.rs b/musicus/src/screens/work_screen.rs index f417c6b..496b814 100644 --- a/musicus/src/screens/work_screen.rs +++ b/musicus/src/screens/work_screen.rs @@ -1,8 +1,10 @@ use super::*; use crate::backend::*; use crate::database::*; +use crate::dialogs::WorkEditorDialog; use crate::widgets::*; use gettextrs::gettext; +use gio::prelude::*; use glib::clone; use gtk::prelude::*; use gtk_macros::get_widget; @@ -12,6 +14,8 @@ use std::rc::Rc; pub struct WorkScreen { backend: Rc, + window: gtk::Window, + work: Work, widget: gtk::Box, stack: gtk::Stack, recording_list: Rc>, @@ -19,13 +23,15 @@ pub struct WorkScreen { } impl WorkScreen { - pub fn new(backend: Rc, work: Work) -> Rc { + pub fn new(backend: Rc, window: &W, work: Work) -> Rc + where + W: IsA, + { let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/work_screen.ui"); get_widget!(builder, gtk::Box, widget); get_widget!(builder, libhandy::HeaderBar, header); get_widget!(builder, gtk::Button, back_button); - get_widget!(builder, gtk::MenuButton, menu_button); get_widget!(builder, gtk::SearchEntry, search_entry); get_widget!(builder, gtk::Stack, stack); get_widget!(builder, gtk::Frame, recording_frame); @@ -33,23 +39,14 @@ impl WorkScreen { header.set_title(Some(&work.title)); header.set_subtitle(Some(&work.composer.name_fl())); - let edit_menu_item = gio::MenuItem::new(Some(&gettext("Edit work")), None); - edit_menu_item.set_action_and_target_value( - Some("win.edit-work"), - Some(&glib::Variant::from(work.id)), - ); + let edit_action = gio::SimpleAction::new("edit", None); + let delete_action = gio::SimpleAction::new("delete", None); - let delete_menu_item = gio::MenuItem::new(Some(&gettext("Delete work")), None); - delete_menu_item.set_action_and_target_value( - Some("win.delete-work"), - Some(&glib::Variant::from(work.id)), - ); + let actions = gio::SimpleActionGroup::new(); + actions.add_action(&edit_action); + actions.add_action(&delete_action); - let menu = gio::Menu::new(); - menu.append_item(&edit_menu_item); - menu.append_item(&delete_menu_item); - - menu_button.set_menu_model(Some(&menu)); + widget.insert_action_group("widget", Some(&actions)); let recording_list = List::new(&gettext("No recordings found.")); @@ -82,6 +79,8 @@ impl WorkScreen { let result = Rc::new(Self { backend, + window: window.clone().upcast(), + work, widget, stack, recording_list, @@ -104,17 +103,29 @@ impl WorkScreen { .set_selected(clone!(@strong result => move |recording| { let navigator = result.navigator.borrow().clone(); if let Some(navigator) = navigator { - navigator.push(RecordingScreen::new(result.backend.clone(), recording.clone())); + navigator.push(RecordingScreen::new(result.backend.clone(), &result.window, recording.clone())); } })); + edit_action.connect_activate(clone!(@strong result => move |_, _| { + WorkEditorDialog::new(result.backend.clone(), &result.window, Some(result.work.clone())).show(); + })); + + delete_action.connect_activate(clone!(@strong result => move |_, _| { + let context = glib::MainContext::default(); + let clone = result.clone(); + context.spawn_local(async move { + clone.backend.db().delete_work(clone.work.id).await.unwrap(); + }); + })); + let context = glib::MainContext::default(); let clone = result.clone(); context.spawn_local(async move { let recordings = clone .backend .db() - .get_recordings_for_work(work.id as u32) + .get_recordings_for_work(clone.work.id as u32) .await .unwrap(); diff --git a/musicus/src/window.rs b/musicus/src/window.rs index ec85095..9d16f0b 100644 --- a/musicus/src/window.rs +++ b/musicus/src/window.rs @@ -161,10 +161,10 @@ impl Window { result.leaflet.set_visible_child(&result.navigator.widget); match poe { PersonOrEnsemble::Person(person) => { - result.navigator.clone().replace(PersonScreen::new(result.backend.clone(), person.clone())); + result.navigator.clone().replace(PersonScreen::new(result.backend.clone(), &result.window, person.clone())); } PersonOrEnsemble::Ensemble(ensemble) => { - result.navigator.clone().replace(EnsembleScreen::new(result.backend.clone(), ensemble.clone())); + result.navigator.clone().replace(EnsembleScreen::new(result.backend.clone(), &result.window, ensemble.clone())); } } }));