use {Color, Colorable, FontSize, Positionable, Sizeable, Widget, Ui};
use event;
use input;
use position::{Align, Dimension, Point, Range, Rect, Scalar};
use std;
use text;
use utils;
use widget;
use cursor;
use widget::primitive::text::Wrap;
use copypasta::{ClipboardContext, ClipboardProvider};
#[derive(WidgetCommon_)]
pub struct TextEdit<'a> {
#[conrod(common_builder)]
common: widget::CommonBuilder,
text: &'a str,
style: Style,
}
#[derive(Copy, Clone, Debug, Default, PartialEq, WidgetStyle_)]
pub struct Style {
#[conrod(default = "theme.shape_color")]
pub color: Option<Color>,
#[conrod(default = "theme.font_size_medium")]
pub font_size: Option<FontSize>,
#[conrod(default = "text::Justify::Left")]
pub justify: Option<text::Justify>,
#[conrod(default = "Align::End")]
pub y_align: Option<Align>,
#[conrod(default = "1.0")]
pub line_spacing: Option<Scalar>,
#[conrod(default = "Wrap::Whitespace")]
pub line_wrap: Option<Wrap>,
#[conrod(default = "true")]
pub restrict_to_height: Option<bool>,
#[conrod(default = "theme.font_id")]
pub font_id: Option<Option<text::font::Id>>,
}
widget_ids! {
struct Ids {
selected_rectangles[],
text,
cursor,
}
}
pub struct State {
cursor: Cursor,
drag: Option<Drag>,
line_infos: Vec<text::line::Info>,
ids: Ids,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum Drag {
Selecting,
#[allow(dead_code)]
MoveSelection,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Cursor {
Idx(text::cursor::Index),
Selection {
start: text::cursor::Index,
end: text::cursor::Index,
},
}
impl<'a> TextEdit<'a> {
pub fn new(text: &'a str) -> Self {
TextEdit {
common: widget::CommonBuilder::default(),
style: Style::default(),
text: text,
}
}
pub fn wrap_by_whitespace(self) -> Self {
self.line_wrap(Wrap::Whitespace)
}
pub fn wrap_by_character(self) -> Self {
self.line_wrap(Wrap::Character)
}
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)
}
pub fn align_text_bottom(self) -> Self {
self.y_align_text(Align::Start)
}
pub fn align_text_y_middle(self) -> Self {
self.y_align_text(Align::Middle)
}
pub fn align_text_top(self) -> Self {
self.y_align_text(Align::End)
}
pub fn align_text_middle(self) -> Self {
self.center_justify().align_text_y_middle()
}
pub fn font_id(mut self, font_id: text::font::Id) -> Self {
self.style.font_id = Some(Some(font_id));
self
}
builder_methods!{
pub font_size { style.font_size = Some(FontSize) }
pub justify { style.justify = Some(text::Justify) }
pub y_align_text { style.y_align = Some(Align) }
pub line_wrap { style.line_wrap = Some(Wrap) }
pub line_spacing { style.line_spacing = Some(Scalar) }
pub restrict_to_height { style.restrict_to_height = Some(bool) }
}
}
impl<'a> Widget for TextEdit<'a> {
type State = State;
type Style = Style;
type Event = Option<String>;
fn init_state(&self, id_gen: widget::id::Generator) -> Self::State {
State {
cursor: Cursor::Idx(text::cursor::Index { line: 0, char: 0 }),
drag: None,
line_infos: Vec::new(),
ids: Ids::new(id_gen),
}
}
fn style(&self) -> Self::Style {
self.style.clone()
}
fn default_y_dimension(&self, ui: &Ui) -> Dimension {
if self.style.restrict_to_height(&ui.theme) {
return widget::default_y_dimension(self, ui);
}
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.get_w(ui) {
None => text.lines().count(),
Some(max_w) => match self.style.line_wrap(&ui.theme) {
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 { id, state, rect, style, ui, .. } = args;
let TextEdit { text, .. } = self;
let mut text = std::borrow::Cow::Borrowed(text);
let font_id = match style.font_id(&ui.theme)
.or(ui.fonts.ids().next())
.and_then(|id| ui.fonts.get(id).map(|_| id))
{
Some(font_id) => font_id,
None => return None,
};
let font_size = style.font_size(ui.theme());
let line_wrap = style.line_wrap(ui.theme());
let justify = style.justify(ui.theme());
let y_align = style.y_align(ui.theme());
let line_spacing = style.line_spacing(ui.theme());
let restrict_to_height = style.restrict_to_height(ui.theme());
type LineInfos<'a> = text::line::Infos<'a, text::line::NextBreakFnPtr>;
fn line_infos<'a>(text: &'a str,
font: &'a text::Font,
font_size: FontSize,
line_wrap: Wrap,
max_width: Scalar) -> LineInfos<'a>
{
let infos = text::line::infos(text, font, font_size);
match line_wrap {
Wrap::Whitespace => infos.wrap_by_whitespace(max_width),
Wrap::Character => infos.wrap_by_character(max_width),
}
}
{
let maybe_new_line_infos = {
let line_info_slice = &state.line_infos[..];
let font = ui.fonts.get(font_id).unwrap();
let new_line_infos = line_infos(&text, font, font_size, line_wrap, rect.w());
match utils::write_if_different(line_info_slice, new_line_infos) {
std::borrow::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);
}
}
match state.cursor {
Cursor::Idx(index) => {
let new_index = index.clamp_to_lines(state.line_infos.iter().cloned());
if index != new_index {
let new_cursor = Cursor::Idx(new_index);
state.update(|state| state.cursor = new_cursor);
}
},
Cursor::Selection { start, end } => {
let new_start = start.clamp_to_lines(state.line_infos.iter().cloned());
let new_end = end.clamp_to_lines(state.line_infos.iter().cloned());
if start != new_start || end != new_end {
let new_cursor = Cursor::Selection { start: new_start, end: new_end };
state.update(|state| state.cursor = new_cursor);
}
},
}
let cursor_xy_at = |cursor_idx: text::cursor::Index,
text: &str,
line_infos: &[text::line::Info],
font: &text::Font|
-> Option<(Scalar, Range)>
{
let xys_per_line = text::cursor::xys_per_line_from_text(text, line_infos, font,
font_size, justify, y_align,
line_spacing, rect);
text::cursor::xy_at(xys_per_line, cursor_idx)
};
let closest_cursor_index_and_xy = |xy: Point,
text: &str,
line_infos: &[text::line::Info],
font: &text::Font|
-> Option<(text::cursor::Index, Point)>
{
let xys_per_line = text::cursor::xys_per_line_from_text(text, line_infos, font,
font_size, justify, y_align,
line_spacing, rect);
text::cursor::closest_cursor_index_and_xy(xy, xys_per_line)
};
let closest_cursor_index_on_line = |x_pos: Scalar,
line_idx: usize,
text: &str,
line_infos: &[text::line::Info],
font: &text::Font| -> Option<text::cursor::Index>
{
let mut xys_per_line = text::cursor::xys_per_line_from_text(text, line_infos, font,
font_size, justify, y_align,
line_spacing, rect);
xys_per_line.nth(line_idx).and_then(|(line_xs,_)| {
let (char_idx,_) = text::cursor::closest_cursor_index_on_line(x_pos,line_xs);
Some(text::cursor::Index { line: line_idx, char: char_idx })
})
};
let mut cursor = state.cursor;
let mut drag = state.drag;
let insert_text = |string: &str,
cursor: Cursor,
text: &str,
infos: &[text::line::Info],
font: &text::Font|
-> Option<(String, Cursor, std::vec::Vec<text::line::Info>)>
{
let string_char_count = string.chars().count();
let (new_text, new_cursor_char_idx): (String, usize) = {
let (cursor_start, cursor_end) = match cursor {
Cursor::Idx(idx) => (idx, idx),
Cursor::Selection { start, end } =>
(std::cmp::min(start, end), std::cmp::max(start, end)),
};
let line_infos = infos.iter().cloned();
let (start_idx, end_idx) =
(text::glyph::index_after_cursor(line_infos.clone(), cursor_start)
.unwrap_or(0),
text::glyph::index_after_cursor(line_infos.clone(), cursor_end)
.unwrap_or(0));
let new_cursor_char_idx = start_idx + string_char_count;
let new_text = text.chars().take(start_idx)
.chain(string.chars())
.chain(text.chars().skip(end_idx))
.collect();
(new_text, new_cursor_char_idx)
};
let new_line_infos: Vec<_> = {
line_infos(&new_text, font, font_size, line_wrap, rect.w()).collect()
};
let num_lines = new_line_infos.len();
let height = text::height(num_lines, font_size, line_spacing);
if height < rect.h() || !restrict_to_height {
let new_cursor_idx = {
let line_infos = new_line_infos.iter().cloned();
text::cursor::index_before_char(line_infos, new_cursor_char_idx)
.unwrap_or(text::cursor::Index {
line: 0,
char: string_char_count,
})
};
Some((new_text, Cursor::Idx(new_cursor_idx), new_line_infos))
} else {
None
}
};
'events: for widget_event in ui.widget_input(id).events() {
match widget_event {
event::Widget::DoubleClick(click) => {
if let input::MouseButton::Left = click.button {
let abs_xy = utils::vec2_add(click.xy, rect.xy());
let infos = &state.line_infos;
let font = ui.fonts.get(font_id).unwrap();
let closest = closest_cursor_index_and_xy(abs_xy, &text, infos, font);
if let Some((cursor_idx, _)) = closest {
let line_infos = state.line_infos.iter().cloned();
let (start, end) = (
cursor_idx.previous_word_start(&text, line_infos.clone())
.unwrap_or(cursor_idx),
cursor_idx.next_word_end(&text, line_infos)
.unwrap_or(cursor_idx),
);
cursor = Cursor::Selection {
start,
end,
};
}
}
}
event::Widget::Press(press) => match press.button {
event::Button::Mouse(input::MouseButton::Left, rel_xy) => {
let abs_xy = utils::vec2_add(rel_xy, rect.xy());
let infos = &state.line_infos;
let font = ui.fonts.get(font_id).unwrap();
let closest = closest_cursor_index_and_xy(abs_xy, &text, infos, font);
if let Some((closest_cursor, _)) = closest {
if press.modifiers.contains(input::keyboard::ModifierKey::SHIFT) {
let (old_selection_start, _) = match cursor {
Cursor::Idx(idx) => (idx, idx),
Cursor::Selection { start, end } => (start, end),
};
cursor = Cursor::Selection {
start: old_selection_start,
end: closest_cursor,
};
} else {
cursor = Cursor::Idx(closest_cursor);
}
}
drag = Some(Drag::Selecting);
}
event::Button::Keyboard(key) => match key {
input::Key::Backspace | input::Key::Delete => {
let delete_word = press.modifiers.contains(input::keyboard::ModifierKey::CTRL);
let (start, end) = match cursor {
Cursor::Idx(cursor_idx) => {
let line_infos = state.line_infos.iter().cloned();
let end = match (key, delete_word) {
(input::Key::Backspace, false) => {
cursor_idx.previous(line_infos)
}
(input::Key::Backspace, true) => {
cursor_idx.previous_word_start(&text, line_infos)
}
(input::Key::Delete, false) => {
cursor_idx.next(line_infos)
}
(input::Key::Delete, true) => {
cursor_idx.next_word_end(&text, line_infos)
}
_ => unreachable!(),
}.unwrap_or(cursor_idx);
(cursor_idx, end)
}
Cursor::Selection { start, end } => (start, end),
};
let (start_idx, end_idx) = {
let line_infos = state.line_infos.iter().cloned();
(text::glyph::index_after_cursor(line_infos.clone(), start),
text::glyph::index_after_cursor(line_infos, end))
};
if let (Some(start_idx), Some(end_idx)) = (start_idx, end_idx) {
let (start_idx, end_idx) = (std::cmp::min(start_idx, end_idx),
std::cmp::max(start_idx, end_idx));
let new_cursor_char_idx =
if start_idx > 0 { start_idx } else { 0 };
let new_cursor_idx = {
let line_infos = state.line_infos.iter().cloned();
text::cursor::index_before_char(line_infos,
new_cursor_char_idx)
.expect("char index was out of range")
};
cursor = Cursor::Idx(new_cursor_idx);
*text.to_mut() = text.chars().take(start_idx)
.chain(text.chars().skip(end_idx))
.collect();
state.update(|state| {
let font = ui.fonts.get(font_id).unwrap();
let w = rect.w();
state.line_infos =
line_infos(&text, font, font_size, line_wrap, w)
.collect();
});
}
},
input::Key::Left | input::Key::Right | input::Key::Up | input::Key::Down => {
let font = ui.fonts.get(font_id).unwrap();
let move_word = press.modifiers.contains(input::keyboard::ModifierKey::CTRL);
let select = press.modifiers.contains(input::keyboard::ModifierKey::SHIFT);
let (old_selection_start, cursor_idx) = match cursor {
Cursor::Idx(idx) => (idx, idx),
Cursor::Selection { start, end } => (start, end),
};
let new_cursor_idx = {
let line_infos = state.line_infos.iter().cloned();
match (key, move_word) {
(input::Key::Left, true) => cursor_idx
.previous_word_start(&text, line_infos),
(input::Key::Right, true) => cursor_idx
.next_word_end(&text, line_infos),
(input::Key::Left, false) => cursor_idx
.previous(line_infos),
(input::Key::Right, false) => cursor_idx
.next(line_infos),
_ => cursor_xy_at(cursor_idx, &text, &state.line_infos, font)
.and_then(|(x_pos, _)| {
let text::cursor::Index { line, .. } = cursor_idx;
let next_line = match key {
input::Key::Up => line.saturating_sub(1),
input::Key::Down => line + 1,
_ => unreachable!(),
};
closest_cursor_index_on_line(x_pos, next_line, &text, &state.line_infos, font)
})
}.unwrap_or(cursor_idx)
};
if select {
cursor = Cursor::Selection {
start: old_selection_start,
end: new_cursor_idx,
};
} else {
match cursor {
Cursor::Idx(_) => {
cursor = Cursor::Idx(new_cursor_idx);
},
Cursor::Selection { start, end } => {
let new_cursor_idx = {
let cursor_idx = match key {
input::Key::Left | input::Key::Up =>
std::cmp::min(start, end),
input::Key::Right | input::Key::Down =>
std::cmp::max(start, end),
_ => unreachable!(),
};
if !move_word {
cursor_idx
} else {
let line_infos = state.line_infos.iter().cloned();
match key {
input::Key::Left | input::Key::Up => {
cursor_idx.previous_word_start(&text, line_infos)
},
input::Key::Right | input::Key::Down => {
cursor_idx.next_word_end(&text, line_infos)
}
_ => unreachable!(),
}.unwrap_or(cursor_idx)
}
};
cursor = Cursor::Idx(new_cursor_idx);
},
}
}
},
input::Key::A => {
if press.modifiers.contains(input::keyboard::ModifierKey::CTRL) {
let start = text::cursor::Index { line: 0, char: 0 };
let end = {
let line_infos = state.line_infos.iter().cloned();
text::cursor::index_before_char(line_infos, text.chars().count())
.expect("char index was out of range")
};
cursor = Cursor::Selection { start: start, end: end };
}
},
input::Key::C => {
if press.modifiers.contains(input::keyboard::ModifierKey::CTRL) {
match cursor {
Cursor::Selection { start, end } => {
let mut clipboard: ClipboardContext = ClipboardContext::new().unwrap();
let (start_idx, end_idx) = {
let line_infos = state.line_infos.iter().cloned();
(
text::glyph::index_after_cursor(
line_infos.clone(),
start,
),
text::glyph::index_after_cursor(line_infos, end),
)
};
if let (Some(start_idx), Some(end_idx)) =
(start_idx, end_idx)
{
let (start_idx, end_idx) = (
std::cmp::min(start_idx, end_idx),
std::cmp::max(start_idx, end_idx),
);
let text_to_copy: String = text
.chars()
.skip(start_idx)
.take(end_idx - start_idx)
.collect();
clipboard.set_contents(text_to_copy).unwrap();
}
}
Cursor::Idx(_) => {}
};
}
}
input::Key::E => {
if press.modifiers.contains(input::keyboard::ModifierKey::CTRL) {
let mut line_infos = state.line_infos.iter().cloned();
let line = line_infos.len() - 1;
match line_infos.nth(line) {
Some(line_info) => {
let char = line_info.end_char() - line_info.start_char;
let new_cursor_idx = text::cursor::Index { line: line, char: char };
cursor = Cursor::Idx(new_cursor_idx);
},
_ => (),
}
}
},
input::Key::V => {
if press.modifiers.contains(input::keyboard::ModifierKey::CTRL) {
let mut clipboard: ClipboardContext = ClipboardContext::new().unwrap();
let font = ui.fonts.get(font_id).unwrap();
let content = &clipboard.get_contents().unwrap_or(String::from(""));
if content.len() > 0 {
match insert_text(
content,
cursor,
&text,
&state.line_infos,
font,
) {
Some((new_text, new_cursor, new_line_infos)) => {
*text.to_mut() = new_text;
cursor = new_cursor;
state.update(|state| state.line_infos = new_line_infos);
}
_ => (),
}
}
}
}
input::Key::End => {
let mut line_infos = state.line_infos.iter().cloned();
let line = match cursor {
Cursor::Idx(idx) => idx.line,
Cursor::Selection {end, ..} => end.line,
};
if let Some(line_info) = line_infos.nth(line) {
let char = line_info.end_char() - line_info.start_char;
let new_cursor_idx = text::cursor::Index { line: line, char: char };
cursor = Cursor::Idx(new_cursor_idx);
}
},
input::Key::Home => {
let mut line_infos = state.line_infos.iter().cloned();
let line = match cursor {
Cursor::Idx(idx) => idx.line,
Cursor::Selection {start, ..} => start.line,
};
if line_infos.nth(line).is_some() {
let char = 0;
let new_cursor_idx = text::cursor::Index { line: line, char: char };
cursor = Cursor::Idx(new_cursor_idx);
}
},
input::Key::Return => {
let font = ui.fonts.get(font_id).unwrap();
match insert_text("\n", cursor, &text, &state.line_infos, font) {
Some((new_text, new_cursor, new_line_infos)) => {
*text.to_mut() = new_text;
cursor = new_cursor;
state.update(|state| state.line_infos = new_line_infos);
}, _ => ()
}
},
_ => (),
},
_ => (),
},
event::Widget::Release(release) => {
if let event::Button::Mouse(input::MouseButton::Left, _) = release.button {
drag = None;
}
},
event::Widget::Text(event::Text { string, modifiers }) => {
if modifiers.contains(input::keyboard::ModifierKey::CTRL)
|| string.chars().count() == 0
|| string.chars().next().is_none() {
continue 'events;
}
match &string[..] {
"\u{f700}" | "\u{f701}" | "\u{f702}" | "\u{f703}" => continue 'events,
_ => ()
}
let font = ui.fonts.get(font_id).unwrap();
match insert_text(&string, cursor, &text, &state.line_infos, font) {
Some((new_text, new_cursor, new_line_infos)) => {
*text.to_mut() = new_text;
cursor = new_cursor;
state.update(|state| state.line_infos = new_line_infos);
}, _ => ()
}
},
event::Widget::Drag(drag_event) if drag_event.button == input::MouseButton::Left => {
match drag {
Some(Drag::Selecting) => {
let start_cursor_idx = match cursor {
Cursor::Idx(idx) => idx,
Cursor::Selection { start, .. } => start,
};
let abs_xy = utils::vec2_add(drag_event.to, rect.xy());
let infos = &state.line_infos;
let font = ui.fonts.get(font_id).unwrap();
match closest_cursor_index_and_xy(abs_xy, &text, infos, font) {
Some((end_cursor_idx, _)) =>
cursor = Cursor::Selection {
start: start_cursor_idx,
end: end_cursor_idx,
},
_ => (),
}
},
Some(Drag::MoveSelection) => {
unimplemented!();
},
None => (),
}
},
_ => (),
}
}
if let Some(_) = ui.widget_input(id).mouse() {
ui.set_mouse_cursor(cursor::MouseCursor::Text);
}
let cursor_has_changed = state.cursor != cursor;
if cursor_has_changed {
state.update(|state| state.cursor = cursor);
}
if state.drag != drag {
state.update(|state| state.drag = drag);
}
fn take_if_owned(text: std::borrow::Cow<str>) -> Option<String> {
match text {
std::borrow::Cow::Borrowed(_) => None,
std::borrow::Cow::Owned(s) => Some(s),
}
}
let color = style.color(ui.theme());
let font_size = style.font_size(ui.theme());
let num_lines = state.line_infos.iter().count();
let text_height = text::height(num_lines, font_size, line_spacing);
let text_y_range = Range::new(0.0, text_height).align_to(y_align, rect.y);
let text_rect = Rect { x: rect.x, y: text_y_range };
match line_wrap {
Wrap::Whitespace => widget::Text::new(&text).wrap_by_word(),
Wrap::Character => widget::Text::new(&text).wrap_by_character(),
}
.font_id(font_id)
.wh(text_rect.dim())
.xy(text_rect.xy())
.justify(justify)
.parent(id)
.graphics_for(id)
.color(color)
.line_spacing(line_spacing)
.font_size(font_size)
.set(state.ids.text, ui);
let cursor_idx = match cursor {
Cursor::Idx(idx) => idx,
Cursor::Selection { end, .. } => end,
};
if ui.global_input().current.widget_capturing_keyboard != Some(id) {
return take_if_owned(text);
}
let (cursor_x, cursor_y_range) = {
let font = ui.fonts.get(font_id).unwrap();
cursor_xy_at(cursor_idx, &text, &state.line_infos, font)
.unwrap_or_else(|| {
let x = rect.left();
let y = Range::new(0.0, font_size as Scalar).align_to(y_align, rect.y);
(x, y)
})
};
let start = [0.0, cursor_y_range.start];
let end = [0.0, cursor_y_range.end];
let prev_cursor_rect = ui.rect_of(state.ids.cursor);
widget::Line::centred(start, end)
.x_y(cursor_x, cursor_y_range.middle())
.graphics_for(id)
.parent(id)
.color(color)
.set(state.ids.cursor, ui);
if cursor_has_changed {
let cursor_rect = ui.rect_of(state.ids.cursor).unwrap();
if prev_cursor_rect != Some(cursor_rect) {
use graph::Walker;
let mut scrollable_parents = ui.widget_graph().scrollable_y_parent_recursion(id);
if let Some(parent_id) = scrollable_parents.next_node(ui.widget_graph()) {
if let Some(parent_rect) = ui.rect_of(parent_id) {
if cursor_rect.bottom() < parent_rect.bottom() {
let distance = parent_rect.bottom() - cursor_rect.bottom();
ui.scroll_widget(parent_id, [0.0, distance]);
} else if cursor_rect.top() > parent_rect.top() {
let distance = cursor_rect.top() - parent_rect.top();
ui.scroll_widget(parent_id, [0.0, -distance]);
}
}
}
}
}
if let Cursor::Selection { start, end } = cursor {
let (start, end) = (std::cmp::min(start, end), std::cmp::max(start, end));
let selected_rects: Vec<Rect> = {
let line_infos = state.line_infos.iter().cloned();
let lines = line_infos.clone().map(|info| &text[info.byte_range()]);
let line_rects = text::line::rects(line_infos.clone(), font_size, rect,
justify, y_align, line_spacing);
let lines_with_rects = lines.zip(line_rects.clone());
let font = ui.fonts.get(font_id).unwrap();
text::line::selected_rects(lines_with_rects, font, font_size, start, end).collect()
};
if state.ids.selected_rectangles.len() < selected_rects.len() {
let num_rects = selected_rects.len();
let id_gen = &mut ui.widget_id_generator();
state.update(|state| state.ids.selected_rectangles.resize(num_rects, id_gen));
}
let selected_rect_color = color.highlighted().alpha(0.25);
let iter = state.ids.selected_rectangles.iter().zip(&selected_rects);
for (&selected_rectangle_id, selected_rect) in iter {
widget::Rectangle::fill(selected_rect.dim())
.xy(selected_rect.xy())
.color(selected_rect_color)
.graphics_for(id)
.parent(id)
.set(selected_rectangle_id, ui);
}
}
take_if_owned(text)
}
}
impl<'a> Colorable for TextEdit<'a> {
builder_method!(color { style.color = Some(Color) });
}