loopsrusteguieframe

egui::Ui::button().clicked isn't working in a for loop


I'm trying to create remove folder button in my app. But ui.button(text).clicked isn't working in a for loop. "Remove" buttons are in each folder.

use eframe::{App, egui, Frame, NativeOptions, run_native};
use eframe::egui::{CentralPanel, Color32, Context, SidePanel, Visuals};
use crate::egui::RichText;

struct MyApp { add_folder_string: Vec<String>, folders: Vec<String> }

impl MyApp {
    fn new(cc: &eframe::CreationContext<'_>) -> Self {
        Self { add_folder_string: Vec::new(), folders: Vec::new() }
    }
}

impl App for MyApp {
    fn update(&mut self, ctx: &Context, frame: &mut Frame) {
        SidePanel::left("LeftPanel").show(ctx, |ui| {
            ctx.set_visuals(Visuals { panel_fill: Color32::from_rgb(111, 255, 203), ..Visuals::default() });
            ui.visuals_mut().extreme_bg_color = Color32::from_rgb(255, 255, 255);
            if self.folders.len() > 0 {
                for i in 0..self.folders.len() {
                    ui.group(|ui| {
                        ui.label(self.folders[i].clone());
                        if ui.button("Remove").clicked() { self.folders.remove(i); }
                    });
                }
            }
            if ui.button("➕").clicked() {
                self.folders.push(String::from("New Folder"));
            }
        });
    }
}

I can remove the last folder I created but trying to remove the ones before that are causing panic

I tried to create a new Vec in MyApp struct and store the bools in it and remove the folders in another loop like this:

use eframe::{App, egui, Frame, NativeOptions, run_native};
use eframe::egui::{CentralPanel, Color32, Context, SidePanel, Visuals};
use crate::egui::RichText;

struct MyApp { add_folder_string: Vec<String>, folders: Vec<String>, remove_folder: Vec<bool> }

impl MyApp {
    fn new(cc: &eframe::CreationContext<'_>) -> Self {
        Self { add_folder_string: Vec::new(), folders: Vec::new(), remove_folder: Vec::new() }
    }
}

impl App for MyApp {
    fn update(&mut self, ctx: &Context, frame: &mut Frame) {
        SidePanel::left("LeftPanel").show(ctx, |ui| {
            ctx.set_visuals(Visuals { panel_fill: Color32::from_rgb(111, 255, 203), ..Visuals::default() });
            ui.visuals_mut().extreme_bg_color = Color32::from_rgb(255, 255, 255);
            self.remove_folder.resize(self.folders.len(), false);
            if self.folders.len() > 0 {
                for i in 0..self.folders.len() {
                    ui.group(|ui| {
                        ui.label(self.folders[i].clone());
                        if ui.button("Remove").clicked() { self.remove_folder[i] = true }
                    });
                }
                if self.remove_folder.contains(&true) {
                    for i in 0..self.remove_folder.len() {
                        if self.remove_folder[i] {
                            self.folders.remove(i);
                            self.remove_folder.remove(i);
                        }
                    }
                }
            }
            if ui.button("➕").clicked() {
                self.folders.push(String::from("New Folder"));
            }
        });
    }
}

But this does the same thing I guess. I don't know why button().clicked "isn't working" in for loop


Solution

  • You could just store the indices of what to remove and then remove them rather than having 2 parallel Vecs with still all the troubles of iterating while removing:

        fn update(&mut self, ctx: &Context, frame: &mut Frame) {
            SidePanel::left("LeftPanel").show(ctx, |ui| {
                ctx.set_visuals(Visuals { panel_fill: Color32::from_rgb(111, 255, 203), ..Visuals::default() });
                ui.visuals_mut().extreme_bg_color = Color32::from_rgb(255, 255, 255);
                let mut remove = Vec::new();
                for (i, folder) in self.folders.iter().enumerate() {
                    ui.group(|ui| {
                        ui.label(folder.clone());
                        if ui.button("Remove").clicked() {
                            remove.push(i)
                        }
                    });
                }
                
                // need to remove in reverse to not mess up later indices
                for i in remove.into_iter().rev() {
                    self.folders.remove(i);
                }
                if ui.button("➕").clicked() {
                    self.folders.push(String::from("New Folder"));
                }
            });
        }