use {Color, Colorable, FontSize, Ui, Widget};
use position::{Dimension, Scalar};
use std;
use text;
use utils;
use widget;
pub struct Text<'a> {
pub common: widget::CommonBuilder,
pub text: &'a str,
pub style: Style,
}
widget_style!{
style Style {
- font_size: FontSize { theme.font_size_medium }
- color: Color { theme.label_color }
- maybe_wrap: Option<Wrap> { Some(Wrap::Whitespace) }
- line_spacing: Scalar { 1.0 }
- justify: text::Justify { text::Justify::Left }
- font_id: Option<text::font::Id> { theme.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::new(),
text: text,
style: Style::new(),
}
}
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 common(&self) -> &widget::CommonBuilder {
&self.common
}
fn common_mut(&mut self) -> &mut widget::CommonBuilder {
&mut self.common
}
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 utils::write_if_different;
use std::borrow::Cow;
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
}
}