egui_charts/ui/drawing_toolbar/
glyph_picker.rs1use super::DrawingToolbarAction;
4use crate::drawings::glyphs::{self, GLYPHS, Glyph};
5use crate::drawings::{DrawingToolRegistry, DrawingToolType};
6use crate::localization as l10n;
7use egui::{Rect, Ui, Vec2};
8
9#[derive(Clone, Debug)]
11pub struct GlyphPicker {
12 pub query: String,
14 pub size: f32,
16 group: &'static str,
17 recent: Vec<&'static str>,
18}
19
20impl Default for GlyphPicker {
21 fn default() -> Self {
22 Self {
23 query: String::new(),
24 size: 40.0,
25 group: "All",
26 recent: Vec::new(),
27 }
28 }
29}
30
31impl GlyphPicker {
32 pub(crate) fn show(
33 &mut self,
34 ui: &mut Ui,
35 registry: &DrawingToolRegistry,
36 ) -> Option<DrawingToolbarAction> {
37 ui.spacing_mut().item_spacing = egui::vec2(6.0, 6.0);
38 ui.set_width(312.0_f32.min(ui.ctx().content_rect().width() - 32.0));
39 ui.add(
40 egui::TextEdit::singleline(&mut self.query)
41 .hint_text(l10n::text(ui.ctx(), "Search icons, emoji and stickers"))
42 .desired_width(f32::INFINITY),
43 );
44 ui.horizontal_wrapped(|ui| {
45 for group in ["All", "Icons", "Emoji", "Stickers"] {
46 ui.selectable_value(&mut self.group, group, l10n::text(ui.ctx(), group));
47 }
48 });
49 ui.horizontal(|ui| {
50 ui.label(l10n::text(ui.ctx(), "Size"));
51 ui.add(
52 egui::DragValue::new(&mut self.size)
53 .range(8.0..=128.0)
54 .suffix(l10n::text(ui.ctx(), " px")),
55 );
56 });
57 ui.separator();
58 let query = self.query.trim().to_lowercase();
59 let ctx = ui.ctx().clone();
60 let matches = |glyph: &&Glyph| {
61 registry.availability(tool_type(glyph.id)).visible
62 && ((query.is_empty()
63 && (self.group == "All" || glyph.category.starts_with(self.group)))
64 || (!query.is_empty()
65 && (glyph.name.to_lowercase().contains(&query)
66 || l10n::text(&ctx, glyph.name).to_lowercase().contains(&query))))
67 };
68 let mut picked = None;
69 egui::ScrollArea::vertical()
70 .max_height(400.0)
71 .show(ui, |ui| {
72 let recent: Vec<_> = self
73 .recent
74 .iter()
75 .filter_map(|id| GLYPHS.iter().find(|g| g.id == *id))
76 .filter(matches)
77 .collect();
78 if !recent.is_empty() {
79 ui.label(l10n::text(ui.ctx(), "Recent"));
80 picked = cells(ui, &recent, registry);
81 }
82 let filtered: Vec<_> = GLYPHS.iter().filter(matches).collect();
83 if filtered.is_empty() {
84 ui.label(l10n::text(ui.ctx(), "No matching glyphs"));
85 }
86 let mut start = 0;
87 while start < filtered.len() {
88 let category = filtered[start].category;
89 let end = start
90 + filtered[start..]
91 .iter()
92 .take_while(|g| g.category == category)
93 .count();
94 ui.label(l10n::text(ui.ctx(), category));
95 picked = picked.or(cells(ui, &filtered[start..end], registry));
96 start = end;
97 }
98 });
99 let glyph = picked?;
100 self.recent.retain(|id| *id != glyph);
101 self.recent.insert(0, glyph);
102 self.recent.truncate(12);
103 Some(DrawingToolbarAction::InsertGlyph {
104 icon_name: glyph,
105 size: self.size,
106 })
107 }
108}
109
110fn cells(ui: &mut Ui, glyphs: &[&Glyph], registry: &DrawingToolRegistry) -> Option<&'static str> {
111 let columns = ((ui.available_width() / 48.0) as usize).max(1);
112 let mut picked = None;
113 for row in glyphs.chunks(columns) {
114 ui.horizontal(|ui| {
115 for glyph in row {
116 let response = ui
117 .add_enabled(
118 registry.availability(tool_type(glyph.id)).enabled,
119 egui::Button::new("").min_size(Vec2::splat(40.0)),
120 )
121 .on_hover_text(l10n::text(ui.ctx(), glyph.name));
122 let translated_label = l10n::text(ui.ctx(), glyph.name);
123 response.widget_info(|| {
124 egui::WidgetInfo::labeled(egui::Role::Button, true, &translated_label)
125 });
126 let rect = Rect::from_center_size(response.rect.center(), Vec2::splat(26.0));
127 let color = ui.visuals().text_color();
128 if !glyphs::paint(ui.painter(), glyph.id, rect, color) {
129 ui.painter().text(
130 rect.center(),
131 egui::Align2::CENTER_CENTER,
132 l10n::text(ui.ctx(), glyph.id),
133 egui::FontId::proportional(24.0),
134 color,
135 );
136 }
137 if response.clicked() {
138 picked = Some(glyph.id);
139 }
140 }
141 });
142 }
143 picked
144}
145
146fn tool_type(id: &str) -> DrawingToolType {
147 if id.starts_with("sticker:") {
148 DrawingToolType::Sticker
149 } else {
150 DrawingToolType::FontIcon
151 }
152}