use crate::{gui::Direction, ops::clamp_size, prelude::*, renderer::Rendering};
impl PixState {
#[inline]
pub fn size_of<S: AsRef<str>>(&self, text: S) -> PixResult<(u32, u32)> {
self.renderer
.size_of(text.as_ref(), self.settings.wrap_width)
}
pub fn text<S>(&mut self, text: S) -> PixResult<(u32, u32)>
where
S: AsRef<str>,
{
self.text_transformed(text, None, None, None)
}
pub fn heading<S>(&mut self, text: S) -> PixResult<(u32, u32)>
where
S: AsRef<str>,
{
let s = self;
s.push();
s.renderer.font_family(&s.theme.fonts.heading)?;
s.renderer.font_size(s.theme.font_size + 6)?;
s.renderer.font_style(s.theme.styles.heading);
let size = s.text_transformed(text, None, None, None);
s.pop();
size
}
pub fn monospace<S>(&mut self, text: S) -> PixResult<(u32, u32)>
where
S: AsRef<str>,
{
let s = self;
s.push();
s.renderer.font_family(&s.theme.fonts.monospace)?;
s.renderer.font_size(s.theme.font_size + 2)?;
s.renderer.font_style(s.theme.styles.monospace);
let size = s.text_transformed(text, None, None, None);
s.pop();
size
}
pub fn text_transformed<S, A, C, F>(
&mut self,
text: S,
angle: A,
center: C,
flipped: F,
) -> PixResult<(u32, u32)>
where
S: AsRef<str>,
A: Into<Option<f64>>,
C: Into<Option<Point<i32>>>,
F: Into<Option<Flipped>>,
{
let text = text.as_ref();
let angle = angle.into();
let center = center.into();
let flipped = flipped.into();
let s = &self.settings;
let fill = s.fill.unwrap_or(Color::TRANSPARENT);
let rect = {
let stroke_size = match (s.stroke, s.stroke_weight) {
(Some(stroke), weight) if weight > 0 => {
Some(self.render_text(text, stroke, weight, angle, center, flipped)?)
}
_ => None,
};
let text_size = self.render_text(text, fill, 0, angle, center, flipped)?;
stroke_size.unwrap_or(text_size)
};
let rect = rect.offset_size([3, 3]);
self.advance_cursor(rect.size());
Ok((rect.width() as u32, rect.height() as u32))
}
pub fn bullet<S>(&mut self, text: S) -> PixResult<(u32, u32)>
where
S: AsRef<str>,
{
let s = self;
let ipad = s.theme.spacing.item_pad;
let font_size = clamp_size(s.theme.font_size);
let pos = s.cursor_pos();
let r = font_size / 5;
s.push();
s.ellipse_mode(EllipseMode::Corner);
s.circle([pos.x() + ipad.x(), pos.y() + font_size / 2, r])?;
s.pop();
s.set_cursor_pos([pos.x() + ipad.x() + 2 * r + 2 * ipad.x(), pos.y()]);
let (w, h) = s.text_transformed(text, 0.0, None, None)?;
Ok((w + r as u32, h))
}
pub fn menu<S>(&mut self, text: S) -> PixResult<bool>
where
S: AsRef<str>,
{
let text = text.as_ref();
let s = self;
let id = s.ui.get_id(&text);
let text = s.ui.get_label(text);
let pos = s.cursor_pos();
let fpad = s.theme.spacing.frame_pad;
let (width, height) = s.text_size(text)?;
let width = s.ui.next_width.take().unwrap_or(width + 2 * fpad.x());
let hover = rect![pos, width, height + 2 * fpad.y()];
let hovered = s.focused() && s.ui.try_hover(id, &hover);
let focused = s.focused() && s.ui.try_focus(id);
let active = s.ui.is_active(id);
s.push();
s.ui.push_cursor();
let [stroke, bg, fg] = if hovered {
s.widget_colors(id, ColorType::Secondary)
} else {
s.widget_colors(id, ColorType::Background)
};
if active || focused {
s.stroke(stroke);
} else {
s.stroke(None);
}
if hovered {
s.frame_cursor(&Cursor::hand())?;
s.fill(bg);
} else {
s.fill(None);
}
s.rect(hover)?;
s.stroke(None);
s.fill(fg);
s.set_cursor_pos([hover.x() + fpad.x(), hover.y() + fpad.y()]);
s.text_transformed(text, 0.0, None, None)?;
s.ui.pop_cursor();
s.pop();
s.ui.handle_focus(id);
s.advance_cursor(hover.size());
Ok(!s.ui.disabled && s.ui.was_clicked(id))
}
pub fn collapsing_tree<S, F>(&mut self, text: S, f: F) -> PixResult<bool>
where
S: AsRef<str>,
F: FnOnce(&mut PixState) -> PixResult<()>,
{
let text = text.as_ref();
let s = self;
let id = s.ui.get_id(&text);
let text = s.ui.get_label(text);
let font_size = clamp_size(s.theme.font_size);
let pos = s.cursor_pos();
let fpad = s.theme.spacing.frame_pad;
let ipad = s.theme.spacing.item_pad;
let expanded = s.ui.expanded(id);
let arrow_width = font_size / 2;
let (width, height) = s.text_size(text)?;
let column_offset = s.ui.column_offset();
let width =
s.ui.next_width
.take()
.unwrap_or_else(|| s.ui_width().unwrap_or(width));
let hover = rect![pos, width - column_offset, height + 2 * fpad.y()];
let hovered = s.focused() && s.ui.try_hover(id, &hover);
let focused = s.focused() && s.ui.try_focus(id);
let active = s.ui.is_active(id);
s.push();
let [stroke, bg, fg] = if hovered {
s.widget_colors(id, ColorType::Secondary)
} else {
s.widget_colors(id, ColorType::Background)
};
if active || focused {
s.stroke(stroke);
} else {
s.stroke(None);
}
if hovered {
s.frame_cursor(&Cursor::hand())?;
s.fill(bg);
} else {
s.fill(None);
}
s.rect(hover)?;
s.stroke(None);
s.fill(fg);
if expanded {
s.arrow(hover.top_left() + fpad, Direction::Down, 1.0)?;
} else {
s.arrow(hover.top_left() + fpad, Direction::Right, 1.0)?;
}
let bullet_offset = arrow_width + 3 * ipad.x();
s.set_cursor_pos([hover.x() + bullet_offset, hover.y() + fpad.y()]);
s.text_transformed(text, 0.0, None, None)?;
s.pop();
if (hovered && s.ui.was_clicked(id)) || (focused && s.ui.key_entered() == Some(Key::Return))
{
s.ui.set_expanded(id, !expanded);
}
s.ui.handle_focus(id);
s.advance_cursor([hover.width(), ipad.y() / 2]);
if expanded {
let (indent_width, _) = s.text_size(" ")?;
s.ui.set_column_offset(indent_width);
f(s)?;
s.ui.reset_column_offset();
}
Ok(expanded)
}
pub fn collapsing_header<S, F>(&mut self, text: S, f: F) -> PixResult<bool>
where
S: AsRef<str>,
F: FnOnce(&mut PixState) -> PixResult<()>,
{
let text = text.as_ref();
let s = self;
let id = s.ui.get_id(&text);
let text = s.ui.get_label(text);
let font_size = clamp_size(s.theme.font_size);
let pos = s.cursor_pos();
let fpad = s.theme.spacing.frame_pad;
let ipad = s.theme.spacing.item_pad;
let expanded = s.ui.expanded(id);
let arrow_width = font_size / 2;
let (width, height) = s.text_size(text)?;
let column_offset = s.ui.column_offset();
let width =
s.ui.next_width
.take()
.unwrap_or_else(|| s.ui_width().unwrap_or(width));
let hover = rect![pos, width - column_offset, height + 2 * fpad.y()];
let hovered = s.focused() && s.ui.try_hover(id, &hover);
let focused = s.focused() && s.ui.try_focus(id);
let active = s.ui.is_active(id);
s.push();
let [stroke, bg, fg] = s.widget_colors(id, ColorType::Secondary);
if active || focused {
s.stroke(stroke);
} else {
s.stroke(None);
}
if hovered {
s.frame_cursor(&Cursor::hand())?;
}
s.fill(bg);
s.rect(hover)?;
s.stroke(None);
s.fill(fg);
if expanded {
s.arrow(hover.top_left() + fpad, Direction::Down, 1.0)?;
} else {
s.arrow(hover.top_left() + fpad, Direction::Right, 1.0)?;
}
let bullet_offset = arrow_width + 3 * ipad.x();
s.set_cursor_pos([hover.x() + bullet_offset, hover.y() + fpad.y()]);
s.text_transformed(text, 0.0, None, None)?;
s.pop();
if (hovered && s.ui.was_clicked(id)) || (focused && s.ui.key_entered() == Some(Key::Return))
{
s.ui.set_expanded(id, !expanded);
}
s.ui.handle_focus(id);
s.advance_cursor([hover.width(), ipad.y() / 2]);
if expanded {
f(s)?;
}
Ok(expanded)
}
}
impl PixState {
#[inline]
fn render_text(
&mut self,
text: &str,
color: Color,
outline: u16,
angle: Option<f64>,
center: Option<Point<i32>>,
flipped: Option<Flipped>,
) -> PixResult<Rect<i32>> {
let s = &self.settings;
let wrap_width = s.wrap_width;
let angle_mode = s.angle_mode;
let colors = self.theme.colors;
let ipad = self.theme.spacing.item_pad;
let mut pos = self.cursor_pos();
if s.rect_mode == RectMode::Center {
let (width, height) = self.size_of(text)?;
pos.offset([-(clamp_size(width) / 2), -(clamp_size(height) / 2)]);
};
if outline == 0 && s.stroke_weight > 0 {
pos += i32::from(s.stroke_weight);
}
self.push();
let color = if self.ui.disabled {
color.blended(colors.background, 0.38)
} else {
color
};
let wrap_width = if wrap_width.is_none() && text.contains('\n') {
text.lines()
.map(|line| {
let (line_width, _) = self.renderer.size_of(line, None).unwrap_or_default();
line_width
})
.max()
.map(|width| width + (pos.x() + ipad.x()) as u32)
} else {
wrap_width
};
let rect = if matches!(angle, Some(angle) if angle != 0.0) {
let angle = if angle_mode == AngleMode::Radians {
angle.map(f64::to_degrees)
} else {
angle
};
let (width, height) = self.renderer.size_of(text, wrap_width)?;
let rect = rect![0, 0, clamp_size(width), clamp_size(height)];
let rect = angle.map_or(rect, |angle| rect.rotated(angle.to_radians(), center));
self.renderer.text(
(pos - rect.top_left()).into(),
text,
wrap_width,
angle,
center,
flipped,
Some(color),
outline,
)?;
rect![pos, rect.width() + rect.left(), rect.height() + rect.top()]
} else {
let (width, height) = self.renderer.text(
pos,
text,
wrap_width,
None,
center,
flipped,
Some(color),
outline,
)?;
rect![pos, clamp_size(width), clamp_size(height)]
};
self.pop();
Ok(rect)
}
}