2023-01-16 09:53:52 +00:00
|
|
|
use crate::{
|
|
|
|
builder::validator::multi_line_text_field_info::MultiLineTextFieldInfo,
|
|
|
|
guihandler::gui::writeable::ModifyText, prelude::*,
|
|
|
|
};
|
|
|
|
|
|
|
|
use std::sync::{
|
|
|
|
atomic::{AtomicU32, Ordering::SeqCst},
|
|
|
|
Arc, Mutex,
|
|
|
|
};
|
|
|
|
|
2024-04-07 13:39:32 +00:00
|
|
|
use anyhow::{Context, Result};
|
2023-01-16 11:58:59 +00:00
|
|
|
use utilities::prelude::*;
|
2023-01-16 09:53:52 +00:00
|
|
|
|
|
|
|
use super::fill_type::InnerFillType;
|
|
|
|
|
|
|
|
pub struct MultiLineTextFieldBuilder {
|
|
|
|
text_alignment: TextAlignment,
|
|
|
|
text_ratio: f32,
|
|
|
|
text_color: Color,
|
|
|
|
text: Option<String>,
|
|
|
|
|
|
|
|
line_count: u32,
|
|
|
|
|
|
|
|
background: Option<FillTypeInfo>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl MultiLineTextFieldBuilder {
|
|
|
|
pub fn set_text(mut self, text: impl Into<String>) -> Self {
|
|
|
|
self.text = Some(text.into());
|
|
|
|
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_ratio(mut self, ratio: f32) -> Self {
|
|
|
|
self.text_ratio = ratio;
|
|
|
|
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_text_color(mut self, text_color: impl Into<Color>) -> Self {
|
|
|
|
self.text_color = text_color.into();
|
|
|
|
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_text_alignment(mut self, text_alignment: TextAlignment) -> Self {
|
|
|
|
self.text_alignment = text_alignment;
|
|
|
|
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_line_count(mut self, line_count: u32) -> Self {
|
|
|
|
self.line_count = line_count;
|
|
|
|
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_background(mut self, background: impl Into<FillTypeInfo>) -> Self {
|
|
|
|
self.background = Some(background.into());
|
|
|
|
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn build(self, gui_handler: Arc<GuiHandler>) -> Result<Arc<MultiLineTextField>> {
|
|
|
|
let base_grid = Grid::new(gui_handler.clone(), 1, self.line_count as usize, false)?;
|
|
|
|
base_grid.set_margin(0);
|
|
|
|
base_grid.set_padding(0);
|
|
|
|
|
|
|
|
if let Some(background) = self.background {
|
|
|
|
base_grid.set_background(background)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
for i in 0..self.line_count {
|
|
|
|
let label = Label::builder()
|
|
|
|
.set_ratio(self.text_ratio)
|
|
|
|
.set_text_color(self.text_color)
|
|
|
|
.set_text_alignment(self.text_alignment)
|
|
|
|
.build(gui_handler.clone())?;
|
|
|
|
|
|
|
|
base_grid.attach(label, 0, i as usize, 1, 1)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
let text = Arc::new(SplittedText::new(self.text));
|
|
|
|
let text_changed_exec = Executable::new();
|
|
|
|
let writeable = Writeable::new(gui_handler, text.clone(), text_changed_exec.clone())?;
|
|
|
|
|
|
|
|
let multi_line_text_field = Arc::new(MultiLineTextField {
|
|
|
|
grid: base_grid,
|
|
|
|
|
|
|
|
text,
|
|
|
|
text_changed_exec,
|
|
|
|
writeable,
|
|
|
|
});
|
|
|
|
|
|
|
|
multi_line_text_field.text_changed_exec.set_callback({
|
|
|
|
let weak_tf = Arc::downgrade(&multi_line_text_field);
|
|
|
|
|
|
|
|
move |_text| {
|
|
|
|
if let Some(tf) = weak_tf.upgrade() {
|
|
|
|
tf.update_text()?;
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
multi_line_text_field.update_color_change();
|
|
|
|
|
|
|
|
Ok(multi_line_text_field)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct SplittedText {
|
|
|
|
characters_per_line: AtomicU32,
|
|
|
|
text: Mutex<String>,
|
|
|
|
splits: Mutex<Vec<String>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl SplittedText {
|
|
|
|
fn new(text: Option<String>) -> Self {
|
|
|
|
Self {
|
|
|
|
characters_per_line: AtomicU32::new(0),
|
|
|
|
text: Mutex::new(text.unwrap_or_default()),
|
|
|
|
splits: Mutex::new(Vec::new()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn text(&self) -> String {
|
|
|
|
self.text.lock().unwrap().clone()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn set_characters_per_line(&self, characters_per_line: u32) {
|
|
|
|
self.characters_per_line.store(characters_per_line, SeqCst);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn lines(&self) -> Vec<String> {
|
|
|
|
self.splits.lock().unwrap().clone()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn update_text(&self) -> Result<()> {
|
|
|
|
let text = self.text.lock().unwrap();
|
|
|
|
let splits = text.split(' ');
|
|
|
|
let character_count = self.characters_per_line.load(SeqCst) as usize;
|
|
|
|
|
|
|
|
let mut current_line = String::new();
|
|
|
|
let mut lines = Vec::new();
|
|
|
|
|
|
|
|
for split in splits {
|
|
|
|
let current_len = current_line.len();
|
|
|
|
let next_len = split.len() + 1;
|
|
|
|
|
|
|
|
if (current_len + next_len) <= character_count {
|
|
|
|
if current_line.is_empty() {
|
|
|
|
current_line = split.to_string();
|
|
|
|
} else {
|
|
|
|
current_line += &format!(" {}", split);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
lines.push(current_line);
|
|
|
|
current_line = split.to_string();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !current_line.is_empty() {
|
|
|
|
lines.push(current_line);
|
|
|
|
}
|
|
|
|
|
|
|
|
*self.splits.lock().unwrap() = lines.clone();
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ModifyText for SplittedText {
|
|
|
|
fn set_text(&self, text: String) -> Result<()> {
|
|
|
|
*self.text.lock().unwrap() = text;
|
|
|
|
|
|
|
|
self.update_text()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn add_letter(&self, letter: char) -> Result<String> {
|
|
|
|
*self.text.lock().unwrap() += &format!("{}", letter);
|
|
|
|
|
|
|
|
self.update_text()?;
|
|
|
|
|
|
|
|
Ok(self.text.lock().unwrap().clone())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn remove_last(&self) -> Result<Option<String>> {
|
|
|
|
self.text.lock().unwrap().pop();
|
|
|
|
|
|
|
|
self.update_text()?;
|
|
|
|
|
|
|
|
let text = self.text.lock().unwrap().clone();
|
|
|
|
Ok(if text.is_empty() { None } else { Some(text) })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct MultiLineTextField {
|
|
|
|
grid: Arc<Grid>,
|
|
|
|
|
|
|
|
text: Arc<SplittedText>,
|
|
|
|
text_changed_exec: Arc<Executable<Option<String>>>,
|
|
|
|
writeable: Arc<Writeable>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl MultiLineTextField {
|
|
|
|
pub fn builder() -> MultiLineTextFieldBuilder {
|
|
|
|
MultiLineTextFieldBuilder {
|
|
|
|
text_alignment: TextAlignment::Center,
|
|
|
|
text_ratio: 0.7,
|
|
|
|
text_color: Color::Black,
|
|
|
|
text: None,
|
|
|
|
|
|
|
|
line_count: 1,
|
|
|
|
|
|
|
|
background: None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_text(&self, text: impl ToString) -> Result<()> {
|
|
|
|
self.text.set_text(text.to_string())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn update_text(&self) -> Result<()> {
|
|
|
|
let lines = self.text.lines();
|
|
|
|
|
|
|
|
self.iter_label(|label, y| {
|
|
|
|
if y < lines.len() {
|
|
|
|
label.set_text(&lines[y])?;
|
|
|
|
} else {
|
|
|
|
label.set_text("")?;
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
fn update_color_change(self: &Arc<Self>) {
|
|
|
|
if let Some(background) = &*self.grid.background() {
|
|
|
|
if let InnerFillType::Color(colorable) = &background.inner {
|
|
|
|
let normal_color = colorable.color();
|
|
|
|
let focus_color = normal_color * 1.4;
|
|
|
|
|
|
|
|
let weak_self = Arc::downgrade(self);
|
|
|
|
|
|
|
|
self.writeable.set_activation_changed(move |active| {
|
|
|
|
if let Some(me) = weak_self.upgrade() {
|
|
|
|
if active {
|
|
|
|
me.grid.set_background(focus_color)?;
|
|
|
|
} else {
|
|
|
|
me.grid.set_background(normal_color)?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn text(&self) -> String {
|
|
|
|
self.text.text()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_text_color(&self, text_color: Color) -> Result<()> {
|
|
|
|
self.iter_label(|label, _| label.set_text_color(text_color))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_alignment(&self, alignment: TextAlignment) -> Result<()> {
|
|
|
|
self.iter_label(|label, _| label.set_alignment(alignment))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_text_ratio(&self, ratio: f32) -> Result<()> {
|
|
|
|
self.iter_label(|label, _| label.set_text_ratio(ratio))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_background(self: &Arc<Self>, background: impl Into<FillTypeInfo>) -> Result<()> {
|
|
|
|
self.grid.set_background(background)?;
|
|
|
|
self.update_color_change();
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn focus_input(&self) -> Result<()> {
|
|
|
|
self.writeable.set_active()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn try_from(
|
|
|
|
multi_line_text_field_info: &MultiLineTextFieldInfo,
|
|
|
|
gui_handler: &Arc<GuiHandler>,
|
|
|
|
) -> Result<Arc<Self>> {
|
|
|
|
let text = multi_line_text_field_info.text.read().unwrap().clone();
|
|
|
|
let color = multi_line_text_field_info.text_color;
|
|
|
|
|
|
|
|
let mut multi_line_text_field_builder = MultiLineTextField::builder()
|
|
|
|
.set_text_color(color)
|
|
|
|
.set_text_alignment(multi_line_text_field_info.text_alignment)
|
2024-04-07 13:39:32 +00:00
|
|
|
.set_line_count(
|
|
|
|
multi_line_text_field_info
|
|
|
|
.line_count
|
|
|
|
.get()
|
|
|
|
.with_context(|| "line count of text field")?,
|
|
|
|
);
|
2023-01-16 09:53:52 +00:00
|
|
|
|
|
|
|
if let Some(background_type) = &multi_line_text_field_info.background_type {
|
|
|
|
multi_line_text_field_builder =
|
|
|
|
multi_line_text_field_builder.set_background(background_type.clone());
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(ratio) = multi_line_text_field_info.text_ratio {
|
|
|
|
multi_line_text_field_builder = multi_line_text_field_builder.set_ratio(ratio);
|
|
|
|
}
|
|
|
|
|
|
|
|
if !text.is_empty() {
|
|
|
|
multi_line_text_field_builder = multi_line_text_field_builder.set_text(text);
|
|
|
|
}
|
|
|
|
|
|
|
|
multi_line_text_field_builder.build(gui_handler.clone())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn iter_label<F>(&self, f: F) -> Result<()>
|
|
|
|
where
|
|
|
|
F: Fn(&Label, usize) -> Result<()>,
|
|
|
|
{
|
|
|
|
for y in 0..self.grid.dimensions().1 {
|
|
|
|
let child = self.grid.child_at(0, y)?.unwrap();
|
|
|
|
let gui_element = child.downcast().unwrap();
|
|
|
|
let label = gui_element.label().unwrap();
|
|
|
|
|
|
|
|
f(label, y)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl GuiElementTraits for MultiLineTextField {
|
|
|
|
fn gridable(&self) -> Option<&dyn Gridable> {
|
|
|
|
Some(self)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn visibility(&self) -> Option<&dyn Visibility> {
|
|
|
|
Some(self)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn downcast<'a>(&'a self) -> Option<GuiElement<'a>> {
|
|
|
|
Some(GuiElement::MultiLineTextField(self))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Visibility for MultiLineTextField {
|
|
|
|
fn visible(&self) -> bool {
|
|
|
|
self.grid.visible()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn set_visibility(&self, visibility: bool) -> Result<()> {
|
|
|
|
if visibility != self.visible() {
|
|
|
|
if visibility {
|
|
|
|
self.writeable.add()?;
|
|
|
|
} else {
|
|
|
|
self.writeable.delete()?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
self.grid.set_visibility(visibility)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Gridable for MultiLineTextField {
|
|
|
|
fn set_frame(
|
|
|
|
&self,
|
|
|
|
x: i32,
|
|
|
|
y: i32,
|
|
|
|
w: u32,
|
|
|
|
h: u32,
|
|
|
|
vert_align: VerticalAlign,
|
|
|
|
hori_align: HorizontalAlign,
|
|
|
|
) -> Result<()> {
|
|
|
|
self.grid.set_frame(x, y, w, h, vert_align, hori_align)?;
|
|
|
|
|
|
|
|
if let Some(child) = self.grid.child_at(0, 0)? {
|
|
|
|
let gui_element = child.downcast().unwrap();
|
|
|
|
let label = gui_element.label().unwrap();
|
|
|
|
|
|
|
|
let left = label.framable.left();
|
|
|
|
let right = label.framable.right();
|
|
|
|
let top = label.framable.top();
|
|
|
|
let bottom = label.framable.bottom();
|
|
|
|
|
|
|
|
let width = right - left;
|
|
|
|
let height = bottom - top;
|
|
|
|
|
|
|
|
let text_ratio = label.text_ratio();
|
|
|
|
|
|
|
|
let text_size = height as f32 * text_ratio;
|
|
|
|
let max_letter_count = (width as f32 / (text_size / 2.0)).floor();
|
|
|
|
|
|
|
|
self.text.set_characters_per_line(max_letter_count as u32);
|
|
|
|
|
|
|
|
self.update_text()?;
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn selectable(&self) -> Option<&Arc<Selectable>> {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
|
|
|
fn type_name(&self) -> &str {
|
|
|
|
"MultiLineLabel"
|
|
|
|
}
|
|
|
|
|
|
|
|
fn set_layer(&self, layer: i32) -> Result<()> {
|
|
|
|
self.writeable.set_ui_layer(layer);
|
|
|
|
self.grid.set_layer(layer)
|
|
|
|
}
|
2024-04-21 05:41:08 +00:00
|
|
|
|
|
|
|
fn position_extent(&self) -> (i32, i32, u32, u32) {
|
|
|
|
self.grid.position_extent()
|
|
|
|
}
|
2023-01-16 09:53:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Drop for MultiLineTextField {
|
|
|
|
fn drop(&mut self) {
|
|
|
|
if self.visible() {
|
|
|
|
self.writeable.delete().unwrap();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|