notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

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 }