1. Project Repository
2. Standard UI Elements
2.1. Global Separated Layout
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { top_menu(ctx); self.folder_col.view(ctx); self.scripts_col.view(ctx); }
2.2. Top Menu
pub fn top_menu(ctx: &egui::Context) { egui::TopBottomPanel::top("top_panel").show(ctx, |ui| { // The top panel is often a good place for a menu bar: egui::MenuBar::new().ui(ui, |ui| { // NOTE: no File->Quit on web pages! let is_web = cfg!(target_arch = "wasm32"); if !is_web { ui.menu_button("File", |ui| { if ui.button("Quit").clicked() { ctx.send_viewport_cmd(egui::ViewportCommand::Close); } }); ui.add_space(16.0); } // egui::widgets::g lobal_theme_preference_buttons(ui); }); }); }
2.3. Left sizable column:
2.3.1. Left Panel
pub fn view(&self, ctx: &egui::Context) { egui::SidePanel::left("Folders Panel") .resizable(true) .default_width(300.0) .width_range(200.0..=600.0) .show(ctx, |ui| { ui.label("Scripts Folders"); ui.separator(); Self::add_folder_button(ui); ui.add_space(10.0); Self::folders(ui); }); }
2.3.2. Loop a list of Struct
fn folders(ui: &mut Ui) { egui::ScrollArea::vertical().show(ui, |ui| { crate::with_folder_state(|state| { let folders_vec = (*state.folder_list.read().unwrap()).clone(); let selected_id = *state.selected_folder_id.read().unwrap(); let rename_folder = state.folder_to_rename.read().unwrap().as_ref().cloned(); let rename_text = state.rename_text.read().unwrap().as_ref().cloned(); if folders_vec.is_empty() { ui.label("No folders yet..."); } else { for folder in &*folders_vec { let is_renaming = rename_folder .as_ref() .map(|f| f.id == folder.id) .unwrap_or(false); let display_name = if is_renaming { rename_text.as_ref().unwrap_or(&folder.name) } else { &folder.name }; let mut folder_item = FolderItem::new(folder, selected_id, display_name); folder_item.view(ui); } } }); }); }
The highlighted is the egui-way modularize part of the code into a component. We explain more detail of folder item in section 2.4. Component with Local State.
2.4. Component with Local State
We break down the component into several sections:
2.4.1. flex-1 clickable button with indicator as "selected label"
struct FolderItem<'a> { folder: &'a crate::prisma::scripts_folder::Data, selected_id: Option<i32>, display_name: &'a str, } impl<'a> FolderItem<'a> { fn view(&mut self, ui: &mut egui::Ui) { ui.horizontal(|ui| { let is_selected = self.selected_id == Some(self.folder.id); // Calculate space for label (available width minus estimated menu space) let available_width = ui.available_width(); let dots_menu_width = 40.0; // Estimate for menu button let label_width = (available_width - dots_menu_width).max(0.0); // Make label expand to fill calculated space ui.add_sized( [label_width, ui.available_height() + 5.0], |ui: &mut egui::Ui| { let response = ui.selectable_label(is_selected, self.display_name); if response.clicked() { dispatch_folder_command(FolderCommand::SelectFolder { folder_id: self.folder.id, }); } response }, ); self.dots_menu(ui, self.folder); }); } }
2.4.2. Dots menu
1struct FolderItem<'a> { 2 folder: &'a crate::prisma::scripts_folder::Data, 3 selected_id: Option<i32>, 4 display_name: &'a str, 5} 6 7impl<'a> FolderItem<'a> { 8 fn dots_menu(&mut self, ui: &mut egui::Ui, folder: &crate::prisma::scripts_folder::Data) { 9 let (delete_folder, rename_folder) = crate::with_folder_state(|state| { 10 let delete_folder = state.folder_to_delete.read().unwrap().as_ref().cloned(); 11 let rename_folder = state.folder_to_rename.read().unwrap().as_ref().cloned(); 12 (delete_folder, rename_folder) 13 }); 14 15 ui.menu_button("...", |ui| { 16 if ui 17 .add_sized([120.0, 20.0], |ui: &mut egui::Ui| { 18 ui.button("Rename Folder") 19 }) 20 .clicked() 21 { 22 let folder_ = Arc::new(folder.clone()); 23 crate::with_folder_state(|state| { 24 *state.folder_to_rename.write().unwrap() = Some(folder_.clone()); 25 *state.rename_text.write().unwrap() = Some(folder_.name.clone()); 26 }); 27 } 28 if ui 29 .add_sized([120.0, 20.0], |ui: &mut egui::Ui| { 30 ui.button("Delete Folder") 31 }) 32 .clicked() 33 { 34 let folder_ = Arc::new(folder.clone()); 35 crate::with_folder_state(|state| { 36 *state.folder_to_delete.write().unwrap() = Some(folder_); 37 }); 38 } 39 });
2.4.3. Confirm Delete Dialog
40 // Show delete confirmation if this folder is selected for deletion 41 if let Some(folder_) = delete_folder 42 && folder_.id == folder.id 43 { 44 egui::Window::new("Confirm Delete") 45 .collapsible(false) 46 .resizable(false) 47 .anchor(egui::Align2::CENTER_CENTER, egui::Vec2::ZERO) 48 .show(ui.ctx(), |ui| { 49 ui.label(format!( 50 "Are you sure you want to delete this folder: {}?", 51 folder.name 52 )); 53 ui.add_space(20.0); 54 ui.horizontal(|ui| { 55 if ui.button("Cancel").clicked() { 56 crate::with_folder_state(|state| { 57 *state.folder_to_delete.write().unwrap() = None; 58 }); 59 } 60 if ui.button("Delete").clicked() { 61 dispatch_folder_command(FolderCommand::DeleteFolder { 62 folder_id: folder.id, 63 }); 64 crate::with_folder_state(|state| { 65 *state.folder_to_delete.write().unwrap() = None; 66 }); 67 } 68 }); 69 }); 70 }
2.4.4. Rename Folder Dialog
71 if let Some(folder_) = rename_folder 72 && folder_.id == folder.id 73 { 74 egui::Window::new("Rename Folder") 75 .collapsible(false) 76 .resizable(false) 77 .anchor(egui::Align2::CENTER_CENTER, egui::Vec2::ZERO) 78 .show(ui.ctx(), |ui| { 79 ui.label("Input new folder name:"); 80 ui.add_space(10.0); 81 let mut text = crate::with_folder_state(|state| { 82 state 83 .rename_text 84 .read() 85 .unwrap() 86 .as_ref() 87 .cloned() 88 .unwrap_or_default() 89 }); 90 ui.text_edit_singleline(&mut text); 91 crate::with_folder_state(|state| { 92 *state.rename_text.write().unwrap() = Some(text.clone()); 93 }); 94 ui.add_space(20.0); 95 ui.horizontal(|ui| { 96 if ui.button("Cancel").clicked() { 97 crate::with_folder_state(|state| { 98 *state.folder_to_rename.write().unwrap() = None; 99 *state.rename_text.write().unwrap() = None; 100 }); 101 } 102 if ui.button("Rename").clicked() { 103 dispatch_folder_command(FolderCommand::RenameFolder { 104 folder_id: folder_.id, 105 new_name: text, 106 }); 107 crate::with_folder_state(|state| { 108 *state.folder_to_rename.write().unwrap() = None; 109 *state.rename_text.write().unwrap() = None; 110 }); 111 } 112 }); 113 }); 114 } 115 } 116}
2.5. Right column (Automatically Resizable)
pub fn scripts_col(ctx: &egui::Context) { egui::CentralPanel::default().show(ctx, |ui| { ui.add_space(-6.0); // Reduce top padding ui.label("Scripts"); ui.separator(); // Example 1: Using Frame with uniform margin egui::Frame::new() .inner_margin(16.0) // Same margin on all sides .show(ui, |ui| { ui.label("This is inside a Frame with 16px margin on all sides"); }); ui.add_space(10.0); ... }); }
2.6. Div like Element
This acts like div with display flex:
egui::Frame::new() .fill(egui::Color32::from_rgb(240, 240, 240)) // Light gray background, like a div .stroke(egui::Stroke::new( 1.0, egui::Color32::from_rgb(200, 200, 200), )) // Subtle border .corner_radius(4.0) // Rounded corners .inner_margin(8.0) // Padding inside the frame .show(ui, |ui| { ui.horizontal_wrapped(|ui| { ui.spacing_mut().item_spacing.x = 0.0; ui.label("This demo showcases how to use "); ui.code("Ui::response"); ui.label(" to create interactive container widgets that may contain other widgets."); }); });
2.7. Theme-aware Div
Frame::canvas(ui.style()) .fill(visuals.bg_fill.gamma_multiply(0.3)) .stroke(visuals.bg_stroke) .inner_margin(ui.spacing().menu_margin) .show(ui, |ui| { ui.set_width(ui.available_width()); ui.add_space(32.0); ui.vertical_centered(|ui| { Label::new( RichText::new(format!("{}", self.count)) .color(text_color) .size(32.0), ) .selectable(false) .ui(ui); }); ui.add_space(32.0); ui.horizontal(|ui| { if ui.button("Reset").clicked() { self.count = 0; } if ui.button("+ 100").clicked() { self.count += 100; } }); });
2.8. Group
ui.group(|ui| { ui.label("This is inside a group() - has background and padding"); });
2.9. Frame with Border Radius
egui::Frame::new() .fill(ui.visuals().window_fill()) .stroke(ui.visuals().window_stroke()) .corner_radius(4.0) .inner_margin(12.0) .show(ui, |ui| { ui.label("Frame with background, border, rounded corners, and 12px margin"); }); ui.add_space(10.0);
2.10. Frame with Margin
egui::Frame::new() .inner_margin(16.0) // Same margin on all sides .show(ui, |ui| { ui.label("This is inside a Frame with 16px margin on all sides"); }); ui.add_space(10.0);
2.11. Space-between Layout
frame.show(ui, |ui| { ui.horizontal(|ui| { ui.label(format!("Name: {}", script.name)); if ui.button("Rename").clicked() { self.renaming_script_id = Some(script.id); self.renaming_name = script.name.clone(); } ui.with_layout( egui::Layout::right_to_left(egui::Align::Center), |ui| { if ui.button("Execute").clicked() { // Execute the script command crate::run_terminal_command(script.command.clone()); } if ui.button("Edit").clicked() { self.editing_script_id = Some(script.id); self.editing_command = script.command.clone(); } if ui.button("Copy").clicked() { ui.ctx().copy_text(script.command.clone()); } }, ); }); } )
2.12. Text Editor
fn edit_script_window(&mut self, ui: &mut Ui, script_id: i32) { egui::Window::new("Edit Script") .collapsible(false) .resizable(true) .default_height(400.0) .default_width(600.0) .anchor(egui::Align2::CENTER_CENTER, egui::Vec2::ZERO) .show(ui.ctx(), |ui| { ui.add( egui::TextEdit::multiline(&mut self.editing_command) .font(egui::TextStyle::Monospace) .code_editor() .desired_rows(20) .desired_width(580.0), ); ui.add_space(20.0); ui.horizontal(|ui| { if ui.button("Cancel").clicked() { self.editing_script_id = None; } if ui.button("Save").clicked() { dispatch_folder_command(FolderCommand::UpdateScript { script_id, new_command: self.editing_command.clone(), }); self.editing_script_id = None; } }); }); }































