blockworx_tools/
edit_text_box.rs1use blockworx_doc::id::TextId;
2use blockworx_geom::{Align2, Angle, Pos2, Rect, Vec2};
3
4use crate::render::text_box::{BoxWidth, text_origin};
5use crate::theme::{Role, Style};
6use crate::{
7 grid::px_point,
8 names::ToolName,
9 shape::ShapeId,
10 tool::{Action, Deletable, ToolTrait, Transition},
11 widget::drawing::Drawing,
12};
13use blockworx_paint::{Canvas, EditId, EditText, Interaction, TextOutcome};
14pub enum EditTextBox {
24 Editing {
25 id: TextId,
26 text: String,
27 anchor: Pos2,
28 width: BoxWidth,
29 },
30}
31
32impl EditTextBox {
33 pub fn new_for(data: &Drawing, id: TextId) -> Option<Self> {
35 let tb = data.text_box(id)?;
36 Some(EditTextBox::Editing {
37 id,
38 text: tb.text.clone(),
39 anchor: px_point(tb.pos),
40 width: BoxWidth::of(tb),
41 })
42 }
43
44 pub fn action(data: &Drawing, id: TextId) -> Transition {
46 match EditTextBox::new_for(data, id) {
47 Some(tool) => Transition::SwitchTool(tool.into()),
48 None => Transition::default(),
49 }
50 }
51}
52
53fn editor_rect(anchor: Pos2) -> Rect {
56 Rect::from_min_size(text_origin(anchor), Vec2::ZERO)
57}
58
59impl ToolTrait for EditTextBox {
60 fn name(&self) -> ToolName {
61 ToolName::EditTextBox
62 }
63
64 fn widget<C: Canvas>(
65 &mut self,
66 data: &mut Drawing,
67 interaction: &Interaction,
68 painter: &mut Style<'_, C>,
69 ) -> Option<Transition> {
70 let (id, opened_on, anchor, width) = match self {
71 EditTextBox::Editing {
72 id,
73 text,
74 anchor,
75 width,
76 } => (*id, text.as_str(), *anchor, *width),
77 };
78 crate::widget::display::render_hidden(data, ShapeId::Text(id), painter);
81 let edit_id = EditId::of("text_box_edit");
82 let Some(outcome) = &interaction.text else {
83 let wrap_width = width.wrap_width(painter);
84 painter.set_edit_text(EditText {
85 align: Align2::LEFT_TOP,
86 position: editor_rect(anchor),
87 angle: Angle::ZERO,
88 text: opened_on.to_owned(),
89 font: painter.theme().title_font.clone(),
90 id: edit_id,
91 multiline: true,
92 char_limit: Some(crate::grid::MAX_TEXT_BOX_CHARS),
93 tab_cycle: false,
94 select_all_on_focus: false,
95 hint: Some(crate::render::ADD_TEXT_PLACEHOLDER),
96 colors: painter
97 .theme()
98 .editor_colors(Role::EditorText, Role::EditorFill),
99 wrap_width: Some(wrap_width),
100 });
101 return None;
102 };
103 let text = match outcome {
104 TextOutcome::Committed(text) | TextOutcome::Tab(text) => text.as_str(),
105 TextOutcome::Cancelled => opened_on,
106 };
107 if text.trim().is_empty() {
108 return Some(Action::Delete(Deletable::Shape(ShapeId::Text(id))).into());
110 }
111 if *outcome != TextOutcome::Cancelled {
112 data.set_text_box_content(id, text);
113 }
114 Some(Transition::SwitchTool(
116 crate::resize_block::ResizeBlock::Selected {
117 shape: ShapeId::Text(id),
118 }
119 .into(),
120 ))
121 }
122}