use crate::*;
use epaint::Galley;
use std::sync::Arc;
#[must_use = "You should put this widget in an ui with `ui.add(widget);`"]
pub struct Label {
pub(crate) text: String,
pub(crate) wrap: Option<bool>,
pub(crate) text_style: Option<TextStyle>,
pub(crate) background_color: Color32,
pub(crate) text_color: Option<Color32>,
code: bool,
strong: bool,
weak: bool,
strikethrough: bool,
underline: bool,
italics: bool,
raised: bool,
sense: Sense,
}
impl Label {
#[allow(clippy::needless_pass_by_value)]
pub fn new(text: impl ToString) -> Self {
Self {
text: text.to_string(),
wrap: None,
text_style: None,
background_color: Color32::TRANSPARENT,
text_color: None,
code: false,
strong: false,
weak: false,
strikethrough: false,
underline: false,
italics: false,
raised: false,
sense: Sense::focusable_noninteractive(),
}
}
pub fn text(&self) -> &str {
&self.text
}
pub fn wrap(mut self, wrap: bool) -> Self {
self.wrap = Some(wrap);
self
}
#[deprecated = "Use Label::wrap instead"]
pub fn multiline(self, multiline: bool) -> Self {
self.wrap(multiline)
}
pub fn text_style(mut self, text_style: TextStyle) -> Self {
self.text_style = Some(text_style);
self
}
pub fn heading(self) -> Self {
self.text_style(TextStyle::Heading)
}
pub fn monospace(self) -> Self {
self.text_style(TextStyle::Monospace)
}
pub fn code(mut self) -> Self {
self.code = true;
self.text_style(TextStyle::Monospace)
}
pub fn strong(mut self) -> Self {
self.strong = true;
self
}
pub fn weak(mut self) -> Self {
self.weak = true;
self
}
pub fn underline(mut self) -> Self {
self.underline = true;
self
}
pub fn strikethrough(mut self) -> Self {
self.strikethrough = true;
self
}
pub fn italics(mut self) -> Self {
self.italics = true;
self
}
pub fn small(self) -> Self {
self.text_style(TextStyle::Small)
}
pub fn small_raised(self) -> Self {
self.text_style(TextStyle::Small).raised()
}
pub fn raised(mut self) -> Self {
self.raised = true;
self
}
pub fn background_color(mut self, background_color: impl Into<Color32>) -> Self {
self.background_color = background_color.into();
self
}
pub fn text_color(mut self, text_color: impl Into<Color32>) -> Self {
self.text_color = Some(text_color.into());
self
}
pub fn sense(mut self, sense: Sense) -> Self {
self.sense = sense;
self
}
}
impl Label {
pub fn layout(&self, ui: &Ui) -> Arc<Galley> {
let max_width = ui.available_width();
self.layout_width(ui, max_width)
}
pub fn layout_width(&self, ui: &Ui, max_width: f32) -> Arc<Galley> {
let text_style = self.text_style_or_default(ui.style());
let wrap_width = if self.should_wrap(ui) {
max_width
} else {
f32::INFINITY
};
let galley = ui
.fonts()
.layout_multiline(text_style, self.text.clone(), wrap_width); self.valign_galley(ui, text_style, galley)
}
pub fn font_height(&self, fonts: &epaint::text::Fonts, style: &Style) -> f32 {
let text_style = self.text_style_or_default(style);
fonts.row_height(text_style)
}
pub fn paint_galley(&self, ui: &mut Ui, pos: Pos2, galley: Arc<Galley>) {
self.paint_galley_impl(ui, pos, galley, false, ui.visuals().text_color())
}
fn paint_galley_impl(
&self,
ui: &mut Ui,
pos: Pos2,
galley: Arc<Galley>,
has_focus: bool,
response_color: Color32,
) {
let Self {
mut background_color,
code,
strong,
weak,
strikethrough,
underline,
italics,
raised: _,
..
} = *self;
let underline = underline || has_focus;
let text_color = if let Some(text_color) = self.text_color {
text_color
} else if strong {
ui.visuals().strong_text_color()
} else if weak {
ui.visuals().weak_text_color()
} else {
response_color
};
if code {
background_color = ui.visuals().code_bg_color;
}
let mut lines = vec![];
if strikethrough || underline || background_color != Color32::TRANSPARENT {
for row in &galley.rows {
let rect = row.rect().translate(pos.to_vec2());
if background_color != Color32::TRANSPARENT {
let rect = rect.expand(1.0); ui.painter().rect_filled(rect, 0.0, background_color);
}
let stroke_width = 1.0;
if strikethrough {
lines.push(Shape::line_segment(
[rect.left_center(), rect.right_center()],
(stroke_width, text_color),
));
}
if underline {
lines.push(Shape::line_segment(
[rect.left_bottom(), rect.right_bottom()],
(stroke_width, text_color),
));
}
}
}
ui.painter()
.galley_with_italics(pos, galley, text_color, italics);
ui.painter().extend(lines);
}
pub fn text_style_or_default(&self, style: &Style) -> TextStyle {
self.text_style.unwrap_or(style.body_text_style)
}
fn should_wrap(&self, ui: &Ui) -> bool {
self.wrap.or(ui.style().wrap).unwrap_or_else(|| {
if let Some(grid) = ui.grid() {
grid.wrap_text()
} else {
let layout = ui.layout();
layout.is_vertical() || layout.is_horizontal() && layout.main_wrap()
}
})
}
fn valign_galley(
&self,
ui: &Ui,
text_style: TextStyle,
mut galley: Arc<Galley>,
) -> Arc<Galley> {
if text_style == TextStyle::Small {
let dy = if self.raised {
-2.0
} else {
let normal_text_height = ui.fonts()[TextStyle::Body].row_height();
let font_height = ui.fonts().row_height(text_style);
(normal_text_height - font_height) / 2.0 - 1.0
};
if dy != 0.0 {
for row in &mut Arc::make_mut(&mut galley).rows {
row.translate_y(dy);
}
}
}
galley
}
}
impl Widget for Label {
fn ui(self, ui: &mut Ui) -> Response {
let sense = self.sense;
if self.should_wrap(ui)
&& ui.layout().main_dir() == Direction::LeftToRight
&& ui.layout().main_wrap()
{
let cursor = ui.cursor();
let max_width = ui.available_width();
let first_row_indentation = max_width - ui.available_size_before_wrap().x;
let text_style = self.text_style_or_default(ui.style());
let galley = ui.fonts().layout_multiline_with_indentation_and_max_width(
text_style,
self.text.clone(),
first_row_indentation,
max_width,
);
let mut galley: Galley = (*galley).clone();
let pos = pos2(ui.max_rect().left(), ui.cursor().top());
assert!(!galley.rows.is_empty(), "Galleys are never empty");
let dy = 0.5 * (cursor.height() - galley.rows[0].height());
galley.rows[0].translate_y(dy);
if let Some(row) = galley.rows.get_mut(1) {
if pos.y + row.y_min < cursor.bottom() {
let y_translation = cursor.bottom() - row.y_min - pos.y;
if y_translation != 0.0 {
for row in galley.rows.iter_mut().skip(1) {
row.translate_y(y_translation);
}
}
}
}
let galley = self.valign_galley(ui, text_style, Arc::new(galley));
let rect = galley.rows[0].rect().translate(vec2(pos.x, pos.y));
let mut response = ui.allocate_rect(rect, sense);
for row in galley.rows.iter().skip(1) {
let rect = row.rect().translate(vec2(pos.x, pos.y));
response |= ui.allocate_rect(rect, sense);
}
response.widget_info(|| WidgetInfo::labeled(WidgetType::Label, &galley.text));
let response_color = ui.style().interact(&response).text_color();
self.paint_galley_impl(ui, pos, galley, response.has_focus(), response_color);
response
} else {
let galley = self.layout(ui);
let (rect, response) = ui.allocate_exact_size(galley.size, sense);
response.widget_info(|| WidgetInfo::labeled(WidgetType::Label, &galley.text));
let response_color = ui.style().interact(&response).text_color();
self.paint_galley_impl(ui, rect.min, galley, response.has_focus(), response_color);
response
}
}
}
impl From<&str> for Label {
fn from(s: &str) -> Label {
Label::new(s)
}
}
impl From<&String> for Label {
fn from(s: &String) -> Label {
Label::new(s)
}
}
impl From<String> for Label {
fn from(s: String) -> Label {
Label::new(s)
}
}