1use crate::{gui::Direction, ops::clamp_size, prelude::*, renderer::Rendering};
30
31impl PixState {
32 #[inline]
55 pub fn size_of<S: AsRef<str>>(&self, text: S) -> PixResult<(u32, u32)> {
56 self.renderer
57 .size_of(text.as_ref(), self.settings.wrap_width)
58 }
59
60 pub fn text<S>(&mut self, text: S) -> PixResult<(u32, u32)>
82 where
83 S: AsRef<str>,
84 {
85 self.text_transformed(text, None, None, None)
86 }
87
88 pub fn heading<S>(&mut self, text: S) -> PixResult<(u32, u32)>
110 where
111 S: AsRef<str>,
112 {
113 let s = self;
114 s.push();
115 s.renderer.font_family(&s.theme.fonts.heading)?;
116 s.renderer.font_size(s.theme.font_size + 6)?;
117 s.renderer.font_style(s.theme.styles.heading);
118 let size = s.text_transformed(text, None, None, None);
119 s.pop();
120 size
121 }
122
123 pub fn monospace<S>(&mut self, text: S) -> PixResult<(u32, u32)>
145 where
146 S: AsRef<str>,
147 {
148 let s = self;
149 s.push();
150 s.renderer.font_family(&s.theme.fonts.monospace)?;
151 s.renderer.font_size(s.theme.font_size + 2)?;
152 s.renderer.font_style(s.theme.styles.monospace);
153 let size = s.text_transformed(text, None, None, None);
154 s.pop();
155 size
156 }
157
158 pub fn text_transformed<S, A, C, F>(
184 &mut self,
185 text: S,
186 angle: A,
187 center: C,
188 flipped: F,
189 ) -> PixResult<(u32, u32)>
190 where
191 S: AsRef<str>,
192 A: Into<Option<f64>>,
193 C: Into<Option<Point<i32>>>,
194 F: Into<Option<Flipped>>,
195 {
196 let text = text.as_ref();
197 let angle = angle.into();
198 let center = center.into();
199 let flipped = flipped.into();
200
201 let s = &self.settings;
202 let fill = s.fill.unwrap_or(Color::TRANSPARENT);
203
204 let rect = {
205 let stroke_size = match (s.stroke, s.stroke_weight) {
206 (Some(stroke), weight) if weight > 0 => {
207 Some(self.render_text(text, stroke, weight, angle, center, flipped)?)
208 }
209 _ => None,
210 };
211 let text_size = self.render_text(text, fill, 0, angle, center, flipped)?;
212 stroke_size.unwrap_or(text_size)
213 };
214 let rect = rect.offset_size([3, 3]);
216 self.advance_cursor(rect.size());
217
218 Ok((rect.width() as u32, rect.height() as u32))
219 }
220
221 pub fn bullet<S>(&mut self, text: S) -> PixResult<(u32, u32)>
243 where
244 S: AsRef<str>,
245 {
246 let s = self;
247 let ipad = s.theme.spacing.item_pad;
248 let font_size = clamp_size(s.theme.font_size);
249 let pos = s.cursor_pos();
250
251 let r = font_size / 5;
252
253 s.push();
254 s.ellipse_mode(EllipseMode::Corner);
255 s.circle([pos.x() + ipad.x(), pos.y() + font_size / 2, r])?;
256 s.pop();
257
258 s.set_cursor_pos([pos.x() + ipad.x() + 2 * r + 2 * ipad.x(), pos.y()]);
259 let (w, h) = s.text_transformed(text, 0.0, None, None)?;
260
261 Ok((w + r as u32, h))
262 }
263
264 pub fn menu<S>(&mut self, text: S) -> PixResult<bool>
270 where
271 S: AsRef<str>,
272 {
273 let text = text.as_ref();
274
275 let s = self;
276 let id = s.ui.get_id(&text);
277 let text = s.ui.get_label(text);
278 let pos = s.cursor_pos();
279 let fpad = s.theme.spacing.frame_pad;
280
281 let (width, height) = s.text_size(text)?;
283 let width = s.ui.next_width.take().unwrap_or(width + 2 * fpad.x());
284
285 let hover = rect![pos, width, height + 2 * fpad.y()];
286 let hovered = s.focused() && s.ui.try_hover(id, &hover);
287 let focused = s.focused() && s.ui.try_focus(id);
288 let active = s.ui.is_active(id);
289
290 s.push();
291 s.ui.push_cursor();
292
293 let [stroke, bg, fg] = if hovered {
295 s.widget_colors(id, ColorType::Secondary)
296 } else {
297 s.widget_colors(id, ColorType::Background)
298 };
299
300 if active || focused {
301 s.stroke(stroke);
302 } else {
303 s.stroke(None);
304 }
305 if hovered {
306 s.frame_cursor(&Cursor::hand())?;
307 s.fill(bg);
308 } else {
309 s.fill(None);
310 }
311 s.rect(hover)?;
312
313 s.stroke(None);
315 s.fill(fg);
316 s.set_cursor_pos([hover.x() + fpad.x(), hover.y() + fpad.y()]);
317 s.text_transformed(text, 0.0, None, None)?;
318
319 s.ui.pop_cursor();
320 s.pop();
321
322 s.ui.handle_focus(id);
324 s.advance_cursor(hover.size());
325 Ok(!s.ui.disabled && s.ui.was_clicked(id))
326 }
327
328 pub fn collapsing_tree<S, F>(&mut self, text: S, f: F) -> PixResult<bool>
335 where
336 S: AsRef<str>,
337 F: FnOnce(&mut PixState) -> PixResult<()>,
338 {
339 let text = text.as_ref();
340
341 let s = self;
342 let id = s.ui.get_id(&text);
343 let text = s.ui.get_label(text);
344 let font_size = clamp_size(s.theme.font_size);
345 let pos = s.cursor_pos();
346 let fpad = s.theme.spacing.frame_pad;
347 let ipad = s.theme.spacing.item_pad;
348 let expanded = s.ui.expanded(id);
349 let arrow_width = font_size / 2;
350
351 let (width, height) = s.text_size(text)?;
353 let column_offset = s.ui.column_offset();
354 let width =
355 s.ui.next_width
356 .take()
357 .unwrap_or_else(|| s.ui_width().unwrap_or(width));
358
359 let hover = rect![pos, width - column_offset, height + 2 * fpad.y()];
360 let hovered = s.focused() && s.ui.try_hover(id, &hover);
361 let focused = s.focused() && s.ui.try_focus(id);
362 let active = s.ui.is_active(id);
363
364 s.push();
365
366 let [stroke, bg, fg] = if hovered {
368 s.widget_colors(id, ColorType::Secondary)
369 } else {
370 s.widget_colors(id, ColorType::Background)
371 };
372
373 if active || focused {
374 s.stroke(stroke);
375 } else {
376 s.stroke(None);
377 }
378 if hovered {
379 s.frame_cursor(&Cursor::hand())?;
380 s.fill(bg);
381 } else {
382 s.fill(None);
383 }
384 s.rect(hover)?;
385
386 s.stroke(None);
388 s.fill(fg);
389 if expanded {
390 s.arrow(hover.top_left() + fpad, Direction::Down, 1.0)?;
391 } else {
392 s.arrow(hover.top_left() + fpad, Direction::Right, 1.0)?;
393 }
394
395 let bullet_offset = arrow_width + 3 * ipad.x();
397 s.set_cursor_pos([hover.x() + bullet_offset, hover.y() + fpad.y()]);
398 s.text_transformed(text, 0.0, None, None)?;
399
400 s.pop();
401
402 if (hovered && s.ui.was_clicked(id)) || (focused && s.ui.key_entered() == Some(Key::Return))
404 {
405 s.ui.set_expanded(id, !expanded);
406 }
407 s.ui.handle_focus(id);
408
409 s.advance_cursor([hover.width(), ipad.y() / 2]);
410
411 if expanded {
412 let (indent_width, _) = s.text_size(" ")?;
413 s.ui.set_column_offset(indent_width);
414 f(s)?;
415 s.ui.reset_column_offset();
416 }
417
418 Ok(expanded)
419 }
420
421 pub fn collapsing_header<S, F>(&mut self, text: S, f: F) -> PixResult<bool>
428 where
429 S: AsRef<str>,
430 F: FnOnce(&mut PixState) -> PixResult<()>,
431 {
432 let text = text.as_ref();
433
434 let s = self;
435 let id = s.ui.get_id(&text);
436 let text = s.ui.get_label(text);
437 let font_size = clamp_size(s.theme.font_size);
438 let pos = s.cursor_pos();
439 let fpad = s.theme.spacing.frame_pad;
440 let ipad = s.theme.spacing.item_pad;
441 let expanded = s.ui.expanded(id);
442 let arrow_width = font_size / 2;
443
444 let (width, height) = s.text_size(text)?;
446 let column_offset = s.ui.column_offset();
447 let width =
448 s.ui.next_width
449 .take()
450 .unwrap_or_else(|| s.ui_width().unwrap_or(width));
451
452 let hover = rect![pos, width - column_offset, height + 2 * fpad.y()];
453 let hovered = s.focused() && s.ui.try_hover(id, &hover);
454 let focused = s.focused() && s.ui.try_focus(id);
455 let active = s.ui.is_active(id);
456
457 s.push();
458
459 let [stroke, bg, fg] = s.widget_colors(id, ColorType::Secondary);
460 if active || focused {
461 s.stroke(stroke);
462 } else {
463 s.stroke(None);
464 }
465 if hovered {
466 s.frame_cursor(&Cursor::hand())?;
467 }
468 s.fill(bg);
469 s.rect(hover)?;
470
471 s.stroke(None);
473 s.fill(fg);
474 if expanded {
475 s.arrow(hover.top_left() + fpad, Direction::Down, 1.0)?;
476 } else {
477 s.arrow(hover.top_left() + fpad, Direction::Right, 1.0)?;
478 }
479
480 let bullet_offset = arrow_width + 3 * ipad.x();
482 s.set_cursor_pos([hover.x() + bullet_offset, hover.y() + fpad.y()]);
483 s.text_transformed(text, 0.0, None, None)?;
484
485 s.pop();
486
487 if (hovered && s.ui.was_clicked(id)) || (focused && s.ui.key_entered() == Some(Key::Return))
489 {
490 s.ui.set_expanded(id, !expanded);
491 }
492 s.ui.handle_focus(id);
493
494 s.advance_cursor([hover.width(), ipad.y() / 2]);
495
496 if expanded {
497 f(s)?;
498 }
499
500 Ok(expanded)
501 }
502}
503
504impl PixState {
505 #[inline]
506 fn render_text(
507 &mut self,
508 text: &str,
509 color: Color,
510 outline: u16,
511 angle: Option<f64>,
512 center: Option<Point<i32>>,
513 flipped: Option<Flipped>,
514 ) -> PixResult<Rect<i32>> {
515 let s = &self.settings;
516 let wrap_width = s.wrap_width;
517 let angle_mode = s.angle_mode;
518 let colors = self.theme.colors;
519 let ipad = self.theme.spacing.item_pad;
520
521 let mut pos = self.cursor_pos();
522 if s.rect_mode == RectMode::Center {
523 let (width, height) = self.size_of(text)?;
524 pos.offset([-(clamp_size(width) / 2), -(clamp_size(height) / 2)]);
525 };
526 if outline == 0 && s.stroke_weight > 0 {
527 pos += i32::from(s.stroke_weight);
528 }
529
530 self.push();
531
532 let color = if self.ui.disabled {
533 color.blended(colors.background, 0.38)
534 } else {
535 color
536 };
537 let wrap_width = if wrap_width.is_none() && text.contains('\n') {
538 text.lines()
539 .map(|line| {
540 let (line_width, _) = self.renderer.size_of(line, None).unwrap_or_default();
541 line_width
542 })
543 .max()
544 .map(|width| width + (pos.x() + ipad.x()) as u32)
545 } else {
546 wrap_width
547 };
548 let rect = if matches!(angle, Some(angle) if angle != 0.0) {
549 let angle = if angle_mode == AngleMode::Radians {
550 angle.map(f64::to_degrees)
551 } else {
552 angle
553 };
554 let (width, height) = self.renderer.size_of(text, wrap_width)?;
555 let rect = rect![0, 0, clamp_size(width), clamp_size(height)];
556 let rect = angle.map_or(rect, |angle| rect.rotated(angle.to_radians(), center));
557 self.renderer.text(
558 (pos - rect.top_left()).into(),
559 text,
560 wrap_width,
561 angle,
562 center,
563 flipped,
564 Some(color),
565 outline,
566 )?;
567 rect![pos, rect.width() + rect.left(), rect.height() + rect.top()]
568 } else {
569 let (width, height) = self.renderer.text(
570 pos,
571 text,
572 wrap_width,
573 None,
574 center,
575 flipped,
576 Some(color),
577 outline,
578 )?;
579 rect![pos, clamp_size(width), clamp_size(height)]
580 };
581
582 self.pop();
583 Ok(rect)
584 }
585}