Skip to content

Commit

Permalink
Add shortcut indicators in context popups (#739)
Browse files Browse the repository at this point in the history
  • Loading branch information
MewPurPur authored May 16, 2024
1 parent 7ebab91 commit b2224ed
Show file tree
Hide file tree
Showing 17 changed files with 268 additions and 141 deletions.
53 changes: 33 additions & 20 deletions src/Indications.gd
Original file line number Diff line number Diff line change
Expand Up @@ -493,41 +493,53 @@ func get_selection_context(popup_method: Callable, context: SelectionContext) ->
if not can_move_down and base_tid[-1] < parent_child_count - filtered_count:
can_move_down = true
if context == SelectionContext.VIEWPORT:
btn_arr.append(Utils.create_btn(TranslationServer.translate("View In List"),
view_in_list.bind(selected_tids[0]), false, load("res://visual/icons/ViewInList.svg")))

btn_arr.append(Utils.create_btn(TranslationServer.translate("Duplicate"),
duplicate_selected, false, load("res://visual/icons/Duplicate.svg")))
btn_arr.append(ContextPopup.create_button(
TranslationServer.translate("View In List"),
view_in_list.bind(selected_tids[0]), false,
load("res://visual/icons/ViewInList.svg")))

btn_arr.append(ContextPopup.create_button(TranslationServer.translate("Duplicate"),
duplicate_selected, false, load("res://visual/icons/Duplicate.svg"),
"duplicate"))

if selected_tids.size() == 1 and not SVG.root_tag.get_tag(
selected_tids[0]).possible_conversions.is_empty():
btn_arr.append(Utils.create_btn(TranslationServer.translate("Convert To"),
btn_arr.append(ContextPopup.create_button(
TranslationServer.translate("Convert To"),
popup_convert_to_context.bind(popup_method), false,
load("res://visual/icons/Reload.svg")))

if can_move_up:
btn_arr.append(Utils.create_btn(TranslationServer.translate("Move Up"),
move_up_selected, false, load("res://visual/icons/MoveUp.svg")))
btn_arr.append(ContextPopup.create_button(
TranslationServer.translate("Move Up"),
move_up_selected, false,
load("res://visual/icons/MoveUp.svg"), "move_up"))
if can_move_down:
btn_arr.append(Utils.create_btn(TranslationServer.translate("Move Down"),
move_down_selected, false, load("res://visual/icons/MoveDown.svg")))
btn_arr.append(ContextPopup.create_button(
TranslationServer.translate("Move Down"),
move_down_selected, false,
load("res://visual/icons/MoveDown.svg"), "move_down"))

btn_arr.append(Utils.create_btn(TranslationServer.translate("Delete"),
delete_selected, false, load("res://visual/icons/Delete.svg")))
btn_arr.append(ContextPopup.create_button(TranslationServer.translate("Delete"),
delete_selected, false, load("res://visual/icons/Delete.svg"), "delete"))
elif not inner_selections.is_empty() and not semi_selected_tid.is_empty():
if context == SelectionContext.VIEWPORT:
btn_arr.append(Utils.create_btn(TranslationServer.translate("View In List"),
view_in_list.bind(semi_selected_tid), false, load("res://visual/icons/ViewInList.svg")))
btn_arr.append(ContextPopup.create_button(
TranslationServer.translate("View In List"),
view_in_list.bind(semi_selected_tid), false,
load("res://visual/icons/ViewInList.svg")))
if inner_selections.size() == 1:
btn_arr.append(Utils.create_btn(TranslationServer.translate("Insert After"),
btn_arr.append(ContextPopup.create_button(
TranslationServer.translate("Insert After"),
popup_insert_command_after_context.bind(popup_method), false,
load("res://visual/icons/Plus.svg")))
btn_arr.append(Utils.create_btn(TranslationServer.translate("Convert To"),
btn_arr.append(ContextPopup.create_button(
TranslationServer.translate("Convert To"),
popup_convert_to_context.bind(popup_method), false,
load("res://visual/icons/Reload.svg")))

btn_arr.append(Utils.create_btn(TranslationServer.translate("Delete"),
delete_selected, false, load("res://visual/icons/Delete.svg")))
btn_arr.append(ContextPopup.create_button(TranslationServer.translate("Delete"),
delete_selected, false, load("res://visual/icons/Delete.svg"), "delete"))

var tag_context := ContextPopup.new()
tag_context.setup(btn_arr, true)
Expand All @@ -539,8 +551,9 @@ func popup_convert_to_context(popup_method: Callable) -> void:
var btn_arr: Array[Button] = []
var tag := SVG.root_tag.get_tag(selected_tids[0])
for tag_name in tag.possible_conversions:
var btn := Utils.create_btn(tag_name, convert_selected_tag_to.bind(tag_name),
!tag.can_replace(tag_name), load("res://visual/icons/tag/%s.svg" % tag_name))
var btn := ContextPopup.create_button(tag_name,
convert_selected_tag_to.bind(tag_name), !tag.can_replace(tag_name),
load("res://visual/icons/tag/%s.svg" % tag_name))
btn.add_theme_font_override("font", load("res://visual/fonts/FontMono.ttf"))
btn_arr.append(btn)
var context_popup := ContextPopup.new()
Expand Down
23 changes: 0 additions & 23 deletions src/Utils.gd
Original file line number Diff line number Diff line change
Expand Up @@ -41,29 +41,6 @@ font_size_property := "font_size") -> void:
control.get_theme_font_size(font_size_property)).x + buffer, max_width)


static func create_btn(text: String, press_action: Callable, disabled := false,
icon: Texture2D = null) -> Button:
var btn := Button.new()
btn.text = text
if is_instance_valid(icon):
btn.icon = icon
if disabled:
btn.disabled = true
else:
btn.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
btn.pressed.connect(press_action)
return btn

static func create_checkbox(text: String, toggle_action: Callable,
start_pressed: bool) -> CheckBox:
var checkbox := CheckBox.new()
checkbox.text = text
checkbox.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
checkbox.button_pressed = start_pressed
checkbox.pressed.connect(toggle_action)
return checkbox


static func get_cubic_bezier_points(cp1: Vector2, cp2: Vector2, cp3: Vector2,
cp4: Vector2) -> PackedVector2Array:
var curve := Curve2D.new()
Expand Down
30 changes: 18 additions & 12 deletions src/ui_elements/BetterLineEdit.gd
Original file line number Diff line number Diff line change
Expand Up @@ -97,28 +97,34 @@ func _gui_input(event: InputEvent) -> void:
var btn_arr: Array[Button] = []
var separator_arr: Array[int] = []
if editable:
btn_arr.append(Utils.create_btn(TranslationServer.translate("Undo"),
btn_arr.append(ContextPopup.create_button(
TranslationServer.translate("Undo"),
menu_option.bind(LineEdit.MENU_UNDO),
false, load("res://visual/icons/Undo.svg")))
btn_arr.append(Utils.create_btn(TranslationServer.translate("Redo"),
false, load("res://visual/icons/Undo.svg"), "ui_undo"))
btn_arr.append(ContextPopup.create_button(
TranslationServer.translate("Redo"),
menu_option.bind(LineEdit.MENU_REDO),
false, load("res://visual/icons/Redo.svg")))
false, load("res://visual/icons/Redo.svg"), "ui_redo"))
if DisplayServer.has_feature(DisplayServer.FEATURE_CLIPBOARD):
separator_arr = [2]
btn_arr.append(Utils.create_btn(TranslationServer.translate("Cut"),
btn_arr.append(ContextPopup.create_button(
TranslationServer.translate("Cut"),
menu_option.bind(LineEdit.MENU_CUT),
text.is_empty(), load("res://visual/icons/Cut.svg")))
btn_arr.append(Utils.create_btn(TranslationServer.translate("Copy"),
text.is_empty(), load("res://visual/icons/Cut.svg"), "ui_cut"))
btn_arr.append(ContextPopup.create_button(
TranslationServer.translate("Copy"),
menu_option.bind(LineEdit.MENU_COPY),
text.is_empty(), load("res://visual/icons/Copy.svg")))
btn_arr.append(Utils.create_btn(TranslationServer.translate("Paste"),
text.is_empty(), load("res://visual/icons/Copy.svg"), "ui_copy"))
btn_arr.append(ContextPopup.create_button(
TranslationServer.translate("Paste"),
menu_option.bind(LineEdit.MENU_PASTE),
!DisplayServer.clipboard_has(),
load("res://visual/icons/Paste.svg")))
load("res://visual/icons/Paste.svg"), "ui_paste"))
else:
btn_arr.append(Utils.create_btn(TranslationServer.translate("Copy"),
btn_arr.append(ContextPopup.create_button(
TranslationServer.translate("Copy"),
menu_option.bind(LineEdit.MENU_COPY),
text.is_empty(), load("res://visual/icons/Copy.svg")))
text.is_empty(), load("res://visual/icons/Copy.svg"), "ui_copy"))

var vp := get_viewport()
var context_popup := ContextPopup.new()
Expand Down
32 changes: 19 additions & 13 deletions src/ui_elements/BetterTextEdit.gd
Original file line number Diff line number Diff line change
Expand Up @@ -113,22 +113,28 @@ func _gui_input(event: InputEvent) -> void:
var btn_arr: Array[Button] = []
var separator_arr: Array[int] = []
if editable:
btn_arr.append(Utils.create_btn(TranslationServer.translate("Undo"),
undo, !has_undo(), load("res://visual/icons/Undo.svg")))
btn_arr.append(Utils.create_btn(TranslationServer.translate("Redo"),
redo, !has_redo(), load("res://visual/icons/Redo.svg")))
btn_arr.append(ContextPopup.create_button(
TranslationServer.translate("Undo"), undo,
!has_undo(), load("res://visual/icons/Undo.svg"), "ui_undo"))
btn_arr.append(ContextPopup.create_button(
TranslationServer.translate("Redo"), redo,
!has_redo(), load("res://visual/icons/Redo.svg"), "ui_redo"))
if DisplayServer.has_feature(DisplayServer.FEATURE_CLIPBOARD):
separator_arr = [2]
btn_arr.append(Utils.create_btn(TranslationServer.translate("Cut"),
cut, text.is_empty(), load("res://visual/icons/Cut.svg")))
btn_arr.append(Utils.create_btn(TranslationServer.translate("Copy"),
copy, text.is_empty(), load("res://visual/icons/Copy.svg")))
btn_arr.append(Utils.create_btn(TranslationServer.translate("Paste"),
paste, !DisplayServer.clipboard_has(),
load("res://visual/icons/Paste.svg")))
btn_arr.append(ContextPopup.create_button(
TranslationServer.translate("Cut"), cut,
text.is_empty(), load("res://visual/icons/Cut.svg"), "ui_cut"))
btn_arr.append(ContextPopup.create_button(
TranslationServer.translate("Copy"), copy,
text.is_empty(), load("res://visual/icons/Copy.svg"), "ui_copy"))
btn_arr.append(ContextPopup.create_button(
TranslationServer.translate("Paste"), paste,
!DisplayServer.clipboard_has(),
load("res://visual/icons/Paste.svg"), "ui_paste"))
else:
btn_arr.append(Utils.create_btn(TranslationServer.translate("Copy"),
copy, text.is_empty(), load("res://visual/icons/Copy.svg")))
btn_arr.append(ContextPopup.create_button(
TranslationServer.translate("Copy"), copy,
text.is_empty(), load("res://visual/icons/Copy.svg"), "ui_copy"))

var context_popup := ContextPopup.new()
context_popup.setup(btn_arr, true, -1, separator_arr)
Expand Down
137 changes: 129 additions & 8 deletions src/ui_elements/context_popup.gd
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,131 @@ class_name ContextPopup extends PanelContainer
func _init() -> void:
mouse_filter = Control.MOUSE_FILTER_STOP

func setup_button(btn: Button, align_left: bool) -> Button:
if not btn is CheckBox:
btn.theme_type_variation = "ContextButton"
btn.pressed.connect(HandlerGUI.remove_popup_overlay)
btn.ready.connect(_order_signals.bind(btn))
btn.focus_mode = Control.FOCUS_NONE

static func create_button(text: String, press_action: Callable, disabled := false,
icon: Texture2D = null, shortcut := "") -> Button:
# Create main button.
var main_button := Button.new()
main_button.text = text
if is_instance_valid(icon):
main_button.icon = icon

if not shortcut.is_empty():
if not InputMap.has_action(shortcut):
printerr("A non-existent shortcut was passed to Utils.create_btn().")
elif InputMap.has_action(shortcut):
var events := InputMap.action_get_events(shortcut)
if not events.is_empty():
# Add button with a shortcut.
var ret_button := Button.new()
ret_button.theme_type_variation = "ContextButton"
ret_button.focus_mode = Control.FOCUS_NONE
if disabled:
main_button.disabled = true
ret_button.disabled = true
main_button.add_theme_stylebox_override("disabled",
main_button.get_theme_stylebox("normal", "ContextButton"))
else:
ret_button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
main_button.add_theme_stylebox_override("normal",
main_button.get_theme_stylebox("normal", "ContextButton"))
var internal_hbox := HBoxContainer.new()
main_button.mouse_filter = Control.MOUSE_FILTER_IGNORE # Unpressable.
internal_hbox.add_theme_constant_override("separation", 6)
main_button.add_theme_color_override("icon_normal_color",
ret_button.get_theme_color("icon_normal_color", "ContextButton"))
var label_margin := MarginContainer.new()
label_margin.add_theme_constant_override("margin_right",
int(ret_button.get_theme_stylebox("normal").content_margin_right))
var label := Label.new()
label.text = events[0].as_text_keycode()
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
label.add_theme_color_override("font_color",
ThemeGenerator.common_subtle_text_color)
label.add_theme_font_size_override("font_size",
main_button.get_theme_font_size("font_size"))

ret_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
internal_hbox.set_anchors_and_offsets_preset(Control.PRESET_TOP_WIDE)
label_margin.size_flags_horizontal = Control.SIZE_EXPAND_FILL
label.size_flags_horizontal = Control.SIZE_FILL
internal_hbox.add_child(main_button)
label_margin.add_child(label)
internal_hbox.add_child(label_margin)
ret_button.add_child(internal_hbox)
ret_button.pressed.connect(press_action)
ret_button.pressed.connect(HandlerGUI.remove_popup_overlay)
return ret_button
# Finish setting up the main button and return it if there's no shortcut.
main_button.theme_type_variation = "ContextButton"
main_button.focus_mode = Control.FOCUS_NONE
if disabled:
main_button.disabled = true
else:
main_button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
main_button.pressed.connect(press_action)
main_button.pressed.connect(HandlerGUI.remove_popup_overlay)
return main_button

static func create_checkbox(text: String, toggle_action: Callable,
start_pressed: bool, shortcut := "") -> CheckBox:
# Create main checkbox.
var checkbox := CheckBox.new()
checkbox.text = text
checkbox.button_pressed = start_pressed
checkbox.toggled.connect(toggle_action.unbind(1))

if not shortcut.is_empty():
if not InputMap.has_action(shortcut):
printerr("A non-existent shortcut was passed to Utils.create_btn().")
elif InputMap.has_action(shortcut):
var events := InputMap.action_get_events(shortcut)
if not events.is_empty():
# Add button with a shortcut.
var ret_button := Button.new()
ret_button.theme_type_variation = "ContextButton"
ret_button.focus_mode = Control.FOCUS_NONE
ret_button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
checkbox.add_theme_stylebox_override("normal",
checkbox.get_theme_stylebox("normal", "ContextButton"))
var internal_hbox := HBoxContainer.new()
checkbox.mouse_filter = Control.MOUSE_FILTER_IGNORE # Unpressable.
internal_hbox.add_theme_constant_override("separation", 6)
checkbox.add_theme_color_override("icon_normal_color",
ret_button.get_theme_color("icon_normal_color", "ContextButton"))
var label_margin := MarginContainer.new()
label_margin.add_theme_constant_override("margin_right",
int(ret_button.get_theme_stylebox("normal").content_margin_right))
var label := Label.new()
label.text = events[0].as_text_keycode()
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
label.add_theme_color_override("font_color",
ThemeGenerator.common_subtle_text_color)
label.add_theme_font_size_override("font_size",
checkbox.get_theme_font_size("font_size"))

ret_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
internal_hbox.set_anchors_and_offsets_preset(Control.PRESET_TOP_WIDE)
label_margin.size_flags_horizontal = Control.SIZE_EXPAND_FILL
label.size_flags_horizontal = Control.SIZE_FILL
internal_hbox.add_child(checkbox)
label_margin.add_child(label)
internal_hbox.add_child(label_margin)
ret_button.add_child(internal_hbox)
ret_button.pressed.connect(
func(): checkbox.button_pressed = !checkbox.button_pressed)
return ret_button
# Finish setting up the checkbox and return it if there's no shortcut.
checkbox.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
checkbox.focus_mode = Control.FOCUS_NONE
return checkbox

func _setup_button(btn: Button, align_left: bool) -> Button:
if align_left:
btn.alignment = HORIZONTAL_ALIGNMENT_LEFT
btn.ready.connect(_order_signals.bind(btn))
if btn.get_child_count() == 1:
btn.get_child(0).resized.connect(_resize_button_around_child.bind(btn))
return btn

# A hack to deal with situations where a popup is replaced by another.
Expand All @@ -22,6 +139,10 @@ func _order_signals(btn: Button) -> void:
btn.pressed.connect(connection.callable, CONNECT_DEFERRED)
set_block_signals(true)

# A hack for buttons that are wrapped around a control.
func _resize_button_around_child(btn: Button) -> void:
var child: Control = btn.get_child(0)
btn.custom_minimum_size = child.size

func setup(buttons: Array[Button], align_left := false, min_width := -1.0,
separator_indices: Array[int] = []) -> void:
Expand All @@ -35,7 +156,7 @@ separator_indices: Array[int] = []) -> void:
var separator := HSeparator.new()
separator.theme_type_variation = "SmallHSeparator"
main_container.add_child(separator)
main_container.add_child(setup_button(buttons[idx], align_left))
main_container.add_child(_setup_button(buttons[idx], align_left))
if min_width > 0:
custom_minimum_size.x = ceili(min_width)

Expand Down Expand Up @@ -72,7 +193,7 @@ min_width := -1.0, separator_indices: Array[int] = []) -> void:
var separator := HSeparator.new()
separator.theme_type_variation = "SmallHSeparator"
main_container.add_child(separator)
main_container.add_child(setup_button(buttons[idx], align_left))
main_container.add_child(_setup_button(buttons[idx], align_left))
if min_width > 0:
custom_minimum_size.x = min_width

Expand Down
3 changes: 2 additions & 1 deletion src/ui_elements/dropdown.gd
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ func _ready() -> void:
func _on_button_pressed() -> void:
var btn_arr: Array[Button] = []
for val in values:
btn_arr.append(Utils.create_btn(val, _on_value_chosen.bind(val), val == value))
btn_arr.append(ContextPopup.create_button(val, _on_value_chosen.bind(val),
val == value))

var value_picker := ContextPopup.new()
value_picker.setup(btn_arr, false, size.x)
Expand Down
4 changes: 2 additions & 2 deletions src/ui_elements/enum_field.gd
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ func _ready() -> void:
func _on_pressed() -> void:
var btn_arr: Array[Button] = []
# Add a default.
var reset_btn := Utils.create_btn("", set_value.bind(""),
var reset_btn := ContextPopup.create_button("", set_value.bind(""),
attribute.get_value().is_empty(), reload_icon)
reset_btn.icon_alignment = HORIZONTAL_ALIGNMENT_CENTER
btn_arr.append(reset_btn)
# Add a button for each enum value.
for enum_constant in DB.attribute_enum_values[attribute.name]:
var btn := Utils.create_btn(enum_constant, set_value.bind(enum_constant),
var btn := ContextPopup.create_button(enum_constant, set_value.bind(enum_constant),
enum_constant == attribute.get_value())
if enum_constant == attribute.get_default():
btn.add_theme_font_override("font", bold_font)
Expand Down
Loading

0 comments on commit b2224ed

Please sign in to comment.