repost.rs (6997B)
1 use std::f32::consts::PI; 2 3 use egui::{ 4 epaint::PathShape, pos2, vec2, CornerRadius, Layout, Margin, RichText, Sense, Shape, Stroke, 5 }; 6 use egui_extras::StripBuilder; 7 use enostr::NoteId; 8 use notedeck::{fonts::get_font_size, NotedeckTextStyle}; 9 use notedeck_ui::{app_images, galley_centered_pos}; 10 11 use crate::repost::RepostAction; 12 13 pub struct RepostDecisionView<'a> { 14 noteid: &'a NoteId, 15 } 16 17 impl<'a> RepostDecisionView<'a> { 18 pub fn new(noteid: &'a NoteId) -> Self { 19 Self { noteid } 20 } 21 22 pub fn show(&self, ui: &mut egui::Ui) -> Option<RepostAction> { 23 let mut action = None; 24 egui::Frame::new() 25 .inner_margin(Margin::symmetric(48, 24)) 26 .show(ui, |ui| { 27 StripBuilder::new(ui) 28 .sizes(egui_extras::Size::exact(48.0), 2) 29 .size(egui_extras::Size::exact(80.0)) 30 .vertical(|mut strip| { 31 strip.cell(|ui| { 32 if ui 33 .with_layout(Layout::left_to_right(egui::Align::Center), |ui| { 34 let r = ui.add( 35 app_images::repost_image(ui.visuals().dark_mode) 36 .max_height(24.0) 37 .sense(Sense::click()), 38 ); 39 r.union(ui.add(repost_item_text("Repost"))) 40 }) 41 .inner 42 .on_hover_cursor(egui::CursorIcon::PointingHand) 43 .clicked() 44 { 45 action = Some(RepostAction::Kind06Repost(*self.noteid)) 46 } 47 }); 48 49 strip.cell(|ui| { 50 if ui 51 .with_layout(Layout::left_to_right(egui::Align::Center), |ui| { 52 let r = ui 53 .add(quote_icon()) 54 .on_hover_cursor(egui::CursorIcon::PointingHand); 55 r.union(ui.add(repost_item_text("Quote"))) 56 }) 57 .inner 58 .on_hover_cursor(egui::CursorIcon::PointingHand) 59 .clicked() 60 { 61 action = Some(RepostAction::Quote(*self.noteid)) 62 } 63 }); 64 65 strip.cell(|ui| { 66 ui.add_space(16.0); 67 let resp = ui.allocate_response( 68 vec2(ui.available_width(), 48.0), 69 Sense::click(), 70 ); 71 72 let color = if resp.hovered() { 73 ui.visuals().widgets.hovered.bg_fill 74 } else { 75 ui.visuals().text_color() 76 }; 77 78 let painter = ui.painter_at(resp.rect); 79 ui.painter().rect_stroke( 80 resp.rect, 81 CornerRadius::same(32), 82 egui::Stroke::new(1.5, color), 83 egui::StrokeKind::Inside, 84 ); 85 86 let galley = painter.layout_no_wrap( 87 "Cancel".to_owned(), 88 NotedeckTextStyle::Heading3.get_font_id(ui.ctx()), 89 ui.visuals().text_color(), 90 ); 91 92 painter.galley( 93 galley_centered_pos(&galley, resp.rect.center()), 94 galley, 95 ui.visuals().text_color(), 96 ); 97 98 if resp.clicked() { 99 action = Some(RepostAction::Cancel); 100 } 101 }); 102 }); 103 }); 104 105 action 106 } 107 } 108 109 fn repost_item_text(text: &str) -> impl egui::Widget + use<'_> { 110 move |ui: &mut egui::Ui| -> egui::Response { 111 ui.add(egui::Label::new( 112 RichText::new(text).size(get_font_size(ui.ctx(), &NotedeckTextStyle::Heading3)), 113 )) 114 .on_hover_cursor(egui::CursorIcon::PointingHand) 115 } 116 } 117 118 pub fn quote_icon() -> impl egui::Widget { 119 move |ui: &mut egui::Ui| -> egui::Response { 120 let h = 32.0; // scaling constant 121 let color = ui.visuals().strong_text_color(); 122 let r = h * 0.12; // dot radius 123 let arc_r = r * 2.0; // larger so it protrudes above 124 let sw = h * 0.06; // stroke width 125 let stroke = Stroke::new(sw, color); 126 127 // Same horizontal layout as before (in "local" coords using height scale) 128 let cx1_raw = h * 0.34; 129 let cx2_raw = h * 0.72; 130 let gap = cx2_raw - cx1_raw; 131 132 // Bounds including stroke (only the left side needs +sw/2 for arcs) 133 let left_bound = (cx1_raw - r) - sw * 0.5; 134 let right_bound = (cx2_raw + r).max(cx2_raw + (arc_r - r)); // safe if arc_r > 2r 135 let width = right_bound - left_bound; 136 137 // Vertical bounds: arc top needs sw/2; total content height = arc_r + r + sw/2 138 let content_h = arc_r + r + sw * 0.5; 139 let v_margin = (h - content_h) * 0.5; 140 141 let resp = ui.allocate_response(vec2(width, h), egui::Sense::click()); 142 let rect = resp.rect; 143 let origin = rect.min; 144 145 // Place centers with bounds aligned to rect 146 let dx = origin.x - left_bound; 147 let cy = origin.y + v_margin + arc_r + sw * 0.5; 148 149 let c1 = pos2(dx + cx1_raw, cy); 150 let c2 = pos2(dx + cx1_raw + gap, cy); 151 152 // arc centers (unchanged) 153 let a1 = pos2(c1.x + (arc_r - r) + 0.8, c1.y); 154 let a2 = pos2(c2.x + (arc_r - r) + 0.8, c2.y); 155 156 // Draw arcs FIRST (upper-left quadrant [π, 3π/2]) 157 let arc = |ac: egui::Pos2| { 158 let steps = 16; 159 (0..=steps) 160 .map(|i| { 161 let t = i as f32 / steps as f32; 162 let ang = PI + t * (PI * 0.5); 163 pos2(ac.x + arc_r * ang.cos(), ac.y + arc_r * ang.sin()) 164 }) 165 .collect::<Vec<_>>() 166 }; 167 let painter = ui.painter_at(rect); 168 painter.add(Shape::Path(PathShape::line(arc(a1), stroke))); 169 painter.add(Shape::Path(PathShape::line(arc(a2), stroke))); 170 171 // Dots LAST to hide the junction 172 painter.circle_filled(c1, r, color); 173 painter.circle_filled(c2, r, color); 174 175 resp 176 } 177 }