header.rs (5210B)
1 use egui::{Frame, Layout, Margin, Stroke, UiBuilder}; 2 use egui_extras::{Size, StripBuilder}; 3 4 pub fn chevron( 5 ui: &mut egui::Ui, 6 pad: f32, 7 size: egui::Vec2, 8 stroke: impl Into<Stroke>, 9 ) -> egui::Response { 10 let (r, painter) = ui.allocate_painter(size, egui::Sense::click()); 11 12 let min = r.rect.min; 13 let max = r.rect.max; 14 15 let apex = egui::Pos2::new(min.x + pad, min.y + size.y / 2.0); 16 let top = egui::Pos2::new(max.x - pad, min.y + pad); 17 let bottom = egui::Pos2::new(max.x - pad, max.y - pad); 18 19 let stroke = stroke.into(); 20 painter.line_segment([apex, top], stroke); 21 painter.line_segment([apex, bottom], stroke); 22 23 r 24 } 25 26 /// Generic UI Widget to render widgets horizontally where each is aligned vertically 27 pub struct HorizontalHeader { 28 height: f32, 29 margin: Margin, 30 layout: Layout, 31 } 32 33 impl HorizontalHeader { 34 pub fn new(height: f32) -> Self { 35 Self { 36 height, 37 margin: Margin::same(8), 38 layout: Layout::left_to_right(egui::Align::Center), 39 } 40 } 41 42 pub fn with_margin(mut self, margin: Margin) -> Self { 43 self.margin = margin; 44 self 45 } 46 47 #[allow(clippy::too_many_arguments)] 48 pub fn ui( 49 self, 50 ui: &mut egui::Ui, 51 left_priority: i8, // lower the value, higher the priority 52 center_priority: i8, 53 right_priority: i8, 54 left_aligned: impl FnMut(&mut egui::Ui), 55 centered: impl FnMut(&mut egui::Ui), 56 right_aligned: impl FnMut(&mut egui::Ui), 57 ) { 58 let prev_spacing = ui.spacing().item_spacing.y; 59 ui.spacing_mut().item_spacing.y = 0.0; 60 Frame::new().inner_margin(self.margin).show(ui, |ui| { 61 let mut rect = ui.available_rect_before_wrap(); 62 rect.set_height(self.height); 63 64 let mut child_ui = ui.new_child(UiBuilder::new().max_rect(rect)); 65 66 horizontal_header_inner( 67 &mut child_ui, 68 self.layout, 69 left_priority, 70 center_priority, 71 right_priority, 72 left_aligned, 73 centered, 74 right_aligned, 75 ); 76 ui.advance_cursor_after_rect(rect); 77 }); 78 ui.spacing_mut().item_spacing.y = prev_spacing; 79 } 80 } 81 82 #[allow(clippy::too_many_arguments)] 83 fn horizontal_header_inner( 84 ui: &mut egui::Ui, 85 layout: Layout, 86 left_priority: i8, // lower the value, higher the priority 87 center_priority: i8, 88 right_priority: i8, 89 left_aligned: impl FnMut(&mut egui::Ui), 90 centered: impl FnMut(&mut egui::Ui), 91 right_aligned: impl FnMut(&mut egui::Ui), 92 ) { 93 let item_spacing = 6.0 * ui.spacing().item_spacing.x; 94 let max_width = ui.available_width() - item_spacing; 95 96 let (left_width, left_aligned) = measure_width(ui, left_aligned); 97 let (center_width, centered) = measure_width(ui, centered); 98 let (right_width, right_aligned) = measure_width(ui, right_aligned); 99 100 let half_max = max_width / 2.0; 101 let half_center = center_width / 2.0; 102 let left_spacing = half_max - left_width - half_center; 103 let right_spacing = half_max - right_width - half_center; 104 105 let mut left_center = half_center; 106 let left_cell = if left_spacing > 0.0 || left_priority < center_priority { 107 Size::exact(left_width) 108 } else { 109 Size::remainder() 110 }; 111 let mut left_gap = Size::exact(left_spacing.max(0.0)); 112 113 if left_spacing <= 0.0 { 114 left_gap = Size::exact(0.0); 115 if left_priority < center_priority { 116 left_center = (half_center + left_spacing).max(0.0); 117 } 118 } 119 120 let mut center_cell = Size::exact((left_center + half_center).max(0.0)); 121 let mut right_gap = Size::exact(right_spacing.max(0.0)); 122 let mut right_cell = Size::exact(right_width); 123 124 if right_spacing <= 0.0 { 125 right_gap = Size::exact(0.0); 126 if center_priority < right_priority { 127 right_cell = Size::remainder(); 128 } else { 129 center_cell = Size::remainder(); 130 } 131 } 132 133 let sizes = [left_cell, left_gap, center_cell, right_gap, right_cell]; 134 135 let mut builder = StripBuilder::new(ui); 136 for size in sizes { 137 builder = builder.size(size); 138 } 139 140 builder.cell_layout(layout).horizontal(|mut strip| { 141 strip.cell(left_aligned); 142 strip.empty(); 143 strip.cell(centered); 144 strip.empty(); 145 strip.cell(right_aligned); 146 }); 147 } 148 149 /// Inspired by VirtualList::ui_custom_layout 150 fn measure_width( 151 ui: &mut egui::Ui, 152 mut render: impl FnMut(&mut egui::Ui), 153 ) -> (f32, impl FnMut(&mut egui::Ui)) { 154 let mut measure_ui = ui.new_child( 155 UiBuilder::new() 156 .max_rect(ui.max_rect()) 157 .layout(Layout::left_to_right(egui::Align::Min)), 158 ); 159 measure_ui.set_invisible(); 160 161 let start_width = measure_ui.next_widget_position(); 162 let res = measure_ui.scope_builder(UiBuilder::new().id_salt(ui.id().with("measure")), |ui| { 163 render(ui); 164 render 165 }); 166 let end_width = measure_ui.next_widget_position(); 167 168 ( 169 (end_width.x - start_width.x + ui.spacing().item_spacing.x).max(0.0), 170 res.inner, 171 ) 172 }