1use std::sync::Arc;
2
3use crate::*;
4
5use self::text_selection::LabelSelectionState;
6
7#[must_use = "You should put this widget in an ui with `ui.add(widget);`"]
24pub struct Label {
25 text: WidgetText,
26 wrap_mode: Option<TextWrapMode>,
27 sense: Option<Sense>,
28 selectable: Option<bool>,
29}
30
31impl Label {
32 pub fn new(text: impl Into<WidgetText>) -> Self {
33 Self {
34 text: text.into(),
35 wrap_mode: None,
36 sense: None,
37 selectable: None,
38 }
39 }
40
41 pub fn text(&self) -> &str {
42 self.text.text()
43 }
44
45 #[inline]
51 pub fn wrap_mode(mut self, wrap_mode: TextWrapMode) -> Self {
52 self.wrap_mode = Some(wrap_mode);
53 self
54 }
55
56 #[inline]
58 pub fn wrap(mut self) -> Self {
59 self.wrap_mode = Some(TextWrapMode::Wrap);
60
61 self
62 }
63
64 #[inline]
66 pub fn truncate(mut self) -> Self {
67 self.wrap_mode = Some(TextWrapMode::Truncate);
68 self
69 }
70
71 #[inline]
74 pub fn extend(mut self) -> Self {
75 self.wrap_mode = Some(TextWrapMode::Extend);
76 self
77 }
78
79 #[inline]
83 pub fn selectable(mut self, selectable: bool) -> Self {
84 self.selectable = Some(selectable);
85 self
86 }
87
88 #[inline]
103 pub fn sense(mut self, sense: Sense) -> Self {
104 self.sense = Some(sense);
105 self
106 }
107}
108
109impl Label {
110 pub fn layout_in_ui(self, ui: &mut Ui) -> (Pos2, Arc<Galley>, Response) {
112 let selectable = self
113 .selectable
114 .unwrap_or_else(|| ui.style().interaction.selectable_labels);
115
116 let mut sense = self.sense.unwrap_or_else(|| {
117 if ui.memory(|mem| mem.options.screen_reader) {
118 Sense::focusable_noninteractive()
120 } else {
121 Sense::hover()
122 }
123 });
124
125 if selectable {
126 let allow_drag_to_select = ui.input(|i| !i.has_touch_screen());
131
132 let mut select_sense = if allow_drag_to_select {
133 Sense::click_and_drag()
134 } else {
135 Sense::click()
136 };
137 select_sense.focusable = false; sense = sense.union(select_sense);
140 }
141
142 if let WidgetText::Galley(galley) = self.text {
143 let (rect, response) = ui.allocate_exact_size(galley.size(), sense);
145 let pos = match galley.job.halign {
146 Align::LEFT => rect.left_top(),
147 Align::Center => rect.center_top(),
148 Align::RIGHT => rect.right_top(),
149 };
150 return (pos, galley, response);
151 }
152
153 let valign = ui.layout().vertical_align();
154 let mut layout_job = self
155 .text
156 .into_layout_job(ui.style(), FontSelection::Default, valign);
157
158 let available_width = ui.available_width();
159
160 let wrap_mode = self.wrap_mode.unwrap_or_else(|| ui.wrap_mode());
161 if wrap_mode == TextWrapMode::Wrap
162 && ui.layout().main_dir() == Direction::LeftToRight
163 && ui.layout().main_wrap()
164 && available_width.is_finite()
165 {
166 let cursor = ui.cursor();
170 let first_row_indentation = available_width - ui.available_size_before_wrap().x;
171 debug_assert!(first_row_indentation.is_finite());
172
173 layout_job.wrap.max_width = available_width;
174 layout_job.first_row_min_height = cursor.height();
175 layout_job.halign = Align::Min;
176 layout_job.justify = false;
177 if let Some(first_section) = layout_job.sections.first_mut() {
178 first_section.leading_space = first_row_indentation;
179 }
180 let galley = ui.fonts(|fonts| fonts.layout_job(layout_job));
181
182 let pos = pos2(ui.max_rect().left(), ui.cursor().top());
183 assert!(!galley.rows.is_empty(), "Galleys are never empty");
184 let rect = galley.rows[0].rect.translate(vec2(pos.x, pos.y));
186 let mut response = ui.allocate_rect(rect, sense);
187 for row in galley.rows.iter().skip(1) {
188 let rect = row.rect.translate(vec2(pos.x, pos.y));
189 response |= ui.allocate_rect(rect, sense);
190 }
191 (pos, galley, response)
192 } else {
193 match wrap_mode {
196 TextWrapMode::Extend => {
197 layout_job.wrap.max_width = f32::INFINITY;
198 }
199 TextWrapMode::Wrap => {
200 layout_job.wrap.max_width = available_width;
201 }
202 TextWrapMode::Truncate => {
203 layout_job.wrap.max_width = available_width;
204 layout_job.wrap.max_rows = 1;
205 layout_job.wrap.break_anywhere = true;
206 }
207 }
208
209 if ui.is_grid() {
210 layout_job.halign = Align::LEFT;
212 layout_job.justify = false;
213 } else {
214 layout_job.halign = ui.layout().horizontal_placement();
215 layout_job.justify = ui.layout().horizontal_justify();
216 };
217
218 let galley = ui.fonts(|fonts| fonts.layout_job(layout_job));
219 let (rect, response) = ui.allocate_exact_size(galley.size(), sense);
220 let galley_pos = match galley.job.halign {
221 Align::LEFT => rect.left_top(),
222 Align::Center => rect.center_top(),
223 Align::RIGHT => rect.right_top(),
224 };
225 (galley_pos, galley, response)
226 }
227 }
228}
229
230impl Widget for Label {
231 fn ui(self, ui: &mut Ui) -> Response {
232 let interactive = self.sense.map_or(false, |sense| sense != Sense::hover());
236
237 let selectable = self.selectable;
238
239 let (galley_pos, galley, mut response) = self.layout_in_ui(ui);
240 response
241 .widget_info(|| WidgetInfo::labeled(WidgetType::Label, ui.is_enabled(), galley.text()));
242
243 if ui.is_rect_visible(response.rect) {
244 if galley.elided {
245 response = response.on_hover_text(galley.text());
247 }
248
249 let response_color = if interactive {
250 ui.style().interact(&response).text_color()
251 } else {
252 ui.style().visuals.text_color()
253 };
254
255 let underline = if response.has_focus() || response.highlighted() {
256 Stroke::new(1.0, response_color)
257 } else {
258 Stroke::NONE
259 };
260
261 ui.painter().add(
262 epaint::TextShape::new(galley_pos, galley.clone(), response_color)
263 .with_underline(underline),
264 );
265
266 let selectable = selectable.unwrap_or_else(|| ui.style().interaction.selectable_labels);
267 if selectable {
268 LabelSelectionState::label_text_selection(ui, &response, galley_pos, &galley);
269 }
270 }
271
272 response
273 }
274}