notedeck

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

configure_deck.rs (10581B)


      1 use crate::{app_style::deck_icon_font_sized, colors::PINK, deck_state::DeckState};
      2 use egui::{vec2, Button, Color32, Label, RichText, Stroke, Ui, Widget};
      3 use notedeck::{NamedFontFamily, NotedeckTextStyle};
      4 
      5 use super::{
      6     anim::{AnimationHelper, ICON_EXPANSION_MULTIPLE},
      7     padding,
      8 };
      9 
     10 pub struct ConfigureDeckView<'a> {
     11     state: &'a mut DeckState,
     12     create_button_text: String,
     13 }
     14 
     15 pub struct ConfigureDeckResponse {
     16     pub icon: char,
     17     pub name: String,
     18 }
     19 
     20 static CREATE_TEXT: &str = "Create Deck";
     21 
     22 impl<'a> ConfigureDeckView<'a> {
     23     pub fn new(state: &'a mut DeckState) -> Self {
     24         Self {
     25             state,
     26             create_button_text: CREATE_TEXT.to_owned(),
     27         }
     28     }
     29 
     30     pub fn with_create_text(mut self, text: &str) -> Self {
     31         self.create_button_text = text.to_owned();
     32         self
     33     }
     34 
     35     pub fn ui(&mut self, ui: &mut Ui) -> Option<ConfigureDeckResponse> {
     36         let title_font = egui::FontId::new(
     37             notedeck::fonts::get_font_size(ui.ctx(), &NotedeckTextStyle::Heading4),
     38             egui::FontFamily::Name(NamedFontFamily::Bold.as_str().into()),
     39         );
     40         padding(16.0, ui, |ui| {
     41             ui.add(Label::new(
     42                 RichText::new("Deck name").font(title_font.clone()),
     43             ));
     44             ui.add_space(8.0);
     45             ui.text_edit_singleline(&mut self.state.deck_name);
     46             ui.add_space(8.0);
     47             ui.add(Label::new(
     48                 RichText::new("We recommend short names")
     49                     .color(ui.visuals().noninteractive().fg_stroke.color)
     50                     .size(notedeck::fonts::get_font_size(
     51                         ui.ctx(),
     52                         &NotedeckTextStyle::Small,
     53                     )),
     54             ));
     55 
     56             ui.add_space(32.0);
     57             ui.add(Label::new(RichText::new("Icon").font(title_font)));
     58 
     59             if ui
     60                 .add(deck_icon(
     61                     ui.id().with("config-deck"),
     62                     self.state.selected_glyph,
     63                     38.0,
     64                     64.0,
     65                     false,
     66                 ))
     67                 .clicked()
     68             {
     69                 self.state.selecting_glyph = !self.state.selecting_glyph;
     70             }
     71 
     72             if self.state.selecting_glyph {
     73                 let max_height = if ui.available_height() - 100.0 > 0.0 {
     74                     ui.available_height() - 100.0
     75                 } else {
     76                     ui.available_height()
     77                 };
     78                 egui::Frame::window(ui.style()).show(ui, |ui| {
     79                     let glyphs = self.state.get_glyph_options(ui);
     80                     if let Some(selected_glyph) = glyph_options_ui(ui, 16.0, max_height, glyphs) {
     81                         self.state.selected_glyph = Some(selected_glyph);
     82                         self.state.selecting_glyph = false;
     83                     }
     84                 });
     85                 ui.add_space(16.0);
     86             }
     87 
     88             if self.state.warn_no_icon && self.state.selected_glyph.is_some() {
     89                 self.state.warn_no_icon = false;
     90             }
     91             if self.state.warn_no_title && !self.state.deck_name.is_empty() {
     92                 self.state.warn_no_title = false;
     93             }
     94 
     95             show_warnings(ui, self.state.warn_no_icon, self.state.warn_no_title);
     96 
     97             let mut resp = None;
     98             if ui
     99                 .add(create_deck_button(&self.create_button_text))
    100                 .clicked()
    101             {
    102                 if self.state.deck_name.is_empty() {
    103                     self.state.warn_no_title = true;
    104                 }
    105                 if self.state.selected_glyph.is_none() {
    106                     self.state.warn_no_icon = true;
    107                 }
    108                 if !self.state.deck_name.is_empty() {
    109                     if let Some(glyph) = self.state.selected_glyph {
    110                         resp = Some(ConfigureDeckResponse {
    111                             icon: glyph,
    112                             name: self.state.deck_name.clone(),
    113                         });
    114                     }
    115                 }
    116             }
    117             resp
    118         })
    119         .inner
    120     }
    121 }
    122 
    123 fn show_warnings(ui: &mut Ui, warn_no_icon: bool, warn_no_title: bool) {
    124     if warn_no_icon || warn_no_title {
    125         let messages = [
    126             if warn_no_title {
    127                 "create a name for the deck"
    128             } else {
    129                 ""
    130             },
    131             if warn_no_icon { "select an icon" } else { "" },
    132         ];
    133         let message = messages
    134             .iter()
    135             .filter(|&&m| !m.is_empty())
    136             .copied()
    137             .collect::<Vec<_>>()
    138             .join(" and ");
    139 
    140         ui.add(
    141             egui::Label::new(
    142                 RichText::new(format!("Please {}.", message)).color(ui.visuals().error_fg_color),
    143             )
    144             .wrap(),
    145         );
    146     }
    147 }
    148 
    149 fn create_deck_button(text: &str) -> impl Widget + '_ {
    150     move |ui: &mut egui::Ui| {
    151         let size = vec2(108.0, 40.0);
    152         ui.allocate_ui_with_layout(size, egui::Layout::top_down(egui::Align::Center), |ui| {
    153             ui.add(Button::new(text).fill(PINK).min_size(size))
    154         })
    155         .inner
    156     }
    157 }
    158 
    159 pub fn deck_icon(
    160     id: egui::Id,
    161     glyph: Option<char>,
    162     font_size: f32,
    163     full_size: f32,
    164     highlight: bool,
    165 ) -> impl Widget {
    166     move |ui: &mut egui::Ui| -> egui::Response {
    167         let max_size = full_size * ICON_EXPANSION_MULTIPLE;
    168 
    169         let helper = AnimationHelper::new(ui, id, vec2(max_size, max_size));
    170         let painter = ui.painter_at(helper.get_animation_rect());
    171         let bg_center = helper.get_animation_rect().center();
    172 
    173         let (stroke, fill_color) = if highlight {
    174             (
    175                 ui.visuals().selection.stroke,
    176                 ui.visuals().widgets.noninteractive.weak_bg_fill,
    177             )
    178         } else {
    179             (
    180                 Stroke::new(
    181                     ui.visuals().widgets.inactive.bg_stroke.width,
    182                     ui.visuals().widgets.inactive.weak_bg_fill,
    183                 ),
    184                 ui.visuals().widgets.noninteractive.weak_bg_fill,
    185             )
    186         };
    187 
    188         let radius = helper.scale_1d_pos((full_size / 2.0) - stroke.width);
    189         painter.circle(bg_center, radius, fill_color, stroke);
    190 
    191         if let Some(glyph) = glyph {
    192             let font =
    193                 deck_icon_font_sized(helper.scale_1d_pos(font_size / std::f32::consts::SQRT_2));
    194             let glyph_galley =
    195                 painter.layout_no_wrap(glyph.to_string(), font, ui.visuals().text_color());
    196 
    197             let top_left = {
    198                 let mut glyph_rect = glyph_galley.rect;
    199                 glyph_rect.set_center(bg_center);
    200                 glyph_rect.left_top()
    201             };
    202 
    203             painter.galley(top_left, glyph_galley, Color32::WHITE);
    204         }
    205 
    206         helper.take_animation_response()
    207     }
    208 }
    209 
    210 fn glyph_icon_max_size(ui: &egui::Ui, glyph: &char, font_size: f32) -> egui::Vec2 {
    211     let painter = ui.painter();
    212     let font = deck_icon_font_sized(font_size * ICON_EXPANSION_MULTIPLE);
    213     let glyph_galley = painter.layout_no_wrap(glyph.to_string(), font, Color32::WHITE);
    214     glyph_galley.rect.size()
    215 }
    216 
    217 fn glyph_icon(glyph: char, font_size: f32, max_size: egui::Vec2, color: Color32) -> impl Widget {
    218     move |ui: &mut egui::Ui| {
    219         let helper = AnimationHelper::new(ui, ("glyph", glyph), max_size);
    220         let painter = ui.painter_at(helper.get_animation_rect());
    221 
    222         let font = deck_icon_font_sized(helper.scale_1d_pos(font_size));
    223         let glyph_galley = painter.layout_no_wrap(glyph.to_string(), font, color);
    224 
    225         let top_left = {
    226             let mut glyph_rect = glyph_galley.rect;
    227             glyph_rect.set_center(helper.get_animation_rect().center());
    228             glyph_rect.left_top()
    229         };
    230 
    231         painter.galley(top_left, glyph_galley, Color32::WHITE);
    232         helper.take_animation_response()
    233     }
    234 }
    235 
    236 fn glyph_options_ui(
    237     ui: &mut egui::Ui,
    238     font_size: f32,
    239     max_height: f32,
    240     glyphs: &[char],
    241 ) -> Option<char> {
    242     let mut selected_glyph = None;
    243     egui::ScrollArea::vertical()
    244         .max_height(max_height)
    245         .show(ui, |ui| {
    246             let max_width = ui.available_width();
    247             let mut row_glyphs = Vec::new();
    248             let mut cur_width = 0.0;
    249             let spacing = ui.spacing().item_spacing.x;
    250 
    251             for (index, glyph) in glyphs.iter().enumerate() {
    252                 let next_glyph_size = glyph_icon_max_size(ui, glyph, font_size);
    253 
    254                 if cur_width + spacing + next_glyph_size.x > max_width {
    255                     if let Some(selected) = paint_row(ui, &row_glyphs, font_size) {
    256                         selected_glyph = Some(selected);
    257                     }
    258                     row_glyphs.clear();
    259                     cur_width = 0.0;
    260                 }
    261 
    262                 cur_width += spacing;
    263                 cur_width += next_glyph_size.x;
    264                 row_glyphs.push(*glyph);
    265 
    266                 if index == glyphs.len() - 1 {
    267                     if let Some(selected) = paint_row(ui, &row_glyphs, font_size) {
    268                         selected_glyph = Some(selected);
    269                     }
    270                 }
    271             }
    272         });
    273     selected_glyph
    274 }
    275 
    276 fn paint_row(ui: &mut egui::Ui, row_glyphs: &[char], font_size: f32) -> Option<char> {
    277     let mut selected_glyph = None;
    278     ui.horizontal(|ui| {
    279         for glyph in row_glyphs {
    280             let glyph_size = glyph_icon_max_size(ui, glyph, font_size);
    281             if ui
    282                 .add(glyph_icon(
    283                     *glyph,
    284                     font_size,
    285                     glyph_size,
    286                     ui.visuals().text_color(),
    287                 ))
    288                 .clicked()
    289             {
    290                 selected_glyph = Some(*glyph);
    291             }
    292         }
    293     });
    294     selected_glyph
    295 }
    296 
    297 mod preview {
    298     use crate::{
    299         deck_state::DeckState,
    300         ui::{Preview, PreviewConfig, View},
    301     };
    302 
    303     use super::ConfigureDeckView;
    304 
    305     pub struct ConfigureDeckPreview {
    306         state: DeckState,
    307     }
    308 
    309     impl ConfigureDeckPreview {
    310         fn new() -> Self {
    311             let state = DeckState::default();
    312 
    313             ConfigureDeckPreview { state }
    314         }
    315     }
    316 
    317     impl View for ConfigureDeckPreview {
    318         fn ui(&mut self, ui: &mut egui::Ui) {
    319             ConfigureDeckView::new(&mut self.state).ui(ui);
    320         }
    321     }
    322 
    323     impl Preview for ConfigureDeckView<'_> {
    324         type Prev = ConfigureDeckPreview;
    325 
    326         fn preview(_cfg: PreviewConfig) -> Self::Prev {
    327             ConfigureDeckPreview::new()
    328         }
    329     }
    330 }