1. Project Repository
1. Project Repository
2. Standard UI Elements
2. Standard UI Elements
2.1. Global Separated Layout
2.1. Global Separated Layout
1fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { 2 top_menu(ctx); 3 self.folder_col.view(ctx); 4 self.scripts_col.view(ctx); 5}
2.2. Top Menu
2.2. Top Menu
1pub fn top_menu(ctx: &egui::Context) { 2 egui::TopBottomPanel::top("top_panel").show(ctx, |ui| { 3 // The top panel is often a good place for a menu bar: 4 5 egui::MenuBar::new().ui(ui, |ui| { 6 // NOTE: no File->Quit on web pages! 7 let is_web = cfg!(target_arch = "wasm32"); 8 if !is_web { 9 ui.menu_button("File", |ui| { 10 if ui.button("Quit").clicked() { 11 ctx.send_viewport_cmd(egui::ViewportCommand::Close); 12 } 13 }); 14 ui.add_space(16.0); 15 } 16 17 // egui::widgets::g lobal_theme_preference_buttons(ui); 18 }); 19 }); 20}
2.3. Left sizable column:
2.3. Left sizable column:
2.3.1. Left Panel
2.3.1. Left Panel
1pub fn view(&self, ctx: &egui::Context) { 2 egui::SidePanel::left("Folders Panel") 3 .resizable(true) 4 .default_width(300.0) 5 .width_range(200.0..=600.0) 6 .show(ctx, |ui| { 7 ui.label("Scripts Folders"); 8 9 ui.separator(); 10 11 Self::add_folder_button(ui); 12 13 ui.add_space(10.0); 14 15 Self::folders(ui); 16 }); 17}
2.3.2. Loop a list of Struct
2.3.2. Loop a list of Struct
1fn folders(ui: &mut Ui) { 2 egui::ScrollArea::vertical().show(ui, |ui| { 3 crate::with_folder_state(|state| { 4 let folders_vec = (*state.folder_list.read().unwrap()).clone(); 5 let selected_id = *state.selected_folder_id.read().unwrap(); 6 let rename_folder = state.folder_to_rename.read().unwrap().as_ref().cloned(); 7 let rename_text = state.rename_text.read().unwrap().as_ref().cloned(); 8 9 if folders_vec.is_empty() { 10 ui.label("No folders yet..."); 11 } else { 12 for folder in &*folders_vec { 13 let is_renaming = rename_folder 14 .as_ref() 15 .map(|f| f.id == folder.id) 16 .unwrap_or(false); 17 let display_name = if is_renaming { 18 rename_text.as_ref().unwrap_or(&folder.name) 19 } else { 20 &folder.name 21 }; 22 let mut folder_item = FolderItem::new(folder, selected_id, display_name); 23 folder_item.view(ui); 24 } 25 } 26 }); 27 }); 28}
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
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"
2.4.1. flex-1 clickable button with indicator as "selected label"
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 view(&mut self, ui: &mut egui::Ui) { 9 ui.horizontal(|ui| { 10 let is_selected = self.selected_id == Some(self.folder.id); 11 12 // Calculate space for label (available width minus estimated menu space) 13 let available_width = ui.available_width(); 14 let dots_menu_width = 40.0; // Estimate for menu button 15 let label_width = (available_width - dots_menu_width).max(0.0); 16 17 // Make label expand to fill calculated space 18 ui.add_sized( 19 [label_width, ui.available_height() + 5.0], 20 |ui: &mut egui::Ui| { 21 let response = ui.selectable_label(is_selected, self.display_name); 22 if response.clicked() { 23 dispatch_folder_command(FolderCommand::SelectFolder { 24 folder_id: self.folder.id, 25 }); 26 } 27 response 28 }, 29 ); 30 31 self.dots_menu(ui, self.folder); 32 }); 33 } 34}
2.4.2. Dots menu
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
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
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)
2.5. Right column (Automatically Resizable)
1pub fn scripts_col(ctx: &egui::Context) { 2 egui::CentralPanel::default().show(ctx, |ui| { 3 ui.add_space(-6.0); // Reduce top padding 4 ui.label("Scripts"); 5 ui.separator(); 6 7 // Example 1: Using Frame with uniform margin 8 egui::Frame::new() 9 .inner_margin(16.0) // Same margin on all sides 10 .show(ui, |ui| { 11 ui.label("This is inside a Frame with 16px margin on all sides"); 12 }); 13 14 ui.add_space(10.0); 15 ... 16 }); 17}
2.6. Div like Element
2.6. Div like Element
This acts like div with display flex:
1egui::Frame::new() 2 .fill(egui::Color32::from_rgb(240, 240, 240)) // Light gray background, like a div 3 .stroke(egui::Stroke::new( 4 1.0, 5 egui::Color32::from_rgb(200, 200, 200), 6 )) // Subtle border 7 .corner_radius(4.0) // Rounded corners 8 .inner_margin(8.0) // Padding inside the frame 9 .show(ui, |ui| { 10 ui.horizontal_wrapped(|ui| { 11 ui.spacing_mut().item_spacing.x = 0.0; 12 ui.label("This demo showcases how to use "); 13 ui.code("Ui::response"); 14 ui.label(" to create interactive container widgets that may contain other widgets."); 15 }); 16 });
2.7. Theme-aware Div
2.7. Theme-aware Div
1Frame::canvas(ui.style()) 2 .fill(visuals.bg_fill.gamma_multiply(0.3)) 3 .stroke(visuals.bg_stroke) 4 .inner_margin(ui.spacing().menu_margin) 5 .show(ui, |ui| { 6 ui.set_width(ui.available_width()); 7 8 ui.add_space(32.0); 9 ui.vertical_centered(|ui| { 10 Label::new( 11 RichText::new(format!("{}", self.count)) 12 .color(text_color) 13 .size(32.0), 14 ) 15 .selectable(false) 16 .ui(ui); 17 }); 18 ui.add_space(32.0); 19 20 ui.horizontal(|ui| { 21 if ui.button("Reset").clicked() { 22 self.count = 0; 23 } 24 if ui.button("+ 100").clicked() { 25 self.count += 100; 26 } 27 }); 28 });
2.8. Group
2.8. Group
1ui.group(|ui| { 2 ui.label("This is inside a group() - has background and padding"); 3});
2.9. Frame with Border Radius
2.9. Frame with Border Radius
1egui::Frame::new() 2 .fill(ui.visuals().window_fill()) 3 .stroke(ui.visuals().window_stroke()) 4 .corner_radius(4.0) 5 .inner_margin(12.0) 6 .show(ui, |ui| { 7 ui.label("Frame with background, border, rounded corners, and 12px margin"); 8 }); 9ui.add_space(10.0);
2.10. Frame with Margin
2.10. Frame with Margin
1egui::Frame::new() 2 .inner_margin(16.0) // Same margin on all sides 3 .show(ui, |ui| { 4 ui.label("This is inside a Frame with 16px margin on all sides"); 5 }); 6 7ui.add_space(10.0);
2.11. Space-between Layout
2.11. Space-between Layout
1frame.show(ui, |ui| { 2 ui.horizontal(|ui| { 3 ui.label(format!("Name: {}", script.name)); 4 if ui.button("Rename").clicked() { 5 self.renaming_script_id = Some(script.id); 6 self.renaming_name = script.name.clone(); 7 } 8 ui.with_layout( 9 egui::Layout::right_to_left(egui::Align::Center), 10 |ui| { 11 if ui.button("Execute").clicked() { 12 // Execute the script command 13 crate::run_terminal_command(script.command.clone()); 14 } 15 if ui.button("Edit").clicked() { 16 self.editing_script_id = Some(script.id); 17 self.editing_command = script.command.clone(); 18 } 19 if ui.button("Copy").clicked() { 20 ui.ctx().copy_text(script.command.clone()); 21 } 22 }, 23 ); 24 }); 25 } 26)
2.12. Text Editor
2.12. Text Editor
1fn edit_script_window(&mut self, ui: &mut Ui, script_id: i32) { 2 egui::Window::new("Edit Script") 3 .collapsible(false) 4 .resizable(true) 5 .default_height(400.0) 6 .default_width(600.0) 7 .anchor(egui::Align2::CENTER_CENTER, egui::Vec2::ZERO) 8 .show(ui.ctx(), |ui| { 9 ui.add( 10 egui::TextEdit::multiline(&mut self.editing_command) 11 .font(egui::TextStyle::Monospace) 12 .code_editor() 13 .desired_rows(20) 14 .desired_width(580.0), 15 ); 16 ui.add_space(20.0); 17 ui.horizontal(|ui| { 18 if ui.button("Cancel").clicked() { 19 self.editing_script_id = None; 20 } 21 if ui.button("Save").clicked() { 22 dispatch_folder_command(FolderCommand::UpdateScript { 23 script_id, 24 new_command: self.editing_command.clone(), 25 }); 26 self.editing_script_id = None; 27 } 28 }); 29 }); 30}





























