Repository: GraphiteEditor/Graphite (language: rust)

Issue:
Keep selection when clicking in properties after creating text
- Create a new text box
- Type something
- Attempt to modify something in the properties of the newly added text layer (e.g. change the fill colour)
- The layer is deselected so the properties disappear.

It would be nice to keep the properties around when the user is trying to edit them.


Discussion / hints:

Fix patch:
diff --git a/editor/src/messages/tool/tool_messages/text_tool.rs b/editor/src/messages/tool/tool_messages/text_tool.rs
index 311952eef1..2a4c5cba21 100644
--- a/editor/src/messages/tool/tool_messages/text_tool.rs
+++ b/editor/src/messages/tool/tool_messages/text_tool.rs
@@ -56,7 +56,7 @@ pub enum TextToolMessage {
 	CommitText,
 	EditSelected,
 	Interact,
-	TextChange { new_text: String },
+	TextChange { new_text: String, is_right_click: bool },
 	UpdateBounds { new_text: String },
 	UpdateOptions(TextOptionsUpdate),
 }
@@ -241,6 +241,19 @@ struct TextToolData {
 }
 
 impl TextToolData {
+	fn delete_empty_layer(&mut self, font_cache: &FontCache, responses: &mut VecDeque<Message>) -> TextToolFsmState {
+		// Remove the editable textbox UI first
+		self.set_editing(false, font_cache, responses);
+
+		// Delete the empty text layer and update the graph
+		responses.add(NodeGraphMessage::DeleteNodes {
+			node_ids: vec![self.layer.to_node()],
+			delete_children: true,
+		});
+		responses.add(NodeGraphMessage::RunDocumentGraph);
+
+		TextToolFsmState::Ready
+	}
 	/// Set the editing state of the currently modifying layer
 	fn set_editing(&self, editable: bool, font_cache: &FontCache, responses: &mut VecDeque<Message>) {
 		if let Some(editing_text) = self.editing_text.as_ref().filter(|_| editable) {
@@ -253,9 +266,13 @@ impl TextToolData {
 				transform: editing_text.transform.to_cols_array(),
 			});
 		} else {
+			// Check if DisplayRemoveEditableTextbox is already in the responses queue
+			let has_remove_textbox = responses.iter().any(|msg| matches!(msg, Message::Frontend(FrontendMessage::DisplayRemoveEditableTextbox)));
 			responses.add(FrontendMessage::DisplayRemoveEditableTextbox);
-			// Clear all selected nodes when no longer editing
-			responses.add(NodeGraphMessage::SelectedNodesSet { nodes: Vec::new() });
+
+			if has_remove_textbox {
+				responses.add(NodeGraphMessage::SelectedNodesSet { nodes: Vec::new() });
+			}
 		}
 	}
 
@@ -474,20 +491,34 @@ impl Fsm for TextToolFsmState {
 				TextToolFsmState::Ready
 			}
 			(TextToolFsmState::Editing, TextToolMessage::CommitText) => {
-				responses.add(FrontendMessage::TriggerTextCommit);
+				if tool_data.new_text.is_empty() {
+					return tool_data.delete_empty_layer(font_cache, responses);
+				}
 
+				responses.add(FrontendMessage::TriggerTextCommit);
 				TextToolFsmState::Editing
 			}
-			(TextToolFsmState::Editing, TextToolMessage::TextChange { new_text }) => {
-				tool_data.set_editing(false, font_cache, responses);
+			(TextToolFsmState::Editing, TextToolMessage::TextChange { new_text, is_right_click }) => {
+				tool_data.new_text = new_text;
 
-				responses.add(NodeGraphMessage::SetInput {
-					input_connector: InputConnector::node(graph_modification_utils::get_text_id(tool_data.layer, &document.network_interface).unwrap(), 1),
-					input: NodeInput::value(TaggedValue::String(new_text), false),
-				});
-				responses.add(NodeGraphMessage::RunDocumentGraph);
+				if !is_right_click {
+					tool_data.set_editing(false, font_cache, responses);
 
-				TextToolFsmState::Ready
+					responses.add(NodeGraphMessage::SetInput {
+						input_connector: InputConnector::node(graph_modification_utils::get_text_id(tool_data.layer, &document.network_interface).unwrap(), 1),
+						input: NodeInput::value(TaggedValue::String(tool_data.new_text.clone()), false),
+					});
+					responses.add(NodeGraphMessage::RunDocumentGraph);
+
+					TextToolFsmState::Ready
+				} else {
+					if tool_data.new_text.is_empty() {
+						return tool_data.delete_empty_layer(font_cache, responses);
+					}
+
+					responses.add(FrontendMessage::TriggerTextCommit);
+					TextToolFsmState::Editing
+				}
 			}
 			(TextToolFsmState::Editing, TextToolMessage::UpdateBounds { new_text }) => {
 				tool_data.new_text = new_text;
diff --git a/frontend/src/components/panels/Document.svelte b/frontend/src/components/panels/Document.svelte
index 52139b5b7f..64433a38e7 100644
--- a/frontend/src/components/panels/Document.svelte
+++ b/frontend/src/components/panels/Document.svelte
@@ -300,7 +300,7 @@
 	export function triggerTextCommit() {
 		if (!textInput) return;
 		const textCleaned = textInputCleanup(textInput.innerText);
-		editor.handle.onChangeText(textCleaned);
+		editor.handle.onChangeText(textCleaned, false);
 	}
 
 	export async function displayEditableTextbox(displayEditableTextbox: DisplayEditableTextbox) {
diff --git a/frontend/src/io-managers/input.ts b/frontend/src/io-managers/input.ts
index 484f4f735e..f67f56cfeb 100644
--- a/frontend/src/io-managers/input.ts
+++ b/frontend/src/io-managers/input.ts
@@ -172,7 +172,8 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
 		}
 
 		if (!inTextInput && !inContextMenu) {
-			if (textToolInteractiveInputElement) editor.handle.onChangeText(textInputCleanup(textToolInteractiveInputElement.innerText));
+			const isRightClick = e.button === 2;
+			if (textToolInteractiveInputElement) editor.handle.onChangeText(textInputCleanup(textToolInteractiveInputElement.innerText), isRightClick);
 			else viewportPointerInteractionOngoing = isTargetingCanvas instanceof Element;
 		}
 
diff --git a/frontend/wasm/src/editor_api.rs b/frontend/wasm/src/editor_api.rs
index 94341aa1e4..c1b0f2c129 100644
--- a/frontend/wasm/src/editor_api.rs
+++ b/frontend/wasm/src/editor_api.rs
@@ -433,8 +433,8 @@ impl EditorHandle {
 
 	/// A text box was committed
 	#[wasm_bindgen(js_name = onChangeText)]
-	pub fn on_change_text(&self, new_text: String) -> Result<(), JsValue> {
-		let message = TextToolMessage::TextChange { new_text };
+	pub fn on_change_text(&self, new_text: String, is_right_click: bool) -> Result<(), JsValue> {
+		let message = TextToolMessage::TextChange { new_text, is_right_click };
 		self.dispatch(message);
 
 		Ok(())


Test patch: