From 83d62f1a06dfa4567abac53aee4b3be4b697f8b2 Mon Sep 17 00:00:00 2001 From: Mew Pur Pur <85438892+MewPurPur@users.noreply.github.com> Date: Sat, 25 May 2024 15:42:33 +0300 Subject: [PATCH] Fix the path field focus bug and others (#759) --- src/HandlerGUI.gd | 13 +- src/ThemeGenerator.gd | 4 +- src/ui_elements/pathdata_field.gd | 200 ++++++++++++++++++------------ src/ui_parts/update_menu.gd | 6 +- 4 files changed, 135 insertions(+), 88 deletions(-) diff --git a/src/HandlerGUI.gd b/src/HandlerGUI.gd index bbe8ecaf..2ce04b16 100644 --- a/src/HandlerGUI.gd +++ b/src/HandlerGUI.gd @@ -157,6 +157,13 @@ func _parse_popup_overlay_event(event: InputEvent) -> void: var last_mouse_click_double := false func _input(event: InputEvent) -> void: + if event.is_action_pressed("quit"): + var confirm_dialog := ConfirmDialog.instantiate() + add_overlay(confirm_dialog) + confirm_dialog.setup(TranslationServer.translate("Quit GodSVG"), + TranslationServer.translate("Do you want to quit GodSVG?"), + TranslationServer.translate("Quit"), get_tree().quit) + # Clear popups or overlays. if not popup_overlay_stack.is_empty() and event.is_action_pressed("ui_cancel"): get_viewport().set_input_as_handled() @@ -195,12 +202,6 @@ func _unhandled_input(event: InputEvent) -> void: elif event.is_action_pressed("undo"): get_viewport().set_input_as_handled() SVG.undo() - elif event.is_action_pressed("quit"): - var confirm_dialog := ConfirmDialog.instantiate() - add_overlay(confirm_dialog) - confirm_dialog.setup(TranslationServer.translate("Quit GodSVG"), - TranslationServer.translate("Do you want to quit GodSVG?"), - TranslationServer.translate("Quit"), get_tree().quit) if get_viewport().gui_is_dragging(): return diff --git a/src/ThemeGenerator.gd b/src/ThemeGenerator.gd index f64a0f34..812e94ff 100644 --- a/src/ThemeGenerator.gd +++ b/src/ThemeGenerator.gd @@ -676,7 +676,9 @@ static func setup_lineedit(theme: Theme) -> void: var mini_stylebox_hover := mini_stylebox.duplicate() mini_stylebox_hover.draw_center = false - mini_stylebox_hover.border_color = line_edit_hover_border_overlay_color + var mini_line_edit_hover_border_overlay_color := line_edit_hover_border_overlay_color + mini_line_edit_hover_border_overlay_color.a *= 1.5 + mini_stylebox_hover.border_color = mini_line_edit_hover_border_overlay_color theme.set_stylebox("hover", "MiniLineEdit", mini_stylebox_hover) var mini_stylebox_pressed := mini_stylebox.duplicate() diff --git a/src/ui_elements/pathdata_field.gd b/src/ui_elements/pathdata_field.gd index a5781915..9f54e3b4 100644 --- a/src/ui_elements/pathdata_field.gd +++ b/src/ui_elements/pathdata_field.gd @@ -34,12 +34,20 @@ var mini_line_edit_font_color := get_theme_color("font_color", "MiniLineEdit") @onready var commands_container: Control = $Commands # Variables around the big optimization. -var active_idx := -1 -var fields: Array[Control] = [] +# The idea is that when the mouse enters a strip, it's remembered as hovered. +# If a numfield is focused, its strip is remembered as focused. +# If a numfield is hovered and then focused, the controls aren't re-added, instead +# the references are moved from the hovered to the focused fields array. +# If a focused field is hovered, no hovered fields are added. +var hovered_idx := -1 +var focused_idx := -1 +var hovered_strip: Control +var focused_strip: Control var current_selections: Array[int] = [] var current_hovered: int = -1 @onready var ci := commands_container.get_canvas_item() +var add_move_button: Control func set_value(new_value: String, update_type := Utils.UpdateType.REGULAR) -> void: @@ -81,28 +89,31 @@ func sync(new_value: String) -> void: line_edit.text = new_value # A plus button for adding a move command if empty. var cmd_count := attribute.get_command_count() - if cmd_count == 0: - var add_move := Button.new() - add_move.icon = plus_icon - add_move.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN - add_move.size_flags_vertical = Control.SIZE_SHRINK_BEGIN - add_move.focus_mode = Control.FOCUS_NONE - add_move.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND - add_move.theme_type_variation = "FlatButton" - add_child(add_move) - add_move.pressed.connect(attribute.insert_command.bind(0, "M")) - add_move.pressed.connect(add_move.queue_free) + if cmd_count == 0 and not is_instance_valid(add_move_button): + add_move_button = Button.new() + add_move_button.icon = plus_icon + add_move_button.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN + add_move_button.size_flags_vertical = Control.SIZE_SHRINK_BEGIN + add_move_button.focus_mode = Control.FOCUS_NONE + add_move_button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND + add_move_button.theme_type_variation = "FlatButton" + add_child(add_move_button) + add_move_button.pressed.connect(attribute.insert_command.bind(0, "M")) + add_move_button.pressed.connect(add_move_button.queue_free) # Rebuild the path commands. commands_container.custom_minimum_size.y = cmd_count * COMMAND_HEIGHT + activate_hovered(-1) + var mm := InputEventMouseMotion.new() + mm.position = get_viewport().get_mouse_position() + Input.parse_input_event(mm) commands_container.queue_redraw() -func update_value(new_value: float, property: String) -> void: - attribute.set_command_property(active_idx, property, new_value) +func update_value(new_value: float, property: String, idx: int) -> void: + attribute.set_command_property(idx, property, new_value) func _on_relative_button_pressed() -> void: - attribute.toggle_relative_command(active_idx) - activate(active_idx, true) + attribute.toggle_relative_command(hovered_idx) # Path commands editor orchestration. @@ -127,15 +138,19 @@ func _on_commands_mouse_exited() -> void: var cmd_idx := Indications.inner_hovered Indications.remove_hovered(tid, cmd_idx) if Indications.semi_hovered_tid == tid: - for field in fields: - if field.has_focus(): - active_idx = cmd_idx - # Should switch out the controls for fake outs. - if active_idx != cmd_idx: - fields = [] - deactivate() + activate_hovered(-1) +# Prevents buttons from selecting a whole subpath when double-clicked. +func _eat_double_clicks(event: InputEvent, button: Button) -> void: + if hovered_idx != -1 and event is InputEventMouseButton and event.double_click: + button.accept_event() + if event.is_pressed(): + if button.toggle_mode: + button.toggled.emit(not button.button_pressed) + else: + button.pressed.emit() + func _on_commands_gui_input(event: InputEvent) -> void: if not event is InputEventMouse: return @@ -145,7 +160,7 @@ func _on_commands_gui_input(event: InputEvent) -> void: if event is InputEventMouseMotion and event.button_mask == 0: Indications.set_hovered(tid, cmd_idx) - activate(cmd_idx) + activate_hovered(cmd_idx) elif event is InputEventMouseButton: if event.button_index == MOUSE_BUTTON_LEFT: if event.is_pressed(): @@ -175,7 +190,8 @@ func _on_commands_gui_input(event: InputEvent) -> void: var viewport := get_viewport() var popup_pos := viewport.get_mouse_position() HandlerGUI.popup_under_pos(Indications.get_selection_context( - HandlerGUI.popup_under_pos.bind(popup_pos, viewport), Indications.SelectionContext.TAG_EDITOR), popup_pos, viewport) + HandlerGUI.popup_under_pos.bind(popup_pos, viewport), + Indications.SelectionContext.TAG_EDITOR), popup_pos, viewport) func commands_draw() -> void: @@ -199,7 +215,7 @@ func commands_draw() -> void: COMMAND_HEIGHT))) # Draw the child controls. They are going to be drawn, not added as a node unless # the mouse hovers them. This is a hack to significantly improve performance. - if i == active_idx: + if i == hovered_idx or i == focused_idx: continue var cmd := attribute.get_command(i) @@ -270,32 +286,59 @@ path_command: PathCommand) -> void: first_rect.position.x = first_rect.end.x + spacings[i] draw_numfield(first_rect, names[i + 1], path_command) -# Prevents buttons from selecting a whole subpath when double-clicked. -func _eat_double_clicks(event: InputEvent, button: Button) -> void: - if active_idx and event is InputEventMouseButton and event.double_click: - button.accept_event() - if event.is_pressed(): - if button.toggle_mode: - button.toggled.emit(not button.button_pressed) - else: - button.pressed.emit() +func activate_hovered(idx: int) -> void: + if idx == hovered_idx or idx >= attribute.get_command_count(): + return + + if is_instance_valid(hovered_strip): + hovered_strip.queue_free() + if focused_idx != idx: + hovered_strip = setup_path_command_controls(idx) + hovered_idx = idx + commands_container.queue_redraw() -func activate(idx: int, force := false) -> void: - if not force and active_idx == idx: +func activate_focused(idx: int) -> void: + if idx == focused_idx: return - for child in commands_container.get_children(): - child.queue_free() + if is_instance_valid(focused_strip): + focused_strip.queue_free() + if hovered_idx != idx: + focused_strip = setup_path_command_controls(idx) + if idx == -1: + hovered_strip = setup_path_command_controls(hovered_idx) + focused_strip = null + else: + focused_strip = hovered_strip + hovered_strip = null + focused_idx = idx + commands_container.queue_redraw() + +func check_focused() -> void: + for child in focused_strip.get_children(): + if child.has_focus(): + return + activate_focused(-1) + +func setup_path_command_controls(idx: int) -> Control: + if idx < 0: + return null + var cmd := attribute.get_command(idx) var cmd_char := cmd.command_char - active_idx = idx + var is_absolute := Utils.is_string_upper(cmd_char) + + var container := Control.new() + container.position.y = idx * COMMAND_HEIGHT + container.size = Vector2(commands_container.size.x, COMMAND_HEIGHT) + container.mouse_filter = Control.MOUSE_FILTER_PASS + commands_container.add_child(container) # Setup the relative button. var relative_button := Button.new() relative_button.focus_mode = Control.FOCUS_NONE relative_button.mouse_filter = Control.MOUSE_FILTER_PASS relative_button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND - var is_absolute := Utils.is_string_upper(cmd_char) relative_button.begin_bulk_theme_override() relative_button.add_theme_font_override("font", code_font) relative_button.add_theme_font_size_override("font_size", 13) @@ -314,11 +357,11 @@ func activate(idx: int, force := false) -> void: [TranslationUtils.get_command_description(cmd_char), TranslationServer.translate("Absolute") if is_absolute\ else TranslationServer.translate("Relative")] - commands_container.add_child(relative_button) + container.add_child(relative_button) relative_button.pressed.connect(_on_relative_button_pressed) relative_button.gui_input.connect(_eat_double_clicks.bind(relative_button)) - relative_button.position = Vector2(3, 2 + idx * COMMAND_HEIGHT) - relative_button.size = Vector2(18, COMMAND_HEIGHT - 4) + relative_button.position = Vector2(3, 2) + relative_button.size = Vector2(COMMAND_HEIGHT - 4, COMMAND_HEIGHT - 4) # Setup the action button. var action_button := Button.new() action_button.icon = more_icon @@ -326,13 +369,15 @@ func activate(idx: int, force := false) -> void: action_button.focus_mode = Control.FOCUS_NONE action_button.mouse_filter = Control.MOUSE_FILTER_PASS action_button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND - commands_container.add_child(action_button) + container.add_child(action_button) action_button.pressed.connect(_on_action_button_pressed.bind(action_button)) action_button.gui_input.connect(_eat_double_clicks.bind(action_button)) - action_button.position = Vector2(commands_container.size.x - 21, - 2 + idx * COMMAND_HEIGHT) + action_button.position = Vector2(commands_container.size.x - 21, 2) action_button.size = Vector2(COMMAND_HEIGHT - 4, COMMAND_HEIGHT - 4) # Setup the fields. + var fields: Array[Control] = [] + var spacings: Array[int] = [] + var property_names: Array[String] = [] match cmd_char.to_upper(): "A": var field_rx: BetterLineEdit = numfield(idx) @@ -347,60 +392,59 @@ func activate(idx: int, force := false) -> void: field_sweep.gui_input.connect(_eat_double_clicks.bind(field_sweep)) fields = [field_rx, field_ry, field_rot, field_large_arc, field_sweep, numfield(idx), numfield(idx)] - setup_fields(cmd, [3, 4, 4, 4, 4, 3], - ["rx", "ry", "rot", "large_arc_flag", "sweep_flag", "x", "y"]) + spacings = [3, 4, 4, 4, 4, 3] + property_names = ["rx", "ry", "rot", "large_arc_flag", "sweep_flag", "x", "y"] "C": fields = [numfield(idx), numfield(idx), numfield(idx), numfield(idx), numfield(idx), numfield(idx)] - setup_fields(cmd, [3, 4, 3, 4, 3], ["x1", "y1", "x2", "y2", "x", "y"]) + spacings = [3, 4, 3, 4, 3] + property_names = ["x1", "y1", "x2", "y2", "x", "y"] "Q": fields = [numfield(idx), numfield(idx), numfield(idx), numfield(idx)] - setup_fields(cmd, [3, 4, 3], ["x1", "y1", "x", "y"]) + spacings = [3, 4, 3] + property_names = ["x1", "y1", "x", "y"] "S": fields = [numfield(idx), numfield(idx), numfield(idx), numfield(idx)] - setup_fields(cmd, [3, 4, 3], ["x2", "y2", "x", "y"]) + spacings = [3, 4, 3] + property_names = ["x2", "y2", "x", "y"] "M", "L", "T": fields = [numfield(idx), numfield(idx)] - setup_fields(cmd, [3], ["x", "y"]) + spacings = [3] + property_names = ["x", "y"] "H": fields = [numfield(idx)] - setup_fields(cmd, [], ["x"]) + property_names = ["x"] "V": fields = [numfield(idx)] - setup_fields(cmd, [], ["y"]) - "Z": fields.clear() - # Remove the graphics, as now there are real nodes. - commands_container.queue_redraw() + property_names = ["y"] + # Setup the fields. + if not fields.is_empty(): + for i in fields.size(): + var field := fields[i] + var property_name := property_names[i] + field.set_value(cmd.get(property_name)) + field.tooltip_text = property_name + field.value_changed.connect(update_value.bind(property_name, idx)) + field.focus_entered.connect(activate_focused.bind(idx)) + field.focus_exited.connect(check_focused) + container.add_child(field) + field.position.y = 2 + fields[0].position.x = 25 + for i in fields.size() - 1: + fields[i + 1].position.x = fields[i].get_end().x + spacings[i] + return container -func deactivate() -> void: - active_idx = -1 - for child in commands_container.get_children(): - child.queue_free() - commands_container.queue_redraw() func numfield(cmd_idx: int) -> BetterLineEdit: var new_field := MiniNumberField.instantiate() new_field.focus_entered.connect(Indications.normal_select.bind(tid, cmd_idx)) return new_field -func setup_fields(path_command: PathCommand, spacings: Array, names: Array) -> void: - for i in fields.size(): - var property_str: String = names[i] - fields[i].set_value(path_command.get(property_str)) - fields[i].tooltip_text = property_str - fields[i].value_changed.connect(update_value.bind(property_str)) - commands_container.add_child(fields[i]) - fields[i].position.y = 2 + active_idx * COMMAND_HEIGHT - - fields[0].position.x = 25 - for i in fields.size() - 1: - fields[i + 1].position.x = fields[i].get_end().x + spacings[i] - func _on_action_button_pressed(action_button_ref: Button) -> void: # Update the selection immediately, since if this path command is # in a multi-selection, only the mouse button release would change the selection. - Indications.normal_select(tid, active_idx) + Indications.normal_select(tid, hovered_idx) var viewport := get_viewport() var action_button_rect := action_button_ref.get_global_rect() HandlerGUI.popup_under_rect_center(Indications.get_selection_context( diff --git a/src/ui_parts/update_menu.gd b/src/ui_parts/update_menu.gd index cd50c251..b8728f10 100644 --- a/src/ui_parts/update_menu.gd +++ b/src/ui_parts/update_menu.gd @@ -74,14 +74,14 @@ func display_results() -> void: # Check if there are results to be displayed. var has_results := false if prereleases_checkbox.button_pressed: - has_results = results.is_empty() + has_results = not results.is_empty() else: for version in results: - if results[version][1] == true: + if results[version][1] == false: has_results = true break # Set the text. - if has_results: + if not has_results: status_label.text = TranslationServer.translate("GodSVG is up-to-date.") return else: