1use eframe::egui::{self, Key, KeyboardShortcut, Modifiers, RichText, Ui};
7use egui_richedit::{Mark, Model};
8
9use crate::i18n::t;
10use crate::{Block, FlowModel, PageEditor};
11
12pub fn marks<M: Model<Paragraph = Vec<usize>>>(
16 ui: &mut Ui,
17 editor: &PageEditor,
18 model: &M,
19) -> Option<Mark> {
20 let mut pressed = None;
21 ui.add_enabled_ui(editor.selection().is_some(), |ui| {
22 ui.horizontal(|ui| pressed = mark_buttons(ui, editor, model));
23 });
24 pressed
25}
26
27#[derive(Default)]
29pub struct Pressed {
30 pub mark: Option<Mark>,
32 pub block: Option<Block>,
34}
35
36pub fn text(ui: &mut Ui, editor: &PageEditor, model: &FlowModel<'_>) -> Pressed {
41 let mut pressed = Pressed::default();
42 ui.add_enabled_ui(editor.selection().is_some(), |ui| {
43 ui.horizontal(|ui| {
44 pressed.mark = mark_buttons(ui, editor, model);
45 ui.separator();
46 for (block, label, name) in [
47 (Block::Body, "\u{b6}", t("Body text")),
48 (Block::Heading(1), "H1", t("Heading 1")),
49 (Block::Heading(2), "H2", t("Heading 2")),
50 (Block::Heading(3), "H3", t("Heading 3")),
51 (Block::Bullets, "\u{2022}", t("Bulleted list")),
52 (Block::Numbers, "1.", t("Numbered list")),
53 ] {
54 let lit = editor
55 .selection()
56 .is_some_and(|selection| model.is_block(selection, block));
57 if ui
58 .selectable_label(lit, label)
59 .on_hover_text(name)
60 .clicked()
61 {
62 pressed.block = Some(block);
63 }
64 }
65 });
66 });
67 pressed
68}
69
70fn mark_facts(mark: Mark) -> (&'static str, Option<Key>) {
73 match mark {
74 Mark::Bold => (t("Bold"), Some(Key::B)),
75 Mark::Italic => (t("Italic"), Some(Key::I)),
76 Mark::Underline => (t("Underline"), Some(Key::U)),
77 Mark::Strike => (t("Strikethrough"), None),
78 }
79}
80
81fn mark_buttons<M: Model<Paragraph = Vec<usize>>>(
82 ui: &mut Ui,
83 editor: &PageEditor,
84 model: &M,
85) -> Option<Mark> {
86 let mut pressed = None;
87 for mark in Mark::ALL {
88 let lit = editor.marked(model, mark) == Some(true);
89 let (name, key) = mark_facts(mark);
90 let label = match mark {
91 Mark::Bold => RichText::new(t("B")).strong(),
92 Mark::Italic => RichText::new(t("I")).italics(),
93 Mark::Underline => RichText::new(t("U")).underline(),
94 Mark::Strike => RichText::new(t("S")).strikethrough(),
95 };
96 let hint = match key {
97 Some(key) => format!(
98 "{name} ({})",
99 ui.ctx()
100 .format_shortcut(&KeyboardShortcut::new(Modifiers::COMMAND, key))
101 ),
102 None => name.to_owned(),
103 };
104 if ui
105 .selectable_label(lit, label)
106 .on_hover_text(hint)
107 .clicked()
108 {
109 pressed = Some(mark);
110 }
111 }
112 pressed
113}
114
115#[derive(Clone, Copy, PartialEq, Eq)]
117pub enum Command {
118 Mark(Mark),
120 Block(Block),
122}
123
124const BLOCKS: [Block; 6] = [
126 Block::Body,
127 Block::Heading(1),
128 Block::Heading(2),
129 Block::Heading(3),
130 Block::Bullets,
131 Block::Numbers,
132];
133
134fn block_name(block: Block) -> &'static str {
135 match block {
136 Block::Body => t("Body text"),
137 Block::Heading(1) => t("Heading 1"),
138 Block::Heading(2) => t("Heading 2"),
139 Block::Heading(_) => t("Heading 3"),
140 Block::Bullets => t("Bulleted list"),
141 Block::Numbers => t("Numbered list"),
142 }
143}
144
145#[derive(Default)]
148pub struct MenuState {
149 enabled: bool,
150 blocks: bool,
151 marks: Vec<Mark>,
152 kinds: Vec<Block>,
153}
154
155impl MenuState {
156 pub fn of(editor: &PageEditor, model: &FlowModel<'_>, blocks: bool) -> Self {
159 let Some(selection) = editor.selection() else {
160 return Self {
161 blocks,
162 ..Self::default()
163 };
164 };
165 Self {
166 enabled: true,
167 blocks,
168 marks: Mark::ALL
169 .into_iter()
170 .filter(|mark| editor.marked(model, *mark) == Some(true))
171 .collect(),
172 kinds: if blocks {
173 BLOCKS
174 .into_iter()
175 .filter(|block| model.is_block(selection, *block))
176 .collect()
177 } else {
178 Vec::new()
179 },
180 }
181 }
182
183 pub fn offering_blocks(blocks: bool) -> Self {
186 Self {
187 blocks,
188 ..Self::default()
189 }
190 }
191}
192
193pub fn menu(ui: &mut Ui, state: &MenuState) -> Option<Command> {
197 let mut chosen = None;
198 for mark in Mark::ALL {
199 let (name, key) = mark_facts(mark);
200 let mut button = egui::Button::new(name).selected(state.marks.contains(&mark));
201 if let Some(key) = key {
202 button = button.shortcut_text(
203 ui.ctx()
204 .format_shortcut(&KeyboardShortcut::new(Modifiers::COMMAND, key)),
205 );
206 }
207 if ui.add_enabled(state.enabled, button).clicked() {
208 chosen = Some(Command::Mark(mark));
209 ui.close();
210 }
211 }
212 if state.blocks {
213 ui.separator();
214 for block in BLOCKS {
215 let button =
216 egui::Button::new(block_name(block)).selected(state.kinds.contains(&block));
217 if ui.add_enabled(state.enabled, button).clicked() {
218 chosen = Some(Command::Block(block));
219 ui.close();
220 }
221 }
222 }
223 chosen
224}