emilk / emilk/egui

Multi window Application makes window not responding when button clicked

Open
#5,835 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Rust
Stars
30.6k
Forks
2.1k
Avg merge
1d 9h
Merged PRs (30d)
72

Description

Hello Good day, i am working on a library based on eframe/egui to integrate GUI into my custom language, the aim i am trying to achieve is to create multi window ability where user can create a multiple form like form1, form2 and etc by calling createform just like winform. show_form is used for showing form which is equivalent to winform show(), runapp is use for starting the application which is equivalent to winform Application.run. Now when i tried to run this script:

import gui

function onclick()
    gui.showform(form2)
end function

set form to gui.form("This is First", 500, 500)

set label to gui.label(form, "This is test", 200, 30, 200, 50, false)
gui.setbackcolor(label, "blue")
gui.setfontname(label, "Algerian")
gui.settextalignment(label, "center")
gui.setpadding(label, 20, 0, 0, 0)

set button to gui.button(form, "Show Form 2", onclick, 250, 80)
gui.setautosize(button, false)
gui.settextalignment(button, "center")

set form2 to gui.form("This is First", 500, 500)
//gui.showform(form2)
gui.runapp(form)

if i click the button to it can show the form it will just freeze/hook and become unresponsive i.e the form1 which is the main entry, but i try that gui.showform that is outside onclick it will just popup and go down when the app started, i have tried many trial to solve the issue but can't find solution. Please help review and solve thank you, i have been on this thing for almost five days now. Below are code related to the form from the library

lazy_static::lazy_static! {
    static ref COMMAND_QUEUE: Mutex<VecDeque<Command>> = Mutex::new(VecDeque::new());
    static ref FORMS: RwLock<HashMap<String, FormSettings>> = RwLock::new(HashMap::new());
    static ref CONTROLS: RwLock<HashMap<String, ControlSettings>> = RwLock::new(HashMap::new());
    static ref EXIT_SENDER: RwLock<Option<Sender<()>>> = RwLock::new(None);
    static ref TEXT_UPDATE_SENDERS: RwLock<HashMap<String, Sender<(String, String)>>> = RwLock::new(HashMap::new());
    static ref MSGBOX_RESULT: RwLock<Option<(String, String)>> = RwLock::new(None); 
}
#[derive(Clone, Debug)]
enum DockStyle {
    None,
    Top,
    Bottom,
    Left,
    Right,
    Fill,
}

#[derive(Clone, Debug)]
struct FormSettings {
    title: String,
    width: f32,
    height: f32,
    visible: bool,
    bg_color: Color32,
    maximized: bool,
    fullscreen: bool,
    startposition: String, // e.g., "centerscreen" or "manual"
    resizable: bool,
    position: Option<(f32, f32)>, // For manual positioning
    border: bool,
    controls_order: Vec<String>,
}

// Create Form 
pub fn createform(args: Vec<Value>) -> Result<Value, String> {
    if args.len() != 3 {
        return Err(format!("createform() expects 3 arguments, got {}", args.len()));
    }

    let title = match &args[0] {
        Value::String(s) => s.clone(),
        _ => return Err("createform() expects a string for title".to_string()),
    };
    let width = match &args[1] {
        Value::Number(n) => *n as f32,
        _ => return Err("createform() expects a number for width".to_string()),
    };
    let height = match &args[2] {
        Value::Number(n) => *n as f32,
        _ => return Err("createform() expects a number for height".to_string()),
    };

    let form_id = Uuid::new_v4().to_string();
    let settings = FormSettings {
        title,
        width,
        height,
        visible: false,
        bg_color: Color32::from_rgb(230, 230, 230), // Light gray background
        maximized: false,
        fullscreen: false,
        startposition: "centerscreen".to_string(),
        resizable: true,
        position: None,
        border: true,
        controls_order: Vec::new(),
    };
    
    println!("Created form {} with visible: {}", form_id, settings.visible);
    FORMS.write().unwrap().insert(form_id.clone(), settings);
    Ok(Value::FormObject(form_id))
}

pub fn runapp(args: Vec<Value>) -> Result<Value, String> {
    if args.len() != 1 {
        return Err(format!("runapp() expects 1 argument, got {}", args.len()));
    }
    let form_id = match &args[0] {
        Value::FormObject(id) => id.clone(),
        _ => return Err("runapp() expects a Form identifier".to_string()),
    };

    // Fix: Set the main form to visible
    {
        let mut forms = FORMS.write().unwrap();
        if let Some(settings) = forms.get_mut(&form_id) {
            settings.visible = true;  // Added to ensure the main form is visible
        } else {
            return Err("Form not found".to_string());
        }
    }

    let (exit_tx, exit_rx) = channel();
    {
        let mut exit_sender = EXIT_SENDER.write().unwrap();
        *exit_sender = Some(exit_tx);
    }

    let forms = FORMS.read().unwrap();
    let form_settings = forms.get(&form_id)
        .ok_or("Form not found".to_string())?
        .clone();

    let mut viewport = egui::ViewportBuilder::default()
        .with_maximized(form_settings.maximized)
        .with_fullscreen(form_settings.fullscreen)
        .with_resizable(form_settings.resizable)
        .with_decorations(form_settings.border);

    if !form_settings.maximized && !form_settings.fullscreen {
        viewport = viewport.with_inner_size([form_settings.width, form_settings.height]);
    }

    if form_settings.startposition == "manual" {
        if let Some((x, y)) = form_settings.position {
            viewport = viewport.with_position([x, y]);
        }
    }

    let options = eframe::NativeOptions {
        centered: true,
        viewport,
        ..Default::default()
    };

    println!(
        "Launching app - Maximized: {}, Fullscreen: {}, Border: {}, Size: {:?}",
        form_settings.maximized,
        form_settings.fullscreen,
        form_settings.border,
        if form_settings.maximized || form_settings.fullscreen {
            "Max/Full".to_string()
        } else {
            format!("{}x{}", form_settings.width, form_settings.height)
        }
    );

    eframe::run_native(
        &form_settings.title,
        options,
        Box::new(|cc| Ok(Box::new(MyApp::new(form_id.clone(), exit_rx, cc)))),
    )
    .map_err(|e| format!("Failed to run app: {}", e))?;

    {
        let mut exit_sender = EXIT_SENDER.write().unwrap();
        *exit_sender = None;
    }
    Ok(Value::Null)
}

The App

struct MyApp {
    form_id: String,
    exit_rx: Arc<Mutex<Receiver<()>>>,
    textbox_texts: HashMap<String, String>,
    text_update_rx: Receiver<(String, String)>,
    shown_viewports: HashSet<String>,
}

impl MyApp {
    fn new(form_id: String, exit_rx: Receiver<()>, cc: &eframe::CreationContext<'_>) -> Self {
        // Define custom fonts
        let mut fonts = FontDefinitions::default();

        
        let exe_path = std::env::current_exe().expect("Failed to get executable path");
        let exe_dir = exe_path.parent().expect("Failed to get executable directory");

        
        let fonts_dir = exe_dir.join("fonts");

        // List of font files to load from the 'fonts' subfolder
        let font_files = [
            ("SegoeUI", "SegoeUI.ttf"),
            ("SegoeUIBold", "SegoeUIBold.ttf"),
            ("SegoeUIItalic", "SegoeUIItalic.ttf"),
            ("SegoeUIBoldItalic", "SegoeUIBoldItalic.ttf"),
            ("SansSerif", "SansSerif.otf"),
            ("Algerian", "algerian.ttf")
        ];

        
        for (font_name, file_name) in font_files.iter() {
            let font_path = fonts_dir.join(file_name);
            if font_path.exists() {
                match fs::read(&font_path) {
                    Ok(font_data) => {
                        fonts.font_data.insert(
                            font_name.to_string(),
                            FontData::from_owned(font_data).into(),
                        );
                        fonts.families.insert(
                            FontFamily::Name(font_name.to_string().into()),
                            vec![font_name.to_string()],
                        );
                    }
                    Err(e) => eprintln!("Failed to load font {}: {}", font_path.display(), e),
                }
            } else {
                eprintln!("Font file not found: {}", font_path.display());
            }
        }

        // Set the fonts in the egui context
        cc.egui_ctx.set_fonts(fonts);

        let (text_update_tx, text_update_rx) = channel();
        TEXT_UPDATE_SENDERS.write().unwrap().insert(form_id.clone(), text_update_tx);

        Self {
            form_id,
            exit_rx: Arc::new(Mutex::new(exit_rx)),
            textbox_texts: HashMap::new(),
            text_update_rx,
            shown_viewports: HashSet::new(),
            
        }
    }
}

Update function

impl eframe::App for MyApp {
    fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {

        // Check if the application should exit (Receiving Signal)
        if self.exit_rx.lock().unwrap().try_recv().is_ok() {
            ctx.send_viewport_cmd(egui::ViewportCommand::Close);
            return;
        }

        // Clone the forms data and release the lock immediately
        let forms_data = {
            let forms = FORMS.read().unwrap(); // Acquire read lock
            forms.clone()                      // Clone the data
        }; // Lock is released here

        // Render the main form if it’s visible
        if let Some(settings) = forms_data.get(&self.form_id) {
            if settings.visible {
                egui::CentralPanel::default().show(ctx, |ui| {
                    // Autosize and constraint logic
                    let (autosize_controls, constraint_controls): (Vec<_>, Vec<_>) = {
                        let controls = CONTROLS.read().unwrap();
                        let autosize = controls.iter()
                            .filter(|(_, c)| c.form_id == self.form_id && c.autosize)
                            .map(|(id, c)| (id.clone(), c.text.clone(), c.fontname.clone(), c.fontsize, c.forecolor, c.padding))
                            .collect();
                        let constraints = controls.iter()
                            .filter(|(_, c)| c.form_id == self.form_id && c.layout_constraint.is_some())
                            .map(|(id, c)| (id.clone(), c.layout_constraint.clone().unwrap()))
                            .collect();
                        (autosize, constraints)
                    };

                    let autosize_sizes: HashMap<String, (f32, f32)> = autosize_controls.into_iter()
                        .map(|(id, text, fontname, fontsize, forecolor, padding)| {
                            let control = ControlSettings {
                                fontname,
                                fontsize,
                                forecolor,
                                padding,
                                ..Default::default()
                            };
                            let (text_width, text_height) = ui_text_size(ui, &text, &control);
                            let total_width = text_width + padding.0 + padding.2;
                            let total_height = text_height + padding.1 + padding.3;
                            (id, (total_width, total_height))
                        })
                        .collect();

                    {
                        let mut controls = CONTROLS.write().unwrap();
                        for (id, (total_width, total_height)) in &autosize_sizes {
                            if let Some(control) = controls.get_mut(id) {
                                control.width = *total_width;
                                control.height = *total_height;
                            }
                        }
                        let target_data: HashMap<String, (egui::Pos2, f32, f32)> = constraint_controls.iter()
                            .filter_map(|(_, constraint)| match constraint {
                                LayoutConstraint::LeftOf { target_id, .. } |
                                LayoutConstraint::RightOf { target_id, .. } |
                                LayoutConstraint::Above { target_id, .. } |
                                LayoutConstraint::Below { target_id, .. } => {
                                    controls.get(target_id).map(|target| {
                                        (target_id.clone(), (target.position, target.width, target.height))
                                    })
                                }
                            })
                            .collect();
                        for (id, constraint) in &constraint_controls {
                            if let Some(control) = controls.get_mut(id) {
                                match constraint {
                                    LayoutConstraint::LeftOf { target_id, space } => {
                                        if let Some((target_pos, target_width, _)) = target_data.get(target_id) {
                                            control.position.x = target_pos.x - control.width - *space;
                                            control.position.y = target_pos.y;
                                        }
                                    }
                                    LayoutConstraint::RightOf { target_id, space } => {
                                        if let Some((target_pos, target_width, _)) = target_data.get(target_id) {
                                            control.position.x = target_pos.x + *target_width + *space;
                                            control.position.y = target_pos.y;
                                        }
                                    }
                                    LayoutConstraint::Above { target_id, space } => {
                                        if let Some((target_pos, _, target_height)) = target_data.get(target_id) {
                                            control.position.x = target_pos.x;
                                            control.position.y = target_pos.y - control.height - *space;
                                        }
                                    }
                                    LayoutConstraint::Below { target_id, space } => {
                                        if let Some((target_pos, _, target_height)) = target_data.get(target_id) {
                                            control.position.x = target_pos.x;
                                            control.position.y = target_pos.y + *target_height + *space;
                                        }
                                    }
                                }
                            }
                        }
                    }

                    // Gather visible controls for rendering
                    let controls_data = {
                        let controls = CONTROLS.read().unwrap();
                        controls.iter()
                            .filter(|(_, c)| c.form_id == self.form_id && c.visible)
                            .map(|(id, c)| (id.clone(), c.clone()))
                            .collect::<Vec<_>>()
                    };

                    let mut textbox_updates = Vec::new();
                    let form_rect = ui.available_rect_before_wrap();
                    ui.painter().rect_filled(form_rect, 0.0, settings.bg_color);

                    // Render controls
                    for (control_id, control) in controls_data.iter() {
                        let mut pos;
                        let mut size;

                        if matches!(control.dock, DockStyle::None) {
                            pos = control.position + egui::Vec2::new(control.margin.0, control.margin.1);
                            size = egui::Vec2::new(control.width, control.height);
                        } else {
                            let (text_width, text_height) = if control.autosize {
                                ui_text_size(ui, &control.text, control)
                            } else {
                                (0.0, 0.0)
                            };

                            match control.dock {
                                DockStyle::Top => {
                                    let height = if control.autosize { text_height + control.padding.1 + control.padding.3 } else { control.height };
                                    pos = form_rect.min;
                                    size = egui::Vec2::new(form_rect.width(), height);
                                }
                                DockStyle::Bottom => {
                                    let height = if control.autosize { text_height + control.padding.1 + control.padding.3 } else { control.height };
                                    pos = egui::pos2(form_rect.min.x, form_rect.max.y - height);
                                    size = egui::Vec2::new(form_rect.width(), height);
                                }
                                DockStyle::Left => {
                                    let width = if control.autosize { text_width + control.padding.0 + control.padding.2 } else { control.width };
                                    pos = form_rect.min;
                                    size = egui::Vec2::new(width, form_rect.height());
                                }
                                DockStyle::Right => {
                                    let width = if control.autosize { text_width + control.padding.0 + control.padding.2 } else { control.width };
                                    pos = egui::pos2(form_rect.max.x - width, form_rect.min.y);
                                    size = egui::Vec2::new(width, form_rect.height());
                                }
                                DockStyle::Fill => {
                                    pos = form_rect.min;
                                    size = form_rect.size();
                                }
                                DockStyle::None => unreachable!(),
                            }
                        }

                        let rect = egui::Rect::from_min_size(pos, size);

                        match control.control_type.as_str() {
                            "label" => {
                                ui.painter().rect_filled(rect, 0.0, control.backcolor);
                                let inner_rect = egui::Rect {
                                    min: rect.min + egui::Vec2::new(control.padding.0, control.padding.1),
                                    max: rect.max - egui::Vec2::new(control.padding.2, control.padding.3),
                                };
                                let font_family = get_font_family(&control.fontname);
                                let font_id = FontId::new(control.fontsize, font_family);
                                let mut job = LayoutJob::default();
                                job.append(
                                    &control.text,
                                    0.0,
                                    TextFormat {
                                        font_id,
                                        color: control.forecolor,
                                        ..Default::default()
                                    },
                                );
                                let galley = ui.fonts(|f| f.layout_job(job));
                                let text_size = galley.rect.size();
                                let align_x = control.text_alignment[0];
                                let align_y = control.text_alignment[1];
                                let offset_x = match align_x {
                                    Align::Min => 0.0,
                                    Align::Center => (inner_rect.width() - text_size.x) / 2.0,
                                    Align::Max => inner_rect.width() - text_size.x,
                                };
                                let offset_y = match align_y {
                                    Align::Min => 0.0,
                                    Align::Center => (inner_rect.height() - text_size.y) / 2.0,
                                    Align::Max => inner_rect.height() - text_size.y,
                                };
                                let text_pos = inner_rect.min + egui::Vec2::new(offset_x, offset_y);
                                ui.painter().galley(text_pos, galley, control.forecolor);
                            }
                            "button" => {
                                // Updated button rendering with text alignment and padding
                                let mut bg_color = control.backcolor;
                                let response = ui.interact(rect, ui.next_auto_id(), Sense::click());
                                if response.hovered() {
                                    bg_color = Color32::from_rgb(
                                        (bg_color.r() as f32 * 1.1).min(255.0) as u8,
                                        (bg_color.g() as f32 * 1.1).min(255.0) as u8,
                                        (bg_color.b() as f32 * 1.1).min(255.0) as u8,
                                    );
                                }
                                if response.clicked() {
                                    bg_color = Color32::from_rgb(
                                        (bg_color.r() as f32 * 0.9).max(0.0) as u8,
                                        (bg_color.g() as f32 * 0.9).max(0.0) as u8,
                                        (bg_color.b() as f32 * 0.9).max(0.0) as u8,
                                    );
                                }
                                ui.painter().rect_filled(rect, 0.0, bg_color);
                                let inner_rect = Rect {
                                    min: rect.min + Vec2::new(control.padding.0, control.padding.1),
                                    max: rect.max - Vec2::new(control.padding.2, control.padding.3),
                                };
                                let font_id = FontId::new(control.fontsize, get_font_family(&control.fontname));
                                let mut job = LayoutJob::default();
                                job.append(&control.text, 0.0, TextFormat {
                                    font_id,
                                    color: control.forecolor,
                                    ..Default::default()
                                });
                                let galley = ui.fonts(|f| f.layout_job(job));
                                let text_size = galley.rect.size();
                                let align_x = control.text_alignment[0];
                                let align_y = control.text_alignment[1];
                                let offset_x = match align_x {
                                    Align::Min => 0.0,
                                    Align::Center => (inner_rect.width() - text_size.x) / 2.0,
                                    Align::Max => inner_rect.width() - text_size.x,
                                };
                                let offset_y = match align_y {
                                    Align::Min => 0.0,
                                    Align::Center => (inner_rect.height() - text_size.y) / 2.0,
                                    Align::Max => inner_rect.height() - text_size.y,
                                };
                                let text_pos = inner_rect.min + Vec2::new(offset_x, offset_y);
                                ui.painter().galley(text_pos, galley, control.forecolor);
                                if response.clicked() {
                                    if let Some(callback) = &control.callback {
                                        
                                        match callback {
                                            Value::Function { name, params, body, closure, object } => {
                                                let mut interpreter = GLOBAL_INTERPRETER.lock().unwrap();
                                                
                                                let mut local_env = Arc::new(Mutex::new(
                                                    Environment::new(Some(closure.clone()))
                                                ));
                                                if !params.is_empty() {
                                                   
                                                }
                                                if let Some(obj) = object {
                                                    let value = obj.lock().unwrap().clone();
                                                    local_env.lock().unwrap().define("this".to_string(), value);
                                                }
                                                let _ = interpreter.visit_block(body, &mut local_env);
                                               
                                            }
                                            Value::String(s) => {
                                                match s.as_str() {
                                                    "close_msgbox" => {
                                                        let mut forms = FORMS.write().unwrap();
                                                        if let Some(settings) = forms.get_mut(&control.form_id) {
                                                            settings.visible = false;
                                                        }
                                                        
                                                    }
                                                    "close_msgbox_yes" => {
                                                        let mut forms = FORMS.write().unwrap();
                                                        if let Some(settings) = forms.get_mut(&control.form_id) {
                                                            settings.visible = false;
                                                        }
                                                        
                                                    }
                                                    "close_msgbox_no" => {
                                                        let mut forms = FORMS.write().unwrap();
                                                        if let Some(settings) = forms.get_mut(&control.form_id) {
                                                            settings.visible = false;
                                                        }
                                                       
                                                    }
                                                    "close_msgbox_cancel" => {
                                                        let mut forms = FORMS.write().unwrap();
                                                        if let Some(settings) = forms.get_mut(&control.form_id) {
                                                            settings.visible = false;
                                                        }
                                                        
                                                    }
                                                    _ => {}
                                                }
                                            }
                                            _ => {}
                                        }
                                    }
                                }
                                if response.hovered() && !control.cursor.is_empty() {
                                    let cursor_icon = match control.cursor.to_lowercase().as_str() {
                                        "hand" | "pointer" => CursorIcon::PointingHand,
                                        "default" => CursorIcon::Default,
                                        "crosshair" => CursorIcon::Crosshair,
                                        "text" => CursorIcon::Text,
                                        "move" => CursorIcon::Move,
                                        "grab" => CursorIcon::Grab,
                                        "grabbing" => CursorIcon::Grabbing,
                                        _ => CursorIcon::Default,
                                    };
                                    ui.output_mut(|o| o.cursor_icon = cursor_icon);
                                }
                            }
                            "textbox" => {
                                let text = self.textbox_texts.entry(control_id.clone()).or_insert_with(|| control.text.clone());
                                ui.painter().rect_filled(rect, 0.0, control.backcolor);
                                let response = ui.allocate_ui_at_rect(rect, |ui| {
                                    let font_family = get_font_family(&control.fontname);
                                    let font_id = FontId::new(control.fontsize, font_family);
                                    if control.multiline {
                                        egui::ScrollArea::vertical().show(ui, |ui| {
                                            let text_edit = egui::TextEdit::multiline(text)
                                                .text_color(control.forecolor)
                                                .font(font_id)
                                                .frame(false)
                                                .desired_width(control.width);
                                            ui.add_sized(size, text_edit)
                                        }).inner
                                    } else {
                                        let text_edit = egui::TextEdit::singleline(text)
                                            .text_color(control.forecolor)
                                            .font(font_id)
                                            .frame(false)
                                            .desired_width(control.width);
                                        ui.add_sized(size, text_edit)
                                    }
                                });
                                if response.inner.changed() {
                                    textbox_updates.push((control_id.clone(), text.clone()));
                                }
                            }
                            _ => {
                                ui.painter().rect_filled(rect, 0.0, Color32::RED);
                            }
                        }
                    }
                    if !textbox_updates.is_empty() {
                        let mut controls = CONTROLS.write().unwrap();
                        for (control_id, new_text) in textbox_updates {
                            if let Some(control) = controls.get_mut(&control_id) {
                                control.text = new_text;
                            }
                        }
                    }
                });
            }
        }

        // Clean up shown_viewports by removing hidden forms
        self.shown_viewports.retain(|form_id| {
            forms_data.get(form_id).map_or(false, |settings| settings.visible)
        });

        // Schedule additional viewports for visible forms
        for (form_id, settings) in forms_data.iter() {
            
            if settings.visible && form_id != &self.form_id {
                
                if !self.shown_viewports.contains(form_id) {
                    
                    self.shown_viewports.insert(form_id.clone());
                    
                    let form_id_clone = form_id.clone();
                    let settings_clone = settings.clone();
                    let textbox_texts_clone = std::sync::Mutex::new(self.textbox_texts.clone());
                    ctx.show_viewport_deferred(
                        egui::ViewportId::from_hash_of(form_id_clone.clone()),
                        egui::ViewportBuilder::default()
                            .with_title(&settings_clone.title)
                            .with_inner_size([settings_clone.width, settings_clone.height])
                            .with_maximized(settings_clone.maximized)
                            .with_fullscreen(settings_clone.fullscreen)
                            .with_resizable(settings_clone.resizable)
                            .with_decorations(settings_clone.border),
                        move |ctx, _class| {
                            let forms = FORMS.read().unwrap();
                            if let Some(settings) = forms.get(&form_id_clone) {
                                
                                if !settings.visible {
                                    
                                    ctx.send_viewport_cmd(egui::ViewportCommand::Close);
                                    return;
                                }
                                if ctx.input(|i| i.viewport().close_requested()) {
                                    
                                    let mut forms = FORMS.write().unwrap();
                                    if let Some(settings) = forms.get_mut(&form_id_clone) {
                                        settings.visible = false;
                                        
                                    }
                                } else {
                                    egui::CentralPanel::default().show(ctx, |ui| {
                                        
                                        let (autosize_controls, constraint_controls): (Vec<_>, Vec<_>) = {
                                            let controls = CONTROLS.read().unwrap();
                                            let autosize = controls.iter()
                                                .filter(|(_, c)| c.form_id == form_id_clone && c.autosize)
                                                .map(|(id, c)| (id.clone(), c.text.clone(), c.fontname.clone(), c.fontsize, c.forecolor, c.padding))
                                                .collect();
                                            let constraints = controls.iter()
                                                .filter(|(_, c)| c.form_id == form_id_clone && c.layout_constraint.is_some())
                                                .map(|(id, c)| (id.clone(), c.layout_constraint.clone().unwrap()))
                                                .collect();
                                            (autosize, constraints)
                                        };

                                        let autosize_sizes: HashMap<String, (f32, f32)> = autosize_controls.into_iter()
                                            .map(|(id, text, fontname, fontsize, forecolor, padding)| {
                                                let control = ControlSettings {
                                                    fontname,
                                                    fontsize,
                                                    forecolor,
                                                    padding,
                                                    ..Default::default()
                                                };
                                                let (text_width, text_height) = ui_text_size(ui, &text, &control);
                                                let total_width = text_width + padding.0 + padding.2;
                                                let total_height = text_height + padding.1 + padding.3;
                                                (id, (total_width, total_height))
                                            })
                                            .collect();

                                        {
                                            let mut controls = CONTROLS.write().unwrap();
                                            for (id, (total_width, total_height)) in &autosize_sizes {
                                                if let Some(control) = controls.get_mut(id) {
                                                    control.width = *total_width;
                                                    control.height = *total_height;
                                                }
                                            }
                                            let target_data: HashMap<String, (egui::Pos2, f32, f32)> = constraint_controls.iter()
                                                .filter_map(|(_, constraint)| match constraint {
                                                    LayoutConstraint::LeftOf { target_id, .. } |
                                                    LayoutConstraint::RightOf { target_id, .. } |
                                                    LayoutConstraint::Above { target_id, .. } |
                                                    LayoutConstraint::Below { target_id, .. } => {
                                                        controls.get(target_id).map(|target| {
                                                            (target_id.clone(), (target.position, target.width, target.height))
                                                        })
                                                    }
                                                })
                                                .collect();
                                            for (id, constraint) in &constraint_controls {
                                                if let Some(control) = controls.get_mut(id) {
                                                    match constraint {
                                                        LayoutConstraint::LeftOf { target_id, space } => {
                                                            if let Some((target_pos, target_width, _)) = target_data.get(target_id) {
                                                                control.position.x = target_pos.x - control.width - *space;
                                                                control.position.y = target_pos.y;
                                                            }
                                                        }
                                                        LayoutConstraint::RightOf { target_id, space } => {
                                                            if let Some((target_pos, target_width, _)) = target_data.get(target_id) {
                                                                control.position.x = target_pos.x + *target_width + *space;
                                                                control.position.y = target_pos.y;
                                                            }
                                                        }
                                                        LayoutConstraint::Above { target_id, space } => {
                                                            if let Some((target_pos, _, target_height)) = target_data.get(target_id) {
                                                                control.position.x = target_pos.x;
                                                                control.position.y = target_pos.y - control.height - *space;
                                                            }
                                                        }
                                                        LayoutConstraint::Below { target_id, space } => {
                                                            if let Some((target_pos, _, target_height)) = target_data.get(target_id) {
                                                                control.position.x = target_pos.x;
                                                                control.position.y = target_pos.y + *target_height + *space;
                                                            }
                                                        }
                                                    }
                                                }
                                            }
                                        }

                                        let controls_data = {
                                            let controls = CONTROLS.read().unwrap();
                                            controls.iter()
                                                .filter(|(_, c)| c.form_id == form_id_clone && c.visible)
                                                .map(|(id, c)| (id.clone(), c.clone()))
                                                .collect::<Vec<_>>()
                                        };

                                        let mut textbox_updates = Vec::new();
                                        let form_rect = ui.available_rect_before_wrap();
                                        ui.painter().rect_filled(form_rect, 0.0, settings.bg_color);

                                        for (control_id, control) in controls_data.iter() {
                                            let mut pos;
                                            let mut size;

                                            if matches!(control.dock, DockStyle::None) {
                                                pos = control.position + Vec2::new(control.margin.0, control.margin.1);
                                                size = Vec2::new(control.width, control.height);
                                            } else {
                                                let (text_width, text_height) = if control.autosize {
                                                    ui_text_size(ui, &control.text, control)
                                                } else {
                                                    (0.0, 0.0)
                                                };

                                                match control.dock {
                                                    DockStyle::Top => {
                                                        let height = if control.autosize { text_height + control.padding.1 + control.padding.3 } else { control.height };
                                                        pos = form_rect.min;
                                                        size = Vec2::new(form_rect.width(), height);
                                                    }
                                                    DockStyle::Bottom => {
                                                        let height = if control.autosize { text_height + control.padding.1 + control.padding.3 } else { control.height };
                                                        pos = egui::pos2(form_rect.min.x, form_rect.max.y - height);
                                                        size = Vec2::new(form_rect.width(), height);
                                                    }
                                                    DockStyle::Left => {
                                                        let width = if control.autosize { text_width + control.padding.0 + control.padding.2 } else { control.width };
                                                        pos = form_rect.min;
                                                        size = Vec2::new(width, form_rect.height());
                                                    }
                                                    DockStyle::Right => {
                                                        let width = if control.autosize { text_width + control.padding.0 + control.padding.2 } else { control.width };
                                                        pos = egui::pos2(form_rect.max.x - width, form_rect.min.y);
                                                        size = Vec2::new(width, form_rect.height());
                                                    }
                                                    DockStyle::Fill => {
                                                        pos = form_rect.min;
                                                        size = form_rect.size();
                                                    }
                                                    DockStyle::None => unreachable!(),
                                                }
                                            }

                                            let rect = Rect::from_min_size(pos, size);

                                            match control.control_type.as_str() {
                                                "label" => {
                                                    ui.painter().rect_filled(rect, 0.0, control.backcolor);
                                                    let inner_rect = Rect {
                                                        min: rect.min + Vec2::new(control.padding.0, control.padding.1),
                                                        max: rect.max - Vec2::new(control.padding.2, control.padding.3),
                                                    };
                                                    let font_family = get_font_family(&control.fontname);
                                                    let font_id = FontId::new(control.fontsize, font_family);
                                                    let mut job = LayoutJob::default();
                                                    job.append(
                                                        &control.text,
                                                        0.0,
                                                        TextFormat {
                                                            font_id,
                                                            color: control.forecolor,
                                                            ..Default::default()
                                                        },
                                                    );
                                                    let galley = ui.fonts(|f| f.layout_job(job));
                                                    let text_size = galley.rect.size();
                                                    let align_x = control.text_alignment[0];
                                                    let align_y = control.text_alignment[1];
                                                    let offset_x = match align_x {
                                                        Align::Min => 0.0,
                                                        Align::Center => (inner_rect.width() - text_size.x) / 2.0,
                                                        Align::Max => inner_rect.width() - text_size.x,
                                                    };
                                                    let offset_y = match align_y {
                                                        Align::Min => 0.0,
                                                        Align::Center => (inner_rect.height() - text_size.y) / 2.0,
                                                        Align::Max => inner_rect.height() - text_size.y,
                                                    };
                                                    let text_pos = inner_rect.min + Vec2::new(offset_x, offset_y);
                                                    ui.painter().galley(text_pos, galley, control.forecolor);
                                                }
                                                "button" => {
                                                    let mut bg_color = control.backcolor;
                                                    let response = ui.interact(rect, ui.next_auto_id(), Sense::click());
                                                    if response.hovered() {
                                                        bg_color = Color32::from_rgb(
                                                            (bg_color.r() as f32 * 1.1).min(255.0) as u8,
                                                            (bg_color.g() as f32 * 1.1).min(255.0) as u8,
                                                            (bg_color.b() as f32 * 1.1).min(255.0) as u8,
                                                        );
                                                    }
                                                    if response.clicked() {
                                                        bg_color = Color32::from_rgb(
                                                            (bg_color.r() as f32 * 0.9).max(0.0) as u8,
                                                            (bg_color.g() as f32 * 0.9).max(0.0) as u8,
                                                            (bg_color.b() as f32 * 0.9).max(0.0) as u8,
                                                        );
                                                    }
                                                    ui.painter().rect_filled(rect, 0.0, bg_color);
                                                    let inner_rect = Rect {
                                                        min: rect.min + Vec2::new(control.padding.0, control.padding.1),
                                                        max: rect.max - Vec2::new(control.padding.2, control.padding.3),
                                                    };
                                                    let font_id = FontId::new(control.fontsize, get_font_family(&control.fontname));
                                                    let mut job = LayoutJob::default();
                                                    job.append(&control.text, 0.0, TextFormat {
                                                        font_id,
                                                        color: control.forecolor,
                                                        ..Default::default()
                                                    });
                                                    let galley = ui.fonts(|f| f.layout_job(job));
                                                    let text_size = galley.rect.size();
                                                    let align_x = control.text_alignment[0];
                                                    let align_y = control.text_alignment[1];
                                                    let offset_x = match align_x {
                                                        Align::Min => 0.0,
                                                        Align::Center => (inner_rect.width() - text_size.x) / 2.0,
                                                        Align::Max => inner_rect.width() - text_size.x,
                                                    };
                                                    let offset_y = match align_y {
                                                        Align::Min => 0.0,
                                                        Align::Center => (inner_rect.height() - text_size.y) / 2.0,
                                                        Align::Max => inner_rect.height() - text_size.y,
                                                    };
                                                    let text_pos = inner_rect.min + Vec2::new(offset_x, offset_y);
                                                    ui.painter().galley(text_pos, galley, control.forecolor);
                                                    if response.clicked() {
                                                        if let Some(callback) = &control.callback {
                                                            match callback {
                                                                Value::Function { name, params, body, closure, object } => {
                                                                    let mut interpreter = GLOBAL_INTERPRETER.lock().unwrap();
                                                                    let mut local_env = Arc::new(Mutex::new(
                                                                        Environment::new(Some(closure.clone()))
                                                                    ));
                                                                    if !params.is_empty() {
                                                                        
                                                                    }
                                                                    if let Some(obj) = object {
                                                                        let value = obj.lock().unwrap().clone();
                                                                        local_env.lock().unwrap().define("this".to_string(), value);
                                                                    }
                                                                    let _ = interpreter.visit_block(body, &mut local_env);
                                                                }
                                                                Value::String(s) => {
                                                                    match s.as_str() {
                                                                        "close_msgbox" => {
                                                                            let mut forms = FORMS.write().unwrap();
                                                                            if let Some(settings) = forms.get_mut(&control.form_id) {
                                                                                settings.visible = false;
                                                                            }
                                                                            
                                                                        }
                                                                        "close_msgbox_yes" => {
                                                                            let mut forms = FORMS.write().unwrap();
                                                                            if let Some(settings) = forms.get_mut(&control.form_id) {
                                                                                settings.visible = false;
                                                                            }
                                                                            
                                                                        }
                                                                        "close_msgbox_no" => {
                                                                            let mut forms = FORMS.write().unwrap();
                                                                            if let Some(settings) = forms.get_mut(&control.form_id) {
                                                                                settings.visible = false;
                                                                            }
                                                                            
                                                                        }
                                                                        "close_msgbox_cancel" => {
                                                                            let mut forms = FORMS.write().unwrap();
                                                                            if let Some(settings) = forms.get_mut(&control.form_id) {
                                                                                settings.visible = false;
                                                                            }
                                                                            
                                                                        }
                                                                        _ => {}
                                                                    }
                                                                }
                                                                _ => {}
                                                            }
                                                        }
                                                    }
                                                    if response.hovered() && !control.cursor.is_empty() {
                                                        let cursor_icon = match control.cursor.to_lowercase().as_str() {
                                                            "hand" | "pointer" => CursorIcon::PointingHand,
                                                            "default" => CursorIcon::Default,
                                                            "crosshair" => CursorIcon::Crosshair,
                                                            "text" => CursorIcon::Text,
                                                            "move" => CursorIcon::Move,
                                                            "grab" => CursorIcon::Grab,
                                                            "grabbing" => CursorIcon::Grabbing,
                                                            _ => CursorIcon::Default,
                                                        };
                                                        ui.output_mut(|o| o.cursor_icon = cursor_icon);
                                                    }
                                                }
                                                "textbox" => {
                                                    let mut textbox_texts = textbox_texts_clone.lock().unwrap();
                                                    let text = textbox_texts.entry(control_id.clone()).or_insert_with(|| control.text.clone());
                                                    ui.painter().rect_filled(rect, 0.0, control.backcolor);
                                                    let response = ui.allocate_ui_at_rect(rect, |ui| {
                                                        let font_family = get_font_family(&control.fontname);
                                                        let font_id = FontId::new(control.fontsize, font_family);
                                                        if control.multiline {
                                                            egui::ScrollArea::vertical().show(ui, |ui| {
                                                                let text_edit = egui::TextEdit::multiline(text)
                                                                    .text_color(control.forecolor)
                                                                    .font(font_id)
                                                                    .frame(false)
                                                                    .desired_width(control.width);
                                                                ui.add_sized(size, text_edit)
                                                            }).inner
                                                        } else {
                                                            let text_edit = egui::TextEdit::singleline(text)
                                                                .text_color(control.forecolor)
                                                                .font(font_id)
                                                                .frame(false)
                                                                .desired_width(control.width);
                                                            ui.add_sized(size, text_edit)
                                                        }
                                                    });
                                                    if response.inner.changed() {
                                                        textbox_updates.push((control_id.clone(), text.clone()));
                                                    }
                                                }
                                                _ => {
                                                    ui.painter().rect_filled(rect, 0.0, Color32::RED);
                                                }
                                            }
                                        }
                                        if !textbox_updates.is_empty() {
                                            let mut controls = CONTROLS.write().unwrap();
                                            for (control_id, new_text) in textbox_updates {
                                                if let Some(control) = controls.get_mut(&control_id) {
                                                    control.text = new_text;
                                                }
                                            }
                                        }
                                    });
                                }
                            }
                        },
                    );
                } 
            } 
        }
    }
}

Show form code

pub fn show_form(args: Vec<Value>) -> Result<Value, String> {
    if args.len() != 1 {
        return Err(format!("show_form() expects 1 argument, got {}", args.len()));
    }
    let form_id = match &args[0] {
        Value::FormObject(id) => id.clone(),
        _ => return Err("show_form() expects a Form identifier".to_string()),
    };

    let mut forms = FORMS.write().unwrap();
    if let Some(settings) = forms.get_mut(&form_id) {
        settings.visible = true;
        println!("Form {} set to visible", form_id);
        Ok(Value::Null)
    } else {
        Err("Form not found".to_string())
    }
}

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the freeze using the VBnet script and inspect the showform path alongside runapp and MyApp::update in the provided Rust code. Trace how the button callback interacts with eframe::run_native and the form visibility state; done means clicking Show Form 2 displays the second window while the main form remains responsive.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
desktop
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.