poetrymagnets.online

git clone https://git.bradleygannon.com/poetrymagnets.online.git
Log | Files | Refs

app.rs (7121B)


      1 use std::sync::Arc;
      2 
      3 use egui::{
      4     Area, Color32, CornerRadius, CursorIcon, FontData, FontDefinitions, FontFamily, Frame, Id,
      5     Label, Margin, Pos2, Rect, Sense, Shadow, TextWrapMode, Vec2, Visuals,
      6 };
      7 use web_sys::js_sys::Math::random;
      8 
      9 const APP_KEY: &str = "poetrymagnets.online";
     10 const FONT_KEY: &str = "Libre Baskerville";
     11 
     12 const TOP_LEFT: Pos2 = Pos2::ZERO;
     13 const BOTTOM_RIGHT: Pos2 = Pos2::new(2048., 2048.);
     14 const WORLD_BOUNDARY: Rect = Rect::from_min_max(TOP_LEFT, BOTTOM_RIGHT);
     15 
     16 #[derive(serde::Deserialize, serde::Serialize)]
     17 struct Magnet {
     18     id: Id,
     19     text: String,
     20     position: Pos2,
     21 }
     22 
     23 #[derive(serde::Deserialize, serde::Serialize)]
     24 #[serde(default)]
     25 pub struct PoetryMagnetsOnlineApp {
     26     zoom_level: f32,
     27     zoom_level_target: f32,
     28     pan_offset: Pos2,
     29     magnets: Vec<Magnet>,
     30 }
     31 
     32 impl Default for PoetryMagnetsOnlineApp {
     33     fn default() -> Self {
     34         let mut magnets =
     35             serde_json::from_str::<Vec<Magnet>>(include_str!("../words.json")).unwrap_or_default();
     36         for magnet in magnets.iter_mut() {
     37             magnet.position = Pos2::new(
     38                 random() as f32 * (BOTTOM_RIGHT.x - 50.),
     39                 random() as f32 * (BOTTOM_RIGHT.y - 25.),
     40             );
     41         }
     42         Self {
     43             zoom_level: 2.,
     44             zoom_level_target: 2.,
     45             pan_offset: Pos2::ZERO,
     46             magnets,
     47         }
     48     }
     49 }
     50 
     51 impl PoetryMagnetsOnlineApp {
     52     pub fn new(cc: &eframe::CreationContext<'_>) -> Self {
     53         let mut fonts = FontDefinitions::default();
     54         fonts.font_data.insert(
     55             FONT_KEY.to_owned(),
     56             Arc::new(FontData::from_static(include_bytes!(
     57                 "../assets/LibreBaskerville-Regular.ttf"
     58             ))),
     59         );
     60         fonts
     61             .families
     62             .get_mut(&FontFamily::Proportional)
     63             .unwrap()
     64             .insert(0, FONT_KEY.to_owned());
     65         cc.egui_ctx.set_fonts(fonts);
     66         cc.egui_ctx.set_visuals(Visuals::light());
     67 
     68         if let Some(storage) = cc.storage {
     69             eframe::get_value(storage, APP_KEY).unwrap_or_default()
     70         } else {
     71             PoetryMagnetsOnlineApp::default()
     72         }
     73     }
     74 }
     75 
     76 impl eframe::App for PoetryMagnetsOnlineApp {
     77     fn save(&mut self, storage: &mut dyn eframe::Storage) {
     78         eframe::set_value(storage, APP_KEY, self);
     79     }
     80     fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
     81         egui::CentralPanel::default()
     82             .frame(egui::Frame::NONE.fill(Color32::GRAY))
     83             .show(ctx, |ui| {
     84                 let response =
     85                     ui.allocate_rect(ui.available_rect_before_wrap(), Sense::click_and_drag());
     86 
     87                 if response.dragged() {
     88                     let new_pan_offset = self.pan_offset + response.drag_delta();
     89                     let new_viewport = ctx.viewport_rect().translate(-new_pan_offset.to_vec2());
     90                     if new_viewport.left() < WORLD_BOUNDARY.left() {
     91                         self.pan_offset.x = 0.;
     92                     } else if WORLD_BOUNDARY.right() < new_viewport.right() {
     93                         self.pan_offset.x = new_viewport.width() - WORLD_BOUNDARY.right();
     94                     } else {
     95                         self.pan_offset.x += response.drag_delta().x;
     96                     }
     97                     if new_viewport.top() <= WORLD_BOUNDARY.top() {
     98                         self.pan_offset.y = 0.;
     99                     } else if WORLD_BOUNDARY.bottom() <= new_viewport.bottom() {
    100                         self.pan_offset.y = new_viewport.height() - WORLD_BOUNDARY.bottom();
    101                     } else {
    102                         self.pan_offset.y += response.drag_delta().y;
    103                     }
    104                     ctx.set_cursor_icon(CursorIcon::Move);
    105                 }
    106 
    107                 let zoom_delta_raw = ui.input(|i| {
    108                     if let Some(multi_touch) = i.multi_touch() {
    109                         multi_touch.zoom_delta
    110                     } else {
    111                         1. - i.raw_scroll_delta.y * 0.001
    112                     }
    113                 });
    114                 if zoom_delta_raw != 1. {
    115                     self.zoom_level_target *= zoom_delta_raw;
    116                     self.zoom_level_target = self.zoom_level_target.clamp(1., 5.);
    117                     let zoom_delta_smooth = (self.zoom_level_target - self.zoom_level) * 0.25;
    118                     self.zoom_level += zoom_delta_smooth;
    119                     self.zoom_level = self.zoom_level.clamp(1., 5.);
    120                 }
    121                 ctx.set_zoom_factor(self.zoom_level);
    122             });
    123         for magnet in self.magnets.iter_mut() {
    124             let magnet_position_with_offset = self.pan_offset + magnet.position.to_vec2();
    125             let magnet_rect_with_buffer =
    126                 Rect::from_min_size(magnet_position_with_offset, Vec2::new(50., 50.));
    127             if !ctx.content_rect().intersects(magnet_rect_with_buffer) {
    128                 continue;
    129             }
    130             let response = Area::new(magnet.id)
    131                 .current_pos(magnet_position_with_offset)
    132                 .constrain(false)
    133                 .show(ctx, |ui| {
    134                     Frame::NONE
    135                         .inner_margin(Margin::symmetric(5, 3))
    136                         .shadow(Shadow {
    137                             offset: [1, 1],
    138                             blur: 0,
    139                             spread: 0,
    140                             color: Color32::GRAY,
    141                         })
    142                         .fill(Color32::WHITE)
    143                         .corner_radius(CornerRadius::same(1))
    144                         .show(ui, |ui| {
    145                             ui.add(
    146                                 Label::new(magnet.text.clone())
    147                                     .selectable(false)
    148                                     .wrap_mode(TextWrapMode::Extend),
    149                             );
    150                         });
    151                 })
    152                 .response;
    153             if response.dragged() {
    154                 let new_magnet_position = magnet.position + response.drag_delta();
    155                 let magnet_rect = response.rect.translate(-self.pan_offset.to_vec2());
    156                 if magnet_rect.left() <= WORLD_BOUNDARY.left() {
    157                     magnet.position.x = WORLD_BOUNDARY.left();
    158                 } else if WORLD_BOUNDARY.right() <= magnet_rect.right() {
    159                     magnet.position.x = WORLD_BOUNDARY.width() - magnet_rect.width();
    160                 } else {
    161                     magnet.position.x = new_magnet_position.x;
    162                 }
    163                 if magnet_rect.top() <= WORLD_BOUNDARY.top() {
    164                     magnet.position.y = WORLD_BOUNDARY.top();
    165                 } else if WORLD_BOUNDARY.bottom() <= magnet_rect.bottom() {
    166                     magnet.position.y = WORLD_BOUNDARY.height() - magnet_rect.height();
    167                 } else {
    168                     magnet.position.y = new_magnet_position.y;
    169                 }
    170                 ctx.set_cursor_icon(CursorIcon::Grabbing);
    171             } else if response.hovered() {
    172                 ctx.set_cursor_icon(CursorIcon::Grab);
    173             }
    174         }
    175     }
    176 }