use position::{Dimension, Scalar};
use std;
use text;
use utils;
use widget;
use {Color, Colorable, FontSize, Ui, Widget};
#[derive(Clone, Debug, WidgetCommon_)]
pub struct Text<'a> {
#[conrod(common_builder)]
pub common: widget::CommonBuilder,
pub text: &'a str,
pub style: Style,
}
#[derive(Copy, Clone, Debug, Default, PartialEq, WidgetStyle_)]
pub struct Style {
#[conrod(default = "theme.font_size_medium")]
pub font_size: Option<FontSize>,
#[conrod(default = "theme.label_color")]
pub color: Option<Color>,
#[conrod(default = "Some(Wrap::Whitespace)")]
pub maybe_wrap: Option<Option<Wrap>>,
#[conrod(default = "1.0")]
pub line_spacing: Option<Scalar>,
#[conrod(default = "text::Justify::Left")]
pub justify: Option<text::Justify>,
#[conrod(default = "theme.font_id")]
pub font_id: Option<Option<text::font::Id>>,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum Wrap {
Character,
Whitespace,
}
#[derive(Clone, Debug, PartialEq)]
pub struct State {
pub string: String,
pub line_infos: Vec<text::line::Info>,
}
impl<'a> Text<'a> {
pub fn new(text: &'a str) -> Self {
Text {
common: widget::CommonBuilder::default(),
text: text,
style: Style::default(),
}
}
pub fn no_line_wrap(mut self) -> Self {
self.style.maybe_wrap = Some(None);
self
}
pub fn wrap_by_word(mut self) -> Self {
self.style.maybe_wrap = Some(Some(Wrap::Whitespace));
self
}
pub fn wrap_by_character(mut self) -> Self {
self.style.maybe_wrap = Some(Some(Wrap::Character));
self
}
pub fn font_id(mut self, font_id: text::font::Id) -> Self {
self.style.font_id = Some(Some(font_id));
self
}
pub fn with_style(mut self, style: Style) -> Self {
self.style = style;
self
}
pub fn left_justify(self) -> Self {
self.justify(text::Justify::Left)
}
pub fn center_justify(self) -> Self {
self.justify(text::Justify::Center)
}
pub fn right_justify(self) -> Self {
self.justify(text::Justify::Right)
}
builder_methods! {
pub font_size { style.font_size = Some(FontSize) }
pub justify { style.justify = Some(text::Justify) }
pub line_spacing { style.line_spacing = Some(Scalar) }
}
}
impl<'a> Widget for Text<'a> {
type State = State;
type Style = Style;
type Event = ();
fn init_state(&self, _: widget::id::Generator) -> Self::State {
State {
string: String::new(),
line_infos: Vec::new(),
}
}
fn style(&self) -> Self::Style {
self.style.clone()
}
fn default_x_dimension(&self, ui: &Ui) -> Dimension {
let font = match self
.style
.font_id(&ui.theme)
.or(ui.fonts.ids().next())
.and_then(|id| ui.fonts.get(id))
{
Some(font) => font,
None => return Dimension::Absolute(0.0),
};
let font_size = self.style.font_size(&ui.theme);
let mut max_width = 0.0;
for line in self.text.lines() {
let width = text::line::width(line, font, font_size);
max_width = utils::partial_max(max_width, width);
}
Dimension::Absolute(max_width)
}
fn default_y_dimension(&self, ui: &Ui) -> Dimension {
use position::Sizeable;
let font = match self
.style
.font_id(&ui.theme)
.or(ui.fonts.ids().next())
.and_then(|id| ui.fonts.get(id))
{
Some(font) => font,
None => return Dimension::Absolute(0.0),
};
let text = &self.text;
let font_size = self.style.font_size(&ui.theme);
let num_lines = match self.style.maybe_wrap(&ui.theme) {
None => text.lines().count(),
Some(wrap) => match self.get_w(ui) {
None => text.lines().count(),
Some(max_w) => match wrap {
Wrap::Character => text::line::infos(text, font, font_size)
.wrap_by_character(max_w)
.count(),
Wrap::Whitespace => text::line::infos(text, font, font_size)
.wrap_by_whitespace(max_w)
.count(),
},
},
};
let line_spacing = self.style.line_spacing(&ui.theme);
let height = text::height(std::cmp::max(num_lines, 1), font_size, line_spacing);
Dimension::Absolute(height)
}
fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
let widget::UpdateArgs {
rect,
state,
style,
ui,
..
} = args;
let Text { text, .. } = self;
let maybe_wrap = style.maybe_wrap(ui.theme());
let font_size = style.font_size(ui.theme());
let font = match style
.font_id(&ui.theme)
.or(ui.fonts.ids().next())
.and_then(|id| ui.fonts.get(id))
{
Some(font) => font,
None => return,
};
let new_line_infos = || match maybe_wrap {
None => text::line::infos(text, font, font_size),
Some(Wrap::Character) => {
text::line::infos(text, font, font_size).wrap_by_character(rect.w())
}
Some(Wrap::Whitespace) => {
text::line::infos(text, font, font_size).wrap_by_whitespace(rect.w())
}
};
if &state.string[..] != text {
state.update(|state| {
state.string = text.to_owned();
state.line_infos = new_line_infos().collect();
});
} else {
use std::borrow::Cow;
use utils::write_if_different;
let maybe_new_line_infos = {
let line_infos = &state.line_infos[..];
match write_if_different(line_infos, new_line_infos()) {
Cow::Owned(new) => Some(new),
_ => None,
}
};
if let Some(new_line_infos) = maybe_new_line_infos {
state.update(|state| state.line_infos = new_line_infos);
}
}
}
}
impl<'a> Colorable for Text<'a> {
fn color(mut self, color: Color) -> Self {
self.style.color = Some(color);
self
}
}