1
0
Fork 0
mirror of https://github.com/Orama-Interactive/Pixelorama.git synced 2025-01-18 17:19:50 +00:00

Implement the Keychain Plugin (#700)

* Start implementing the godot_better_input plugin

* Update ShortcutEdit.gd

* Load & save preset option

* Add some groups and fix action events not being deleted on load

* Add MenuInputAction class for multiple menu accelerators

* Create a proper plugin and a BetterInput autoload

* Update menu accelerators

* Move settings to BetterInput

* Move menu enums to Global, make more MenuInputActions

* Add more menu events

* Add new groups

* Optimize BetterInput _input() method

* Remove a lot of lines of code

* Change some previous events, add ignore actions and a View menu group

* Change update_item_accelerator to update_ui

* Move MenuInputAction initialization to BetterInput.gd

* Update hint tooltips when a shortcut changes

Temporarily comment out some code regarding the configurable modifiers

* Some MenuInputAction variable name changes

* Add handle_input() to InputAction

* Update the shortcuts of buttons

* Fix shortcut selector menu position

* Change plugin name into Keychain

* Fix keyboard input dialog exiting when Enter or Space is being pressed

* Add two more groups

* Make groups folded by default

* Temporarily make tool modifier shortcuts not configurable

A temporary change, they will be made configurable again, with different actions that are currently mapped to the same events, local/independent from each other.

* Add license for Keychain

* Fix issue where a key event would be added in other input types

* Fix bug where the assigned state was not updated when the dialog appeared again

* Update Main.tscn

* Add a disabled line edit in keyboard shortcut selector to grab focus

* Load presets in the Keychain autoload

This way, the input actions get updated from the start, instead of only at the ShortcutEdit scene.

WARNING, this currently causes crashes if the menu items have no shortcut binded to them.

* Move custom settings away from Keychain.gd

To keep it the same as the upstream plugin

* Change menu enum names

* Made action_get_first_key() more general

* Use arrays for menu items instead of dictionaries, fixes crash

* Move moveable panels to Window menu

* Format

* Optimize hint tooltip updating

* Add support for translations in Keychain

* Translation changes

* Made tool modifiers configurable

Needs more testing.

* Made camera arrow key movement configurable & joypad axis support

This commit removes the ability to press Shift and Control+Shift to adjust the camera arrow key movement speed. Instead, the speed depends on the zoom level.

The right joypad analog stick is configured to move the camera by default.

* Rename presets into shortcut profiles, use Resources and let users create their own

* [skip ci] Update addons README

* Update Global.gd
This commit is contained in:
Emmanouil Papadeas 2022-05-16 15:07:51 +03:00 committed by GitHub
parent 1e0eb19c15
commit f509dfb9f6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
65 changed files with 2484 additions and 1983 deletions

View file

@ -1,5 +1,11 @@
# Addons # Addons
## Keychain
- Upstream: https://github.com/Orama-Interactive/Keychain
- Version: Based on git commit 751540203124426947d69bd81f50fea07eded91d with slight modifications in Keychain.gd and ShortcutEdit.tscn.
- License: [MIT](https://github.com/Orama-Interactive/Keychain/blob/main/LICENSE)
## gdgifexporter ## gdgifexporter
- Upstream: https://github.com/jegor377/godot-gdgifexporter - Upstream: https://github.com/jegor377/godot-gdgifexporter

216
addons/keychain/Keychain.gd Normal file
View file

@ -0,0 +1,216 @@
extends Node
const TRANSLATIONS_PATH := "res://addons/keychain/translations"
const PROFILES_PATH := "user://shortcut_profiles"
# Change these settings
var profiles := [preload("profiles/default.tres")]
var selected_profile: ShortcutProfile = profiles[0]
var profile_index := 0
# Syntax: "action_name": InputAction.new("Action Display Name", "Group", true)
# Note that "action_name" must already exist in the Project's Input Map.
var actions := {}
# Syntax: "Group Name": InputGroup.new("Parent Group Name")
var groups := {}
var ignore_actions := []
var ignore_ui_actions := true
var changeable_types := [true, true, true, true]
var multiple_menu_accelerators := false
var config_path := "user://cache.ini"
var config_file: ConfigFile
class InputAction:
var display_name := ""
var group := ""
var global := true
func _init(_display_name := "", _group := "", _global := true) -> void:
display_name = _display_name
group = _group
global = _global
func update_node(_action: String) -> void:
pass
func handle_input(_event: InputEvent, _action: String) -> bool:
return false
# This class is useful for the accelerators of PopupMenu items
# It's possible for PopupMenu items to have multiple shortcuts by using
# set_item_shortcut(), but we have no control over the accelerator text that appears.
# Thus, we are stuck with using accelerators instead of shortcuts.
# If Godot ever receives the ability to change the accelerator text of the items,
# we could in theory remove this class.
# If you don't care about PopupMenus in the same scene as ShortcutEdit
# such as projects like Pixelorama where everything is in the same scene,
# then you can ignore this class.
class MenuInputAction:
extends InputAction
var node_path := ""
var node: PopupMenu
var menu_item_id := 0
var echo := false
func _init(
_display_name := "",
_group := "",
_global := true,
_node_path := "",
_menu_item_id := 0,
_echo := false
) -> void:
._init(_display_name, _group, _global)
node_path = _node_path
menu_item_id = _menu_item_id
echo = _echo
func get_node(root: Node) -> void:
var temp_node = root.get_node(node_path)
if temp_node is PopupMenu:
node = node
elif temp_node is MenuButton:
node = temp_node.get_popup()
func update_node(action: String) -> void:
if !node:
return
var first_key: InputEventKey = Keychain.action_get_first_key(action)
var accel := first_key.get_scancode_with_modifiers() if first_key else 0
node.set_item_accelerator(menu_item_id, accel)
func handle_input(event: InputEvent, action: String) -> bool:
if not node:
return false
if event.is_action_pressed(action):
if event is InputEventKey:
var acc: int = node.get_item_accelerator(menu_item_id)
# If the event is the same as the menu item's accelerator, skip
if acc == event.get_scancode_with_modifiers():
return true
node.emit_signal("id_pressed", menu_item_id)
return true
if event.is_action(action) and echo:
if event.is_echo():
node.emit_signal("id_pressed", menu_item_id)
return true
return false
class InputGroup:
var parent_group := ""
var folded := true
var tree_item: TreeItem
func _init(_parent_group := "", _folded := true) -> void:
parent_group = _parent_group
folded = _folded
func _ready() -> void:
if !config_file:
config_file = ConfigFile.new()
if !config_path.empty():
config_file.load(config_path)
set_process_input(multiple_menu_accelerators)
# Load shortcut profiles
var profile_dir := Directory.new()
profile_dir.make_dir(PROFILES_PATH)
profile_dir.open(PROFILES_PATH)
profile_dir.list_dir_begin()
var file_name = profile_dir.get_next()
while file_name != "":
if !profile_dir.current_is_dir():
if file_name.get_extension() == "tres":
var file = load(PROFILES_PATH.plus_file(file_name))
if file is ShortcutProfile:
profiles.append(file)
file_name = profile_dir.get_next()
# If there are no profiles besides the default, create one custom
if profiles.size() == 1:
var profile := ShortcutProfile.new()
profile.name = "Custom"
profile.resource_path = PROFILES_PATH.plus_file("custom.tres")
var saved := profile.save()
if saved:
profiles.append(profile)
for profile in profiles:
profile.fill_bindings()
var l18n_dir := Directory.new()
l18n_dir.open(TRANSLATIONS_PATH)
l18n_dir.list_dir_begin()
file_name = l18n_dir.get_next()
while file_name != "":
if !l18n_dir.current_is_dir():
if file_name.get_extension() == "po":
var t: Translation = load(TRANSLATIONS_PATH.plus_file(file_name))
TranslationServer.add_translation(t)
file_name = l18n_dir.get_next()
profile_index = config_file.get_value("shortcuts", "shortcuts_profile", 0)
change_profile(profile_index)
for action in actions:
var input_action: InputAction = actions[action]
if input_action is MenuInputAction:
# Below line has been modified
input_action.get_node(Global.top_menu_container.get_node("MenuItems"))
func _input(event: InputEvent) -> void:
if event is InputEventMouseMotion:
return
for action in actions:
var input_action: InputAction = actions[action]
var done: bool = input_action.handle_input(event, action)
if done:
return
func change_profile(index: int) -> void:
if index >= profiles.size():
index = profiles.size() - 1
profile_index = index
selected_profile = profiles[index]
for action in selected_profile.bindings:
action_erase_events(action)
for event in selected_profile.bindings[action]:
action_add_event(action, event)
# NOTE: Following line not present in the plugin itself, be careful not to overwrite
Global.update_hint_tooltips()
func action_add_event(action: String, event: InputEvent) -> void:
InputMap.action_add_event(action, event)
if action in actions:
actions[action].update_node(action)
func action_erase_event(action: String, event: InputEvent) -> void:
InputMap.action_erase_event(action, event)
if action in actions:
actions[action].update_node(action)
func action_erase_events(action: String) -> void:
InputMap.action_erase_events(action)
if action in actions:
actions[action].update_node(action)
func action_get_first_key(action: String) -> InputEventKey:
var first_key: InputEventKey = null
var events := InputMap.get_action_list(action)
for event in events:
if event is InputEventKey:
first_key = event
break
return first_key

21
addons/keychain/LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Orama Interactive
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,410 @@
extends Control
enum { KEYBOARD, MOUSE, JOY_BUTTON, JOY_AXIS }
const MOUSE_BUTTON_NAMES := [
"Left Button",
"Right Button",
"Middle Button",
"Wheel Up Button",
"Wheel Down Button",
"Wheel Left Button",
"Wheel Right Button",
"X Button 1",
"X Button 2",
]
const JOY_BUTTON_NAMES := [
"DualShock Cross, Xbox A, Nintendo B",
"DualShock Circle, Xbox B, Nintendo A",
"DualShock Square, Xbox X, Nintendo Y",
"DualShock Triangle, Xbox Y, Nintendo X",
"L, L1",
"R, R1",
"L2",
"R2",
"L3",
"R3",
"Select, DualShock Share, Nintendo -",
"Start, DualShock Options, Nintendo +",
"D-Pad Up",
"D-Pad Down",
"D-Pad Left",
"D-Pad Right",
"Home, DualShock PS, Guide",
"Xbox Share, PS5 Microphone, Nintendo Capture",
"Xbox Paddle 1",
"Xbox Paddle 2",
"Xbox Paddle 3",
"Xbox Paddle 4",
"PS4/5 Touchpad",
]
const JOY_AXIS_NAMES := [
"(Left Stick Left)",
"(Left Stick Right)",
"(Left Stick Up)",
"(Left Stick Down)",
"(Right Stick Left)",
"(Right Stick Right)",
"(Right Stick Up)",
"(Right Stick Down)",
"",
"",
"",
"",
"",
"(L2)",
"",
"(R2)",
"",
"",
"",
"",
]
var currently_editing_tree_item: TreeItem
var is_editing := false
# Textures taken from Godot https://github.com/godotengine/godot/tree/master/editor/icons
var add_tex: Texture = preload("assets/add.svg")
var edit_tex: Texture = preload("assets/edit.svg")
var delete_tex: Texture = preload("assets/close.svg")
var joy_axis_tex: Texture = preload("assets/joy_axis.svg")
var joy_button_tex: Texture = preload("assets/joy_button.svg")
var key_tex: Texture = preload("assets/keyboard.svg")
var key_phys_tex: Texture = preload("assets/keyboard_physical.svg")
var mouse_tex: Texture = preload("assets/mouse.svg")
var shortcut_tex: Texture = preload("assets/shortcut.svg")
var folder_tex: Texture = preload("assets/folder.svg")
onready var tree: Tree = $VBoxContainer/ShortcutTree
onready var profile_option_button: OptionButton = find_node("ProfileOptionButton")
onready var rename_profile_button: Button = find_node("RenameProfile")
onready var delete_profile_button: Button = find_node("DeleteProfile")
onready var shortcut_type_menu: PopupMenu = $ShortcutTypeMenu
onready var keyboard_shortcut_selector: ConfirmationDialog = $KeyboardShortcutSelectorDialog
onready var mouse_shortcut_selector: ConfirmationDialog = $MouseShortcutSelectorDialog
onready var joy_key_shortcut_selector: ConfirmationDialog = $JoyKeyShortcutSelectorDialog
onready var joy_axis_shortcut_selector: ConfirmationDialog = $JoyAxisShortcutSelectorDialog
onready var profile_settings: ConfirmationDialog = $ProfileSettings
onready var profile_name: LineEdit = $ProfileSettings/ProfileName
onready var delete_confirmation: ConfirmationDialog = $DeleteConfirmation
func _ready() -> void:
for profile in Keychain.profiles:
profile_option_button.add_item(profile.name)
_fill_selector_options()
# Remove input types that are not changeable
var i := 0
for type in Keychain.changeable_types:
if !type:
shortcut_type_menu.remove_item(i)
else:
i += 1
profile_option_button.select(Keychain.profile_index)
_on_ProfileOptionButton_item_selected(Keychain.profile_index)
func _construct_tree() -> void:
var buttons_disabled := false if Keychain.selected_profile.customizable else true
var tree_root: TreeItem = tree.create_item()
for group in Keychain.groups: # Create groups
var input_group: Keychain.InputGroup = Keychain.groups[group]
_create_group_tree_item(input_group, group)
for action in InputMap.get_actions(): # Fill the tree with actions and their events
if action in Keychain.ignore_actions:
continue
if Keychain.ignore_ui_actions and action.begins_with("ui_"):
continue
var display_name := get_action_name(action)
var group_name := ""
if action in Keychain.actions:
var input_action: Keychain.InputAction = Keychain.actions[action]
group_name = input_action.group
var tree_item: TreeItem
if group_name and group_name in Keychain.groups:
var input_group: Keychain.InputGroup = Keychain.groups[group_name]
var group_root: TreeItem = input_group.tree_item
tree_item = tree.create_item(group_root)
else:
tree_item = tree.create_item(tree_root)
tree_item.set_text(0, display_name)
tree_item.set_metadata(0, action)
tree_item.set_icon(0, shortcut_tex)
for event in InputMap.get_action_list(action):
add_event_tree_item(event, tree_item)
tree_item.add_button(0, add_tex, 0, buttons_disabled, "Add")
tree_item.add_button(0, delete_tex, 1, buttons_disabled, "Delete")
tree_item.collapsed = true
func _fill_selector_options() -> void:
keyboard_shortcut_selector.entered_shortcut.visible = true
keyboard_shortcut_selector.option_button.visible = false
mouse_shortcut_selector.input_type_l.text = "Mouse Button Index:"
joy_key_shortcut_selector.input_type_l.text = "Joypad Button Index:"
joy_axis_shortcut_selector.input_type_l.text = "Joypad Axis Index:"
var mouse_option_button: OptionButton = mouse_shortcut_selector.option_button
for option in MOUSE_BUTTON_NAMES:
mouse_option_button.add_item(option)
var joy_key_option_button: OptionButton = joy_key_shortcut_selector.option_button
for i in JOY_BUTTON_MAX:
var text: String = tr("Button") + " %s" % i
if i < JOY_BUTTON_NAMES.size():
text += " (%s)" % tr(JOY_BUTTON_NAMES[i])
joy_key_option_button.add_item(text)
var joy_axis_option_button: OptionButton = joy_axis_shortcut_selector.option_button
var i := 0.0
for option in JOY_AXIS_NAMES:
var sign_symbol = "+" if floor(i) != i else "-"
var text: String = tr("Axis") + " %s %s %s" % [floor(i), sign_symbol, tr(option)]
joy_axis_option_button.add_item(text)
i += 0.5
func _create_group_tree_item(group: Keychain.InputGroup, group_name: String) -> void:
if group.tree_item:
return
var group_root: TreeItem
if group.parent_group:
var parent_group: Keychain.InputGroup = Keychain.groups[group.parent_group]
_create_group_tree_item(parent_group, group.parent_group)
group_root = tree.create_item(parent_group.tree_item)
else:
group_root = tree.create_item(tree.get_root())
group_root.set_text(0, group_name)
group_root.set_icon(0, folder_tex)
group.tree_item = group_root
if group.folded:
group_root.collapsed = true
func get_action_name(action: String) -> String:
var display_name := ""
if action in Keychain.actions:
display_name = Keychain.actions[action].display_name
if display_name.empty():
display_name = _humanize_snake_case(action)
return display_name
func _humanize_snake_case(text: String) -> String:
text = text.replace("_", " ")
var first_letter := text.left(1)
first_letter = first_letter.capitalize()
text.erase(0, 1)
text = text.insert(0, first_letter)
return text
func add_event_tree_item(event: InputEvent, action_tree_item: TreeItem) -> void:
var event_class := event.get_class()
match event_class:
"InputEventKey":
if !Keychain.changeable_types[0]:
return
"InputEventMouseButton":
if !Keychain.changeable_types[1]:
return
"InputEventJoypadButton":
if !Keychain.changeable_types[2]:
return
"InputEventJoypadMotion":
if !Keychain.changeable_types[3]:
return
var buttons_disabled := false if Keychain.selected_profile.customizable else true
var event_tree_item: TreeItem = tree.create_item(action_tree_item)
event_tree_item.set_text(0, event_to_str(event))
event_tree_item.set_metadata(0, event)
match event_class:
"InputEventKey":
var scancode: int = event.get_scancode_with_modifiers()
if scancode > 0:
event_tree_item.set_icon(0, key_tex)
else:
event_tree_item.set_icon(0, key_phys_tex)
"InputEventMouseButton":
event_tree_item.set_icon(0, mouse_tex)
"InputEventJoypadButton":
event_tree_item.set_icon(0, joy_button_tex)
"InputEventJoypadMotion":
event_tree_item.set_icon(0, joy_axis_tex)
event_tree_item.add_button(0, edit_tex, 0, buttons_disabled, "Edit")
event_tree_item.add_button(0, delete_tex, 1, buttons_disabled, "Delete")
func event_to_str(event: InputEvent) -> String:
var output := ""
if event is InputEventKey:
var scancode: int = event.get_scancode_with_modifiers()
var physical_str := ""
if scancode == 0:
scancode = event.get_physical_scancode_with_modifiers()
physical_str = " " + tr("(Physical)")
output = OS.get_scancode_string(scancode) + physical_str
elif event is InputEventMouseButton:
output = tr(MOUSE_BUTTON_NAMES[event.button_index - 1])
elif event is InputEventJoypadButton:
var button_index: int = event.button_index
output = tr("Button")
if button_index >= JOY_BUTTON_NAMES.size():
output += " %s" % button_index
else:
output += " %s (%s)" % [button_index, tr(JOY_BUTTON_NAMES[button_index])]
elif event is InputEventJoypadMotion:
var positive_axis: bool = event.axis_value > 0
var axis_value: int = event.axis * 2 + int(positive_axis)
var sign_symbol = "+" if positive_axis else "-"
output = tr("Axis")
output += " %s %s %s" % [event.axis, sign_symbol, tr(JOY_AXIS_NAMES[axis_value])]
return output
func _on_ShortcutTree_button_pressed(item: TreeItem, _column: int, id: int) -> void:
var action = item.get_metadata(0)
currently_editing_tree_item = item
if action is String:
if id == 0: # Add
var rect: Rect2 = tree.get_item_area_rect(item, 0)
rect.position.x = rect.end.x - 42
rect.position.y += 42 - tree.get_scroll().y
rect.position += rect_global_position
rect.size = Vector2(110, 23 * shortcut_type_menu.get_item_count())
shortcut_type_menu.popup(rect)
elif id == 1: # Delete
Keychain.action_erase_events(action)
Keychain.selected_profile.change_action(action)
var child := item.get_children()
while child != null:
child.free()
child = item.get_children()
elif action is InputEvent:
var parent_action = item.get_parent().get_metadata(0)
if id == 0: # Edit
if action is InputEventKey:
keyboard_shortcut_selector.popup_centered()
elif action is InputEventMouseButton:
mouse_shortcut_selector.popup_centered()
elif action is InputEventJoypadButton:
joy_key_shortcut_selector.popup_centered()
elif action is InputEventJoypadMotion:
joy_axis_shortcut_selector.popup_centered()
elif id == 1: # Delete
if not parent_action is String:
return
Keychain.action_erase_event(parent_action, action)
Keychain.selected_profile.change_action(parent_action)
item.free()
func _on_ShortcutTree_item_activated() -> void:
var selected_item: TreeItem = tree.get_selected()
if selected_item.get_button_count(0) > 0 and !selected_item.is_button_disabled(0, 0):
_on_ShortcutTree_button_pressed(tree.get_selected(), 0, 0)
func _on_ShortcutTypeMenu_id_pressed(id: int) -> void:
if id == KEYBOARD:
keyboard_shortcut_selector.popup_centered()
elif id == MOUSE:
mouse_shortcut_selector.popup_centered()
elif id == JOY_BUTTON:
joy_key_shortcut_selector.popup_centered()
elif id == JOY_AXIS:
joy_axis_shortcut_selector.popup_centered()
func _on_ProfileOptionButton_item_selected(index: int) -> void:
Keychain.change_profile(index)
rename_profile_button.disabled = false if Keychain.selected_profile.customizable else true
delete_profile_button.disabled = false if Keychain.selected_profile.customizable else true
# Re-construct the tree
for group in Keychain.groups:
Keychain.groups[group].tree_item = null
tree.clear()
_construct_tree()
Keychain.config_file.set_value("shortcuts", "shortcuts_profile", index)
Keychain.config_file.save(Keychain.config_path)
func _on_NewProfile_pressed() -> void:
is_editing = false
profile_name.text = "New Shortcut Profile"
profile_settings.window_title = "New Shortcut Profile"
profile_settings.popup_centered()
func _on_RenameProfile_pressed() -> void:
is_editing = true
profile_name.text = Keychain.selected_profile.name
profile_settings.window_title = "Rename Shortcut Profile"
profile_settings.popup_centered()
func _on_DeleteProfile_pressed() -> void:
delete_confirmation.popup_centered()
func _on_OpenProfileFolder_pressed() -> void:
OS.shell_open(ProjectSettings.globalize_path(Keychain.PROFILES_PATH))
func _on_ProfileSettings_confirmed() -> void:
var file_name := profile_name.text + ".tres"
var profile := ShortcutProfile.new()
profile.name = profile_name.text
profile.resource_path = Keychain.PROFILES_PATH.plus_file(file_name)
profile.fill_bindings()
var saved := profile.save()
if not saved:
return
if is_editing:
var old_file_name: String = Keychain.selected_profile.resource_path
if old_file_name != file_name:
_delete_profile_file(old_file_name)
Keychain.profiles[Keychain.profile_index] = profile
profile_option_button.set_item_text(Keychain.profile_index, profile.name)
else: # Add new shortcut profile
Keychain.profiles.append(profile)
profile_option_button.add_item(profile.name)
Keychain.profile_index = Keychain.profiles.size() - 1
profile_option_button.select(Keychain.profile_index)
_on_ProfileOptionButton_item_selected(Keychain.profile_index)
func _delete_profile_file(file_name: String) -> void:
var dir := Directory.new()
dir.remove(file_name)
func _on_DeleteConfirmation_confirmed() -> void:
_delete_profile_file(Keychain.selected_profile.resource_path)
profile_option_button.remove_item(Keychain.profile_index)
Keychain.profiles.remove(Keychain.profile_index)
Keychain.profile_index -= 1
if Keychain.profile_index < 0:
Keychain.profile_index = 0
profile_option_button.select(Keychain.profile_index)
_on_ProfileOptionButton_item_selected(Keychain.profile_index)

View file

@ -0,0 +1,114 @@
[gd_scene load_steps=7 format=2]
[ext_resource path="res://addons/keychain/ShortcutEdit.gd" type="Script" id=1]
[ext_resource path="res://addons/keychain/assets/joy_button.svg" type="Texture" id=2]
[ext_resource path="res://addons/keychain/assets/keyboard.svg" type="Texture" id=3]
[ext_resource path="res://addons/keychain/assets/joy_axis.svg" type="Texture" id=4]
[ext_resource path="res://addons/keychain/assets/mouse.svg" type="Texture" id=5]
[ext_resource path="res://addons/keychain/ShortcutSelectorDialog.tscn" type="PackedScene" id=6]
[node name="ShortcutEdit" type="VBoxContainer"]
anchor_right = 1.0
anchor_bottom = 1.0
size_flags_vertical = 3
script = ExtResource( 1 )
[node name="VBoxContainer" type="VBoxContainer" parent="."]
margin_right = 1280.0
margin_bottom = 720.0
size_flags_vertical = 3
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
margin_right = 1280.0
margin_bottom = 20.0
[node name="ProfileLabel" type="Label" parent="VBoxContainer/HBoxContainer"]
margin_top = 3.0
margin_right = 102.0
margin_bottom = 17.0
text = "Shortcut profile:"
[node name="ProfileOptionButton" type="OptionButton" parent="VBoxContainer/HBoxContainer"]
margin_left = 106.0
margin_right = 135.0
margin_bottom = 20.0
mouse_default_cursor_shape = 2
[node name="NewProfile" type="Button" parent="VBoxContainer/HBoxContainer"]
margin_left = 139.0
margin_right = 179.0
margin_bottom = 20.0
mouse_default_cursor_shape = 2
text = "New"
[node name="RenameProfile" type="Button" parent="VBoxContainer/HBoxContainer"]
margin_left = 183.0
margin_right = 247.0
margin_bottom = 20.0
mouse_default_cursor_shape = 2
text = "Rename"
[node name="DeleteProfile" type="Button" parent="VBoxContainer/HBoxContainer"]
margin_left = 251.0
margin_right = 306.0
margin_bottom = 20.0
mouse_default_cursor_shape = 2
text = "Delete"
[node name="OpenProfileFolder" type="Button" parent="VBoxContainer/HBoxContainer"]
margin_left = 310.0
margin_right = 401.0
margin_bottom = 20.0
mouse_default_cursor_shape = 2
text = "Open Folder"
[node name="ShortcutTree" type="Tree" parent="VBoxContainer"]
margin_top = 24.0
margin_right = 1280.0
margin_bottom = 720.0
size_flags_vertical = 3
hide_root = true
[node name="ShortcutTypeMenu" type="PopupMenu" parent="."]
margin_right = 20.0
margin_bottom = 20.0
items = [ "Key", ExtResource( 3 ), 0, false, false, 0, 0, null, "", false, "Mouse Button", ExtResource( 5 ), 0, false, false, 1, 0, null, "", false, "Joy Button", ExtResource( 2 ), 0, false, false, 2, 0, null, "", false, "Joy Axis", ExtResource( 4 ), 0, false, false, 3, 0, null, "", false ]
[node name="KeyboardShortcutSelectorDialog" parent="." instance=ExtResource( 6 )]
[node name="MouseShortcutSelectorDialog" parent="." instance=ExtResource( 6 )]
input_type = 1
[node name="JoyKeyShortcutSelectorDialog" parent="." instance=ExtResource( 6 )]
input_type = 2
[node name="JoyAxisShortcutSelectorDialog" parent="." instance=ExtResource( 6 )]
input_type = 3
[node name="ProfileSettings" type="ConfirmationDialog" parent="."]
margin_right = 200.0
margin_bottom = 70.0
[node name="ProfileName" type="LineEdit" parent="ProfileSettings"]
margin_left = 8.0
margin_top = 8.0
margin_right = 192.0
margin_bottom = 34.0
caret_blink = true
caret_blink_speed = 0.5
[node name="DeleteConfirmation" type="ConfirmationDialog" parent="."]
margin_right = 200.0
margin_bottom = 70.0
dialog_text = "Are you sure you want to delete this shortcut profile?"
[connection signal="item_selected" from="VBoxContainer/HBoxContainer/ProfileOptionButton" to="." method="_on_ProfileOptionButton_item_selected"]
[connection signal="pressed" from="VBoxContainer/HBoxContainer/NewProfile" to="." method="_on_NewProfile_pressed"]
[connection signal="pressed" from="VBoxContainer/HBoxContainer/RenameProfile" to="." method="_on_RenameProfile_pressed"]
[connection signal="pressed" from="VBoxContainer/HBoxContainer/DeleteProfile" to="." method="_on_DeleteProfile_pressed"]
[connection signal="pressed" from="VBoxContainer/HBoxContainer/OpenProfileFolder" to="." method="_on_OpenProfileFolder_pressed"]
[connection signal="button_pressed" from="VBoxContainer/ShortcutTree" to="." method="_on_ShortcutTree_button_pressed"]
[connection signal="item_activated" from="VBoxContainer/ShortcutTree" to="." method="_on_ShortcutTree_item_activated"]
[connection signal="id_pressed" from="ShortcutTypeMenu" to="." method="_on_ShortcutTypeMenu_id_pressed"]
[connection signal="confirmed" from="ProfileSettings" to="." method="_on_ProfileSettings_confirmed"]
[connection signal="confirmed" from="DeleteConfirmation" to="." method="_on_DeleteConfirmation_confirmed"]

View file

@ -0,0 +1,31 @@
class_name ShortcutProfile
extends Resource
export(String) var name := ""
export(bool) var customizable := true
export(Dictionary) var bindings := {}
func _init() -> void:
bindings = bindings.duplicate(true)
func fill_bindings() -> void:
for action in InputMap.get_actions():
if not action in bindings:
bindings[action] = InputMap.get_action_list(action)
func change_action(action: String) -> void:
if not customizable:
return
bindings[action] = InputMap.get_action_list(action)
save()
func save() -> bool:
var err := ResourceSaver.save(resource_path, self)
if err != OK:
print("Error saving shortcut profile %s. Error code: %s" % [resource_path, err])
return false
return true

View file

@ -0,0 +1,190 @@
extends ConfirmationDialog
enum InputTypes { KEYBOARD, MOUSE, JOY_BUTTON, JOY_AXIS }
export(InputTypes) var input_type: int = InputTypes.KEYBOARD
var listened_input: InputEvent
onready var root: Node = get_parent()
onready var input_type_l: Label = $VBoxContainer/InputTypeLabel
onready var entered_shortcut: LineEdit = $VBoxContainer/EnteredShortcut
onready var option_button: OptionButton = $VBoxContainer/OptionButton
onready var already_exists: Label = $VBoxContainer/AlreadyExistsLabel
func _ready() -> void:
set_process_input(false)
if input_type == InputTypes.KEYBOARD:
get_ok().focus_neighbour_top = entered_shortcut.get_path()
get_cancel().focus_neighbour_top = entered_shortcut.get_path()
entered_shortcut.focus_neighbour_bottom = get_ok().get_path()
else:
get_ok().focus_neighbour_top = option_button.get_path()
get_cancel().focus_neighbour_top = option_button.get_path()
option_button.focus_neighbour_bottom = get_ok().get_path()
get_close_button().focus_mode = Control.FOCUS_NONE
func _input(event: InputEvent) -> void:
if not event is InputEventKey:
return
if event.pressed:
listened_input = event
entered_shortcut.text = OS.get_scancode_string(event.get_scancode_with_modifiers())
_show_assigned_state(event)
func _show_assigned_state(event: InputEvent) -> void:
var metadata = root.currently_editing_tree_item.get_metadata(0)
var action := ""
if metadata is InputEvent: # Editing an input event
action = root.currently_editing_tree_item.get_parent().get_metadata(0)
elif metadata is String: # Adding a new input event to an action
action = metadata
var matching_pair: Array = _find_matching_event_in_map(action, event)
if matching_pair:
already_exists.text = tr("Already assigned to: %s") % root.get_action_name(matching_pair[0])
else:
already_exists.text = ""
func _on_ShortcutSelectorDialog_confirmed() -> void:
if listened_input == null:
return
_apply_shortcut_change(listened_input)
func _apply_shortcut_change(input_event: InputEvent) -> void:
var metadata = root.currently_editing_tree_item.get_metadata(0)
if metadata is InputEvent: # Editing an input event
var parent_metadata = root.currently_editing_tree_item.get_parent().get_metadata(0)
var changed: bool = _set_shortcut(parent_metadata, metadata, input_event)
if !changed:
return
root.currently_editing_tree_item.set_metadata(0, input_event)
root.currently_editing_tree_item.set_text(0, root.event_to_str(input_event))
elif metadata is String: # Adding a new input event to an action
var changed: bool = _set_shortcut(metadata, null, input_event)
if !changed:
return
root.add_event_tree_item(input_event, root.currently_editing_tree_item)
func _set_shortcut(action: String, old_event: InputEvent, new_event: InputEvent) -> bool:
if InputMap.action_has_event(action, new_event): # If the current action already has that event
return false
if old_event:
Keychain.action_erase_event(action, old_event)
# Loop through other actions to see if the event exists there, to re-assign it
var matching_pair := _find_matching_event_in_map(action, new_event)
if matching_pair:
var group := ""
if action in Keychain.actions:
group = Keychain.actions[action].group
var action_to_replace: String = matching_pair[0]
var input_to_replace: InputEvent = matching_pair[1]
Keychain.action_erase_event(action_to_replace, input_to_replace)
Keychain.selected_profile.change_action(action_to_replace)
var tree_item: TreeItem = root.tree.get_root()
var prev_tree_item: TreeItem
while tree_item != null: # Loop through Tree's TreeItems...
var metadata = tree_item.get_metadata(0)
if metadata is InputEvent:
if input_to_replace.shortcut_match(metadata):
var map_action: String = tree_item.get_parent().get_metadata(0)
if map_action in Keychain.actions:
# If it's local, check if it's the same group, otherwise ignore
if !Keychain.actions[map_action].global:
if Keychain.actions[map_action].group != group:
tree_item = _get_next_tree_item(tree_item)
continue
tree_item.free()
break
tree_item = _get_next_tree_item(tree_item)
Keychain.action_add_event(action, new_event)
Keychain.selected_profile.change_action(action)
return true
# Based on https://github.com/godotengine/godot/blob/master/scene/gui/tree.cpp#L685
func _get_next_tree_item(current: TreeItem) -> TreeItem:
if current.get_children():
current = current.get_children()
elif current.get_next():
current = current.get_next()
else:
while current and !current.get_next():
current = current.get_parent()
if current:
current = current.get_next()
return current
func _find_matching_event_in_map(action: String, event: InputEvent) -> Array:
var group := ""
if action in Keychain.actions:
group = Keychain.actions[action].group
for map_action in InputMap.get_actions():
if map_action in Keychain.ignore_actions:
continue
if Keychain.ignore_ui_actions and map_action.begins_with("ui_"):
continue
for map_event in InputMap.get_action_list(map_action):
if event.shortcut_match(map_event):
if map_action in Keychain.actions:
# If it's local, check if it's the same group, otherwise ignore
if !Keychain.actions[map_action].global:
if Keychain.actions[map_action].group != group:
continue
return [map_action, map_event]
return []
func _on_ShortcutSelectorDialog_about_to_show() -> void:
if input_type == InputTypes.KEYBOARD:
listened_input = null
already_exists.text = ""
entered_shortcut.text = ""
yield(get_tree(), "idle_frame")
entered_shortcut.grab_focus()
else:
if !listened_input:
_on_OptionButton_item_selected(0)
else:
_show_assigned_state(listened_input)
func _on_ShortcutSelectorDialog_popup_hide() -> void:
set_process_input(false)
func _on_OptionButton_item_selected(index: int) -> void:
if input_type == InputTypes.MOUSE:
listened_input = InputEventMouseButton.new()
listened_input.button_index = index + 1
elif input_type == InputTypes.JOY_BUTTON:
listened_input = InputEventJoypadButton.new()
listened_input.button_index = index
elif input_type == InputTypes.JOY_AXIS:
listened_input = InputEventJoypadMotion.new()
listened_input.axis = index / 2
listened_input.axis_value = -1.0 if index % 2 == 0 else 1.0
_show_assigned_state(listened_input)
func _on_EnteredShortcut_focus_entered() -> void:
set_process_input(true)
func _on_EnteredShortcut_focus_exited() -> void:
set_process_input(false)

View file

@ -0,0 +1,49 @@
[gd_scene load_steps=2 format=2]
[ext_resource path="res://addons/keychain/ShortcutSelectorDialog.gd" type="Script" id=1]
[node name="ShortcutSelectorDialog" type="ConfirmationDialog"]
margin_right = 200.0
margin_bottom = 70.0
popup_exclusive = true
window_title = "Set the shortcut"
script = ExtResource( 1 )
[node name="VBoxContainer" type="VBoxContainer" parent="."]
margin_left = 8.0
margin_top = 8.0
margin_right = 341.0
margin_bottom = 64.0
[node name="InputTypeLabel" type="Label" parent="VBoxContainer"]
margin_right = 333.0
margin_bottom = 14.0
text = "Press a key or a key combination to set the shortcut"
[node name="EnteredShortcut" type="LineEdit" parent="VBoxContainer"]
visible = false
margin_top = 18.0
margin_right = 333.0
margin_bottom = 32.0
align = 1
editable = false
virtual_keyboard_enabled = false
[node name="OptionButton" type="OptionButton" parent="VBoxContainer"]
margin_top = 18.0
margin_right = 333.0
margin_bottom = 38.0
mouse_default_cursor_shape = 2
[node name="AlreadyExistsLabel" type="Label" parent="VBoxContainer"]
margin_top = 42.0
margin_right = 333.0
margin_bottom = 56.0
align = 1
[connection signal="about_to_show" from="." to="." method="_on_ShortcutSelectorDialog_about_to_show"]
[connection signal="confirmed" from="." to="." method="_on_ShortcutSelectorDialog_confirmed"]
[connection signal="popup_hide" from="." to="." method="_on_ShortcutSelectorDialog_popup_hide"]
[connection signal="focus_entered" from="VBoxContainer/EnteredShortcut" to="." method="_on_EnteredShortcut_focus_entered"]
[connection signal="focus_exited" from="VBoxContainer/EnteredShortcut" to="." method="_on_EnteredShortcut_focus_exited"]
[connection signal="item_selected" from="VBoxContainer/OptionButton" to="." method="_on_OptionButton_item_selected"]

View file

@ -0,0 +1 @@
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m7 1v6h-6v2h6v6h2v-6h6v-2h-6v-6z" fill="#e0e0e0"/></svg>

After

Width:  |  Height:  |  Size: 149 B

View file

@ -0,0 +1,35 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/add.svg-4084e314648c872072757f9b0f544cf9.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/keychain/assets/add.svg"
dest_files=[ "res://.import/add.svg-4084e314648c872072757f9b0f544cf9.stex" ]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/bptc_ldr=0
compress/normal_map=0
flags/repeat=0
flags/filter=true
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=false
process/HDR_as_SRGB=false
process/invert_color=false
process/normal_map_invert_y=false
stream=false
size_limit=0
detect_3d=true
svg/scale=1.0

View file

@ -0,0 +1 @@
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m3.7578 2.3438-1.4141 1.4141 4.2422 4.2422-4.2422 4.2422 1.4141 1.4141 4.2422-4.2422 4.2422 4.2422 1.4141-1.4141-4.2422-4.2422 4.2422-4.2422-1.4141-1.4141-4.2422 4.2422z" fill="#e0e0e0"/></svg>

After

Width:  |  Height:  |  Size: 286 B

View file

@ -0,0 +1,35 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/close.svg-12d57fa7e5a34826f312eed6ba1feb1a.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/keychain/assets/close.svg"
dest_files=[ "res://.import/close.svg-12d57fa7e5a34826f312eed6ba1feb1a.stex" ]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/bptc_ldr=0
compress/normal_map=0
flags/repeat=0
flags/filter=true
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=false
process/HDR_as_SRGB=false
process/invert_color=false
process/normal_map_invert_y=false
stream=false
size_limit=0
detect_3d=true
svg/scale=1.0

View file

@ -0,0 +1 @@
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m7 1c-.554 0-1 .446-1 1v2h4v-2c0-.554-.446-1-1-1zm-1 4v7l2 3 2-3v-7zm1 1h1v5h-1z" fill="#e0e0e0"/></svg>

After

Width:  |  Height:  |  Size: 197 B

View file

@ -0,0 +1,35 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/edit.svg-cd9834545a8696f1e8611efa12a48f33.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/keychain/assets/edit.svg"
dest_files=[ "res://.import/edit.svg-cd9834545a8696f1e8611efa12a48f33.stex" ]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/bptc_ldr=0
compress/normal_map=0
flags/repeat=0
flags/filter=true
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=false
process/HDR_as_SRGB=false
process/invert_color=false
process/normal_map_invert_y=false
stream=false
size_limit=0
detect_3d=true
svg/scale=1.0

View file

@ -0,0 +1 @@
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m2 2a1 1 0 0 0 -1 1v2 6 2a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-7a1 1 0 0 0 -1-1h-4a1 1 0 0 1 -1-1v-1a1 1 0 0 0 -1-1z" fill="#e0e0e0"/></svg>

After

Width:  |  Height:  |  Size: 228 B

View file

@ -0,0 +1,35 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/folder.svg-490bb7e2d2aa4425de998b1d382cf534.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/keychain/assets/folder.svg"
dest_files=[ "res://.import/folder.svg-490bb7e2d2aa4425de998b1d382cf534.stex" ]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/bptc_ldr=0
compress/normal_map=0
flags/repeat=0
flags/filter=true
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=false
process/HDR_as_SRGB=false
process/invert_color=false
process/normal_map_invert_y=false
stream=false
size_limit=0
detect_3d=true
svg/scale=1.0

View file

@ -0,0 +1 @@
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 -1036.4)"><path d="m27 1038.4h7v14h-7z" fill="#fff" fill-opacity=".99608"/><g fill="#e0e0e0"><path d="m3 1a2 2 0 0 0 -2 2v10a2 2 0 0 0 2 2h12v-14zm4 2h2a1 1 0 0 1 1 1v2h2a1 1 0 0 1 1 1v2a1 1 0 0 1 -1 1h-2v2a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1-1v-2h-2a1 1 0 0 1 -1-1v-2a1 1 0 0 1 1-1h2v-2a1 1 0 0 1 1-1z" fill-opacity=".99608" transform="translate(0 1036.4)"/><circle cx="8" cy="1044.4" r="1"/></g></g></svg>

After

Width:  |  Height:  |  Size: 512 B

View file

@ -0,0 +1,35 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/joy_axis.svg-9e1156700cfe46afb1ca622536a9a2e1.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/keychain/assets/joy_axis.svg"
dest_files=[ "res://.import/joy_axis.svg-9e1156700cfe46afb1ca622536a9a2e1.stex" ]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/bptc_ldr=0
compress/normal_map=0
flags/repeat=0
flags/filter=true
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=false
process/HDR_as_SRGB=false
process/invert_color=false
process/normal_map_invert_y=false
stream=false
size_limit=0
detect_3d=true
svg/scale=1.0

View file

@ -0,0 +1 @@
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><g fill-opacity=".99608" transform="translate(0 -1036.4)"><path d="m27 1038.4h7v14h-7z" fill="#fff"/><path d="m1 1v14h12c1.1046 0 2-.8954 2-2v-10c0-1.1046-.89543-2-2-2zm7 1a2 2 0 0 1 2 2 2 2 0 0 1 -2 2 2 2 0 0 1 -2-2 2 2 0 0 1 2-2zm-4 4a2 2 0 0 1 2 2 2 2 0 0 1 -2 2 2 2 0 0 1 -2-2 2 2 0 0 1 2-2zm8 0a2 2 0 0 1 2 2 2 2 0 0 1 -2 2 2 2 0 0 1 -2-2 2 2 0 0 1 2-2zm-4 4a2 2 0 0 1 2 2 2 2 0 0 1 -2 2 2 2 0 0 1 -2-2 2 2 0 0 1 2-2z" fill="#e0e0e0" transform="translate(0 1036.4)"/></g></svg>

After

Width:  |  Height:  |  Size: 566 B

View file

@ -0,0 +1,35 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/joy_button.svg-df5663c6f296cab556e81b9c770699d0.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/keychain/assets/joy_button.svg"
dest_files=[ "res://.import/joy_button.svg-df5663c6f296cab556e81b9c770699d0.stex" ]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/bptc_ldr=0
compress/normal_map=0
flags/repeat=0
flags/filter=true
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=false
process/HDR_as_SRGB=false
process/invert_color=false
process/normal_map_invert_y=false
stream=false
size_limit=0
detect_3d=true
svg/scale=1.0

View file

@ -0,0 +1 @@
<svg height="16" width="16" xmlns="http://www.w3.org/2000/svg"><g fill-opacity=".996"><path d="m4 2a1 1 0 0 0 -1 1v9.084c0 .506.448.916 1 .916h8c.552 0 1-.41 1-.916v-9.084a1 1 0 0 0 -1-1zm1.543 1.139h1.393l1.834 4.199h1.295v.437c.708.052 1.246.239 1.61.559.368.316.55.747.55 1.295 0 .552-.182.99-.55 1.314-.368.32-.906.505-1.61.553v.467h-1.294v-.473c-.708-.06-1.247-.248-1.615-.564-.364-.316-.545-.75-.545-1.297 0-.548.181-.977.545-1.29.368-.315.907-.504 1.615-.564v-.437h-1.464l-.282-.733h-1.595l-.284.733h-1.439l1.836-4.2zm.684 1.39-.409 1.057h.817zm3.84 4.338v1.526c.28-.04.483-.12.607-.24.124-.125.185-.302.185-.53 0-.224-.063-.396-.191-.516-.124-.12-.326-.2-.602-.24zm-1.296.006c-.284.04-.487.12-.61.24-.12.116-.182.288-.182.516 0 .22.065.392.193.512.132.12.331.202.6.246v-1.514z" fill="#e0e0e0"/><path d="m27 2h7v14h-7z" fill="#fff"/><path d="m1 4v9a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-9h-1v9a1 1 0 0 1 -1 1h-10a1 1 0 0 1 -1-1v-9z" fill="#e0e0e0"/></g></svg>

After

Width:  |  Height:  |  Size: 961 B

View file

@ -0,0 +1,35 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/keyboard.svg-fac365b6f70899f1dfa71ce4b4761ac6.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/keychain/assets/keyboard.svg"
dest_files=[ "res://.import/keyboard.svg-fac365b6f70899f1dfa71ce4b4761ac6.stex" ]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/bptc_ldr=0
compress/normal_map=0
flags/repeat=0
flags/filter=true
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=false
process/HDR_as_SRGB=false
process/invert_color=false
process/normal_map_invert_y=false
stream=false
size_limit=0
detect_3d=true
svg/scale=1.0

View file

@ -0,0 +1 @@
<svg height="16" width="16" xmlns="http://www.w3.org/2000/svg"><g fill-opacity=".996"><path d="m4 2a1 1 0 0 0 -1 1v9.084c0 .506.448.916 1 .916h8c.552 0 1-.41 1-.916v-9.084a1 1 0 0 0 -1-1zm2.762 1.768h2.476l3.264 7.464h-2.604l-.502-1.3h-2.835l-.502 1.3h-2.561zm1.217 2.474-.725 1.878h1.45z" fill="#e0e0e0"/><path d="m27 2h7v14h-7z" fill="#fff"/><path d="m1 4v9a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-9h-1v9a1 1 0 0 1 -1 1h-10a1 1 0 0 1 -1-1v-9z" fill="#e0e0e0"/></g></svg>

After

Width:  |  Height:  |  Size: 465 B

View file

@ -0,0 +1,35 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/keyboard_physical.svg-f50c796569ade32b57ece1ba0bd7dfbb.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/keychain/assets/keyboard_physical.svg"
dest_files=[ "res://.import/keyboard_physical.svg-f50c796569ade32b57ece1ba0bd7dfbb.stex" ]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/bptc_ldr=0
compress/normal_map=0
flags/repeat=0
flags/filter=true
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=false
process/HDR_as_SRGB=false
process/invert_color=false
process/normal_map_invert_y=false
stream=false
size_limit=0
detect_3d=true
svg/scale=1.0

View file

@ -0,0 +1 @@
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m7 1.1016a5 5 0 0 0 -4 4.8984h4zm2 .0039063v4.8945h4a5 5 0 0 0 -4-4.8945zm-6 6.8945v2a5 5 0 0 0 5 5 5 5 0 0 0 5-5v-2z" fill="#e0e0e0"/></svg>

After

Width:  |  Height:  |  Size: 234 B

View file

@ -0,0 +1,35 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/mouse.svg-559695787f3bb55c16dc66bd6a9b9032.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/keychain/assets/mouse.svg"
dest_files=[ "res://.import/mouse.svg-559695787f3bb55c16dc66bd6a9b9032.stex" ]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/bptc_ldr=0
compress/normal_map=0
flags/repeat=0
flags/filter=true
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=false
process/HDR_as_SRGB=false
process/invert_color=false
process/normal_map_invert_y=false
stream=false
size_limit=0
detect_3d=true
svg/scale=1.0

View file

@ -0,0 +1 @@
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m4 2c-.55228 0-1 .4477-1 1v9.084c.0004015.506.448.91602 1 .91602h8c.552 0 .9996-.41002 1-.91602v-9.084c0-.5523-.44772-1-1-1zm-3 2v9a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-9h-1v9a.99998.99998 0 0 1 -1 1h-10a1 1 0 0 1 -1-1v-9zm6 0h3l-1 3h2l-4 4 1-3h-2z" fill="#e0e0e0" fill-opacity=".99608"/></svg>

After

Width:  |  Height:  |  Size: 382 B

View file

@ -0,0 +1,35 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/shortcut.svg-401daac18a817142e2b29e5801e00053.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/keychain/assets/shortcut.svg"
dest_files=[ "res://.import/shortcut.svg-401daac18a817142e2b29e5801e00053.stex" ]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/bptc_ldr=0
compress/normal_map=0
flags/repeat=0
flags/filter=true
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=false
process/HDR_as_SRGB=false
process/invert_color=false
process/normal_map_invert_y=false
stream=false
size_limit=0
detect_3d=true
svg/scale=1.0

View file

@ -0,0 +1,7 @@
[plugin]
name="Keychain"
description="A plugin for the Godot Engine that aims to give the player full control over the input actions of the game."
author="Orama Interactive"
version="1.1"
script="plugin.gd"

10
addons/keychain/plugin.gd Normal file
View file

@ -0,0 +1,10 @@
tool
extends EditorPlugin
func _enter_tree() -> void:
add_autoload_singleton("Keychain", "res://addons/keychain/Keychain.gd")
func _exit_tree() -> void:
remove_autoload_singleton("Keychain")

View file

@ -0,0 +1,10 @@
[gd_resource type="Resource" load_steps=2 format=2]
[ext_resource path="res://addons/keychain/ShortcutProfile.gd" type="Script" id=1]
[resource]
script = ExtResource( 1 )
name = "Default"
customizable = false
bindings = {
}

View file

@ -0,0 +1,4 @@
# Localization files for Keychain
Keychain uses .po files to handle localization. More information in the Godot documentation: [Localization using gettext](https://docs.godotengine.org/en/stable/tutorials/i18n/localization_using_gettext.html)
Simply add a .po file of the language you want to provide localization for, and Keychain will automatically add it to the TranslationServer when the project runs.

View file

@ -0,0 +1,209 @@
msgid ""
msgstr ""
msgid "OK"
msgstr ""
msgid "Cancel"
msgstr ""
msgid "Add"
msgstr ""
msgid "Edit"
msgstr ""
msgid "Delete"
msgstr ""
msgid "Shortcut profile:"
msgstr ""
msgid "Default"
msgstr ""
msgid "Custom"
msgstr ""
msgid "New"
msgstr ""
msgid "Rename"
msgstr ""
msgid "Open Folder"
msgstr ""
msgid "New Shortcut Profile"
msgstr ""
msgid "Rename Shortcut Profile"
msgstr ""
msgid "Are you sure you want to delete this shortcut profile?"
msgstr ""
msgid "Key"
msgstr ""
msgid "Mouse Button"
msgstr ""
msgid "Joy Button"
msgstr ""
msgid "Joy Axis"
msgstr ""
msgid "Button"
msgstr ""
msgid "Axis"
msgstr ""
msgid "(Physical)"
msgstr ""
msgid "Set the shortcut"
msgstr ""
msgid "Press a key or a key combination to set the shortcut"
msgstr ""
msgid "Mouse Button Index:"
msgstr ""
msgid "Joypad Button Index:"
msgstr ""
msgid "Joypad Axis Index:"
msgstr ""
msgid "Already assigned to: %s"
msgstr ""
msgid "Left Button"
msgstr ""
msgid "Right Button"
msgstr ""
msgid "Middle Button"
msgstr ""
msgid "Wheel Up Button"
msgstr ""
msgid "Wheel Down Button"
msgstr ""
msgid "Wheel Left Button"
msgstr ""
msgid "Wheel Right Button"
msgstr ""
msgid "X Button 1"
msgstr ""
msgid "X Button 2"
msgstr ""
msgid "DualShock Cross, Xbox A, Nintendo B"
msgstr ""
msgid "DualShock Circle, Xbox B, Nintendo A"
msgstr ""
msgid "DualShock Square, Xbox X, Nintendo Y"
msgstr ""
msgid "DualShock Triangle, Xbox Y, Nintendo X"
msgstr ""
msgid "L, L1"
msgstr ""
msgid "R, R1"
msgstr ""
msgid "L2"
msgstr ""
msgid "R2"
msgstr ""
msgid "L3"
msgstr ""
msgid "R3"
msgstr ""
msgid "Select, DualShock Share, Nintendo -"
msgstr ""
msgid "Start, DualShock Options, Nintendo +"
msgstr ""
msgid "D-Pad Up"
msgstr ""
msgid "D-Pad Down"
msgstr ""
msgid "D-Pad Left"
msgstr ""
msgid "D-Pad Right"
msgstr ""
msgid "Home, DualShock PS, Guide"
msgstr ""
msgid "Xbox Share, PS5 Microphone, Nintendo Capture"
msgstr ""
msgid "Xbox Paddle 1"
msgstr ""
msgid "Xbox Paddle 2"
msgstr ""
msgid "Xbox Paddle 3"
msgstr ""
msgid "Xbox Paddle 4"
msgstr ""
msgid "PS4/5 Touchpad"
msgstr ""
msgid "(Left Stick Left)"
msgstr ""
msgid "(Left Stick Right)"
msgstr ""
msgid "(Left Stick Up)"
msgstr ""
msgid "(Left Stick Down)"
msgstr ""
msgid "(Right Stick Left)"
msgstr ""
msgid "(Right Stick Right)"
msgstr ""
msgid "(Right Stick Up)"
msgstr ""
msgid "(Right Stick Down)"
msgstr ""
msgid "(L2)"
msgstr ""
msgid "(R2)"
msgstr ""

View file

@ -0,0 +1,215 @@
msgid ""
msgstr ""
"Project-Id-Version: Keychain\n"
"Last-Translator: Overloaded\n"
"Language-Team: none\n"
"Language: el_GR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "OK"
msgstr "Εντάξει"
msgid "Cancel"
msgstr "Άκυρο"
msgid "Add"
msgstr "Προσθήκη"
msgid "Edit"
msgstr "Επεξεργασία"
msgid "Delete"
msgstr "Διαγραφή"
msgid "Shortcut profile:"
msgstr "Προφίλ συντομέυσεων:"
msgid "Default"
msgstr "Προεπιλογή"
msgid "Custom"
msgstr "Προσαρμοσμένο"
msgid "New"
msgstr "Νέο"
msgid "Rename"
msgstr "Μετονομασία"
msgid "Open Folder"
msgstr "Άνοιγμα φακέλου"
msgid "New Shortcut Profile"
msgstr "Νέο προφίλ συντομέυσεων"
msgid "Rename Shortcut Profile"
msgstr "Μετονομασία προφίλ συντομεύσεων"
msgid "Are you sure you want to delete this shortcut profile?"
msgstr "Είστε σίγουροι πως θέλετε να διαγραφτεί αυτό το προφίλ συντομέυσεων;"
msgid "Key"
msgstr "Πλήκτρο"
msgid "Mouse Button"
msgstr "Κουμπί ποντικιού"
msgid "Joy Button"
msgstr "Κουμπί χειριστηρίου"
msgid "Joy Axis"
msgstr "Άξονας χειριστηρίου"
msgid "Button"
msgstr "Κουμπί"
msgid "Axis"
msgstr "Άξονας"
msgid "(Physical)"
msgstr "(Φυσικό)"
msgid "Set the shortcut"
msgstr "Επιλέξτε μια συντόμευση"
msgid "Press a key or a key combination to set the shortcut"
msgstr "Πατήστε ένα πλήκτρο ή συνδυασμό πλήκτρων για να ορίσετε τη συντόμευση"
msgid "Mouse Button Index:"
msgstr "Δείκτης κουμπιού ποντικού"
msgid "Joypad Button Index:"
msgstr "Δείκτης κουμπιού χειριστηρίου"
msgid "Joypad Axis Index:"
msgstr "Δείκτης άξονα χειριστηρίου"
msgid "Already assigned to: %s"
msgstr "Έχει ήδη εκχωρηθεί σε: %s"
msgid "Left Button"
msgstr "Αριστερό κουμπί"
msgid "Right Button"
msgstr "Δεξί κουμπί"
msgid "Middle Button"
msgstr "Μεσαίο κουμπί"
msgid "Wheel Up Button"
msgstr "Τρόχος πάνω"
msgid "Wheel Down Button"
msgstr "Τρόχος κάτω"
msgid "Wheel Left Button"
msgstr "Τρόχος αριστερά"
msgid "Wheel Right Button"
msgstr "Τρόχος δεξιά"
msgid "X Button 1"
msgstr "Κουμπί X 1"
msgid "X Button 2"
msgstr "Κουμπί X 2"
msgid "DualShock Cross, Xbox A, Nintendo B"
msgstr "DualShock Σταυρός, Xbox A, Nintendo B"
msgid "DualShock Circle, Xbox B, Nintendo A"
msgstr "DualShock Κύκλος, Xbox B, Nintendo A"
msgid "DualShock Square, Xbox X, Nintendo Y"
msgstr "DualShock Τετράγωνο, Xbox X, Nintendo Y"
msgid "DualShock Triangle, Xbox Y, Nintendo X"
msgstr "DualShock Τρίγωνο, Xbox Y, Nintendo X"
msgid "L, L1"
msgstr "L, L1"
msgid "R, R1"
msgstr "R, R1"
msgid "L2"
msgstr "L2"
msgid "R2"
msgstr "R2"
msgid "L3"
msgstr "L3"
msgid "R3"
msgstr "R3"
msgid "Select, DualShock Share, Nintendo -"
msgstr ""
msgid "Start, DualShock Options, Nintendo +"
msgstr ""
msgid "D-Pad Up"
msgstr "D-Pad Πάνω"
msgid "D-Pad Down"
msgstr "D-Pad Κάτω"
msgid "D-Pad Left"
msgstr "D-Pad Αριστερά"
msgid "D-Pad Right"
msgstr "D-Pad Δεξιά"
msgid "Home, DualShock PS, Guide"
msgstr ""
msgid "Xbox Share, PS5 Microphone, Nintendo Capture"
msgstr "Xbox Share, PS5 Μικρόφωνο, Nintendo Capture"
msgid "Xbox Paddle 1"
msgstr ""
msgid "Xbox Paddle 2"
msgstr ""
msgid "Xbox Paddle 3"
msgstr ""
msgid "Xbox Paddle 4"
msgstr ""
msgid "PS4/5 Touchpad"
msgstr "PS4/5 επιφάνεια αφής"
msgid "(Left Stick Left)"
msgstr "(Αριστερό Stick Αριστερά)"
msgid "(Left Stick Right)"
msgstr "(Αριστερό Stick Δεξιά)"
msgid "(Left Stick Up)"
msgstr "(Αριστερό Stick Πάνω)"
msgid "(Left Stick Down)"
msgstr "(Αριστερό Stick Κάτω)"
msgid "(Right Stick Left)"
msgstr "(Δεξί Stick Αριστερά)"
msgid "(Right Stick Right)"
msgstr "(Δεξί Stick Δεξιά)"
msgid "(Right Stick Up)"
msgstr "(Δεξί Stick Πάνω)"
msgid "(Right Stick Down)"
msgstr "(Δεξί Stick Κάτω)"
msgid "(L2)"
msgstr "(L2)"
msgid "(R2)"
msgstr "(R2)"

View file

@ -109,6 +109,11 @@ _global_script_classes=[ {
"language": "GDScript", "language": "GDScript",
"path": "res://src/Classes/ShaderImageEffect.gd" "path": "res://src/Classes/ShaderImageEffect.gd"
}, { }, {
"base": "Resource",
"class": "ShortcutProfile",
"language": "GDScript",
"path": "res://addons/keychain/ShortcutProfile.gd"
}, {
"base": "Guide", "base": "Guide",
"class": "SymmetryGuide", "class": "SymmetryGuide",
"language": "GDScript", "language": "GDScript",
@ -135,6 +140,7 @@ _global_script_class_icons={
"Project": "", "Project": "",
"SelectionTool": "", "SelectionTool": "",
"ShaderImageEffect": "", "ShaderImageEffect": "",
"ShortcutProfile": "",
"SymmetryGuide": "" "SymmetryGuide": ""
} }
@ -168,6 +174,7 @@ Tools="*res://src/Autoload/Tools.gd"
Html5FileExchange="*res://src/Autoload/HTML5FileExchange.gd" Html5FileExchange="*res://src/Autoload/HTML5FileExchange.gd"
Export="*res://src/Autoload/Export.gd" Export="*res://src/Autoload/Export.gd"
Palettes="*res://src/Autoload/Palettes.gd" Palettes="*res://src/Autoload/Palettes.gd"
Keychain="*res://addons/keychain/Keychain.gd"
[debug] [debug]
@ -187,7 +194,7 @@ window/per_pixel_transparency/enabled.Android=false
[editor_plugins] [editor_plugins]
enabled=PoolStringArray( "res://addons/dockable_container/plugin.cfg" ) enabled=PoolStringArray( "res://addons/dockable_container/plugin.cfg", "res://addons/keychain/plugin.cfg" )
[gui] [gui]
@ -310,6 +317,7 @@ right_shading_tool={
toggle_fullscreen={ toggle_fullscreen={
"deadzone": 0.5, "deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777254,"physical_scancode":0,"unicode":0,"echo":false,"script":null) "events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777254,"physical_scancode":0,"unicode":0,"echo":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":true,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777221,"physical_scancode":0,"unicode":0,"echo":false,"script":null)
] ]
} }
left_colorpicker_tool={ left_colorpicker_tool={
@ -332,19 +340,15 @@ ctrl={
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777238,"physical_scancode":0,"unicode":0,"echo":false,"script":null) "events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777238,"physical_scancode":0,"unicode":0,"echo":false,"script":null)
] ]
} }
redo_secondary={
"deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":true,"control":true,"meta":false,"command":true,"pressed":false,"scancode":90,"physical_scancode":0,"unicode":0,"echo":false,"script":null)
]
}
delete={ delete={
"deadzone": 0.5, "deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777224,"physical_scancode":0,"unicode":0,"echo":false,"script":null) "events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777224,"physical_scancode":0,"unicode":0,"echo":false,"script":null)
] ]
} }
space={ pan={
"deadzone": 0.5, "deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":32,"physical_scancode":0,"unicode":0,"echo":false,"script":null) "events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":32,"physical_scancode":0,"unicode":0,"echo":false,"script":null)
, Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"button_mask":0,"position":Vector2( 0, 0 ),"global_position":Vector2( 0, 0 ),"factor":1.0,"button_index":3,"pressed":false,"doubleclick":false,"script":null)
] ]
} }
new_file={ new_file={
@ -390,6 +394,7 @@ undo={
redo={ redo={
"deadzone": 0.5, "deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":true,"meta":false,"command":true,"pressed":false,"scancode":89,"physical_scancode":0,"unicode":0,"echo":false,"script":null) "events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":true,"meta":false,"command":true,"pressed":false,"scancode":89,"physical_scancode":0,"unicode":0,"echo":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":true,"control":true,"meta":false,"command":true,"pressed":false,"scancode":90,"physical_scancode":0,"unicode":0,"echo":false,"script":null)
] ]
} }
show_grid={ show_grid={
@ -552,22 +557,17 @@ right_magic_wand_tool={
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":true,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":81,"physical_scancode":0,"unicode":0,"echo":false,"script":null) "events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":true,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":81,"physical_scancode":0,"unicode":0,"echo":false,"script":null)
] ]
} }
enter={ confirm={
"deadzone": 0.5, "deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777221,"physical_scancode":0,"unicode":0,"echo":false,"script":null) "events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777221,"physical_scancode":0,"unicode":0,"echo":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777222,"physical_scancode":0,"unicode":0,"echo":false,"script":null) , Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777222,"physical_scancode":0,"unicode":0,"echo":false,"script":null)
] ]
} }
escape={ cancel={
"deadzone": 0.5, "deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777217,"physical_scancode":0,"unicode":0,"echo":false,"script":null) "events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777217,"physical_scancode":0,"unicode":0,"echo":false,"script":null)
] ]
} }
alt={
"deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777240,"physical_scancode":0,"unicode":0,"echo":false,"script":null)
]
}
left_linetool_tool={ left_linetool_tool={
"deadzone": 0.5, "deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":76,"physical_scancode":0,"unicode":0,"echo":false,"script":null) "events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":76,"physical_scancode":0,"unicode":0,"echo":false,"script":null)
@ -613,11 +613,107 @@ new_brush={
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":true,"meta":false,"command":true,"pressed":false,"scancode":66,"physical_scancode":0,"unicode":0,"echo":false,"script":null) "events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":true,"meta":false,"command":true,"pressed":false,"scancode":66,"physical_scancode":0,"unicode":0,"echo":false,"script":null)
] ]
} }
edit_mode={ moveable_panels={
"deadzone": 0.5, "deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777252,"physical_scancode":0,"unicode":0,"echo":false,"script":null) "events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777252,"physical_scancode":0,"unicode":0,"echo":false,"script":null)
] ]
} }
change_tool_mode={
"deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777238,"physical_scancode":0,"unicode":0,"echo":false,"script":null)
]
}
draw_create_line={
"deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777237,"physical_scancode":0,"unicode":0,"echo":false,"script":null)
]
}
draw_snap_angle={
"deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777238,"physical_scancode":0,"unicode":0,"echo":false,"script":null)
]
}
shape_perfect={
"deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777237,"physical_scancode":0,"unicode":0,"echo":false,"script":null)
]
}
shape_center={
"deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777238,"physical_scancode":0,"unicode":0,"echo":false,"script":null)
]
}
shape_displace={
"deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777240,"physical_scancode":0,"unicode":0,"echo":false,"script":null)
]
}
selection_add={
"deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777237,"physical_scancode":0,"unicode":0,"echo":false,"script":null)
]
}
selection_subtract={
"deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777238,"physical_scancode":0,"unicode":0,"echo":false,"script":null)
]
}
selection_intersect={
"deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":true,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777238,"physical_scancode":0,"unicode":0,"echo":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":true,"meta":false,"command":true,"pressed":false,"scancode":16777237,"physical_scancode":0,"unicode":0,"echo":false,"script":null)
]
}
transform_snap_axis={
"deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777237,"physical_scancode":0,"unicode":0,"echo":false,"script":null)
]
}
transform_snap_grid={
"deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777238,"physical_scancode":0,"unicode":0,"echo":false,"script":null)
]
}
transform_move_selection_only={
"deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777240,"physical_scancode":0,"unicode":0,"echo":false,"script":null)
]
}
transform_copy_selection_content={
"deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":true,"meta":false,"command":true,"pressed":false,"scancode":16777240,"physical_scancode":0,"unicode":0,"echo":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":true,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777238,"physical_scancode":0,"unicode":0,"echo":false,"script":null)
]
}
draw_color_picker={
"deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777240,"physical_scancode":0,"unicode":0,"echo":false,"script":null)
]
}
camera_left={
"deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777231,"physical_scancode":0,"unicode":0,"echo":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":2,"axis_value":-1.0,"script":null)
]
}
camera_right={
"deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777233,"physical_scancode":0,"unicode":0,"echo":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":2,"axis_value":1.0,"script":null)
]
}
camera_up={
"deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777232,"physical_scancode":0,"unicode":0,"echo":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":3,"axis_value":-1.0,"script":null)
]
}
camera_down={
"deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777234,"physical_scancode":0,"unicode":0,"echo":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":3,"axis_value":1.0,"script":null)
]
}
[locale] [locale]

View file

@ -8,6 +8,43 @@ enum TileMode { NONE, BOTH, X_AXIS, Y_AXIS }
enum IconColorFrom { THEME, CUSTOM } enum IconColorFrom { THEME, CUSTOM }
enum ButtonSize { SMALL, BIG } enum ButtonSize { SMALL, BIG }
enum FileMenu { NEW, OPEN, OPEN_LAST_PROJECT, RECENT, SAVE, SAVE_AS, EXPORT, EXPORT_AS, QUIT }
enum EditMenu { UNDO, REDO, COPY, CUT, PASTE, DELETE, NEW_BRUSH, PREFERENCES }
enum ViewMenu {
TILE_MODE,
GREYSCALE_VIEW,
MIRROR_VIEW,
SHOW_GRID,
SHOW_PIXEL_GRID,
SHOW_RULERS,
SHOW_GUIDES,
}
enum WindowMenu { WINDOW_OPACITY, PANELS, LAYOUTS, MOVABLE_PANELS, ZEN_MODE, FULLSCREEN_MODE }
enum ImageMenu {
SCALE_IMAGE,
CENTRALIZE_IMAGE,
CROP_IMAGE,
RESIZE_CANVAS,
FLIP,
ROTATE,
INVERT_COLORS,
DESATURATION,
OUTLINE,
DROP_SHADOW,
HSV,
GRADIENT,
SHADER
}
enum SelectMenu { SELECT_ALL, CLEAR_SELECTION, INVERT }
enum HelpMenu {
VIEW_SPLASH_SCREEN,
ONLINE_DOCS,
ISSUE_TRACKER,
OPEN_LOGS_FOLDER,
CHANGELOG,
ABOUT_PIXELORAMA
}
var root_directory := "." var root_directory := "."
var window_title := "" setget _title_changed # Why doesn't Godot have get_window_title()? var window_title := "" setget _title_changed # Why doesn't Godot have get_window_title()?
var config_cache := ConfigFile.new() var config_cache := ConfigFile.new()
@ -165,6 +202,8 @@ onready var current_version: String = ProjectSettings.get_setting("application/c
func _ready() -> void: func _ready() -> void:
_initialize_keychain()
if OS.has_feature("standalone"): if OS.has_feature("standalone"):
root_directory = OS.get_executable_path().get_base_dir() root_directory = OS.get_executable_path().get_base_dir()
# root_directory must be set earlier than this is because XDGDataDirs depends on it # root_directory must be set earlier than this is because XDGDataDirs depends on it
@ -189,6 +228,117 @@ func _ready() -> void:
ui_tooltips[node] = tooltip ui_tooltips[node] = tooltip
func _initialize_keychain() -> void:
Keychain.config_file = config_cache
Keychain.actions = {
"new_file": Keychain.MenuInputAction.new("", "File menu", true, "FileMenu", FileMenu.NEW),
"open_file": Keychain.MenuInputAction.new("", "File menu", true, "FileMenu", FileMenu.OPEN),
"save_file": Keychain.MenuInputAction.new("", "File menu", true, "FileMenu", FileMenu.SAVE),
"save_file_as":
Keychain.MenuInputAction.new("", "File menu", true, "FileMenu", FileMenu.SAVE_AS),
"export_file":
Keychain.MenuInputAction.new("", "File menu", true, "FileMenu", FileMenu.EXPORT),
"export_file_as":
Keychain.MenuInputAction.new("", "File menu", true, "FileMenu", FileMenu.EXPORT_AS),
"quit": Keychain.MenuInputAction.new("", "File menu", true, "FileMenu", FileMenu.QUIT),
"redo":
Keychain.MenuInputAction.new("", "Edit menu", true, "EditMenu", EditMenu.REDO, true),
"undo":
Keychain.MenuInputAction.new("", "Edit menu", true, "EditMenu", EditMenu.UNDO, true),
"cut": Keychain.MenuInputAction.new("", "Edit menu", true, "EditMenu", EditMenu.CUT),
"copy": Keychain.MenuInputAction.new("", "Edit menu", true, "EditMenu", EditMenu.COPY),
"paste": Keychain.MenuInputAction.new("", "Edit menu", true, "EditMenu", EditMenu.PASTE),
"delete": Keychain.MenuInputAction.new("", "Edit menu", true, "EditMenu", EditMenu.DELETE),
"new_brush":
Keychain.MenuInputAction.new("", "Edit menu", true, "EditMenu", EditMenu.NEW_BRUSH),
"mirror_view":
Keychain.MenuInputAction.new("", "View menu", true, "ViewMenu", ViewMenu.MIRROR_VIEW),
"show_grid":
Keychain.MenuInputAction.new("", "View menu", true, "ViewMenu", ViewMenu.SHOW_GRID),
"show_pixel_grid":
Keychain.MenuInputAction.new("", "View menu", true, "ViewMenu", ViewMenu.SHOW_PIXEL_GRID),
"show_guides":
Keychain.MenuInputAction.new("", "View menu", true, "ViewMenu", ViewMenu.SHOW_GUIDES),
"show_rulers":
Keychain.MenuInputAction.new("", "View menu", true, "ViewMenu", ViewMenu.SHOW_RULERS),
"moveable_panels":
Keychain.MenuInputAction.new(
"", "Window menu", true, "WindowMenu", WindowMenu.MOVABLE_PANELS
),
"zen_mode":
Keychain.MenuInputAction.new("", "Window menu", true, "WindowMenu", WindowMenu.ZEN_MODE),
"toggle_fullscreen":
Keychain.MenuInputAction.new(
"", "Window menu", true, "WindowMenu", WindowMenu.FULLSCREEN_MODE
),
"clear_selection":
Keychain.MenuInputAction.new(
"", "Select menu", true, "SelectMenu", SelectMenu.CLEAR_SELECTION
),
"select_all":
Keychain.MenuInputAction.new("", "Select menu", true, "SelectMenu", SelectMenu.SELECT_ALL),
"invert_selection":
Keychain.MenuInputAction.new("", "Select menu", true, "SelectMenu", SelectMenu.INVERT),
"open_docs":
Keychain.MenuInputAction.new("", "Help menu", true, "HelpMenu", HelpMenu.ONLINE_DOCS),
"zoom_in": Keychain.InputAction.new("", "General"),
"zoom_out": Keychain.InputAction.new("", "General"),
"camera_left": Keychain.InputAction.new("", "General"),
"camera_right": Keychain.InputAction.new("", "General"),
"camera_up": Keychain.InputAction.new("", "General"),
"camera_down": Keychain.InputAction.new("", "General"),
"pan": Keychain.InputAction.new("", "General"),
"confirm": Keychain.InputAction.new("", "General"),
"cancel": Keychain.InputAction.new("", "General"),
"switch_colors": Keychain.InputAction.new("", "Buttons"),
"go_to_first_frame": Keychain.InputAction.new("", "Buttons"),
"go_to_last_frame": Keychain.InputAction.new("", "Buttons"),
"go_to_previous_frame": Keychain.InputAction.new("", "Buttons"),
"go_to_next_frame": Keychain.InputAction.new("", "Buttons"),
"play_backwards": Keychain.InputAction.new("", "Buttons"),
"play_forward": Keychain.InputAction.new("", "Buttons"),
"change_tool_mode": Keychain.InputAction.new("", "Tool modifiers", false),
"draw_create_line": Keychain.InputAction.new("", "Draw tools", false),
"draw_snap_angle": Keychain.InputAction.new("", "Draw tools", false),
"draw_color_picker": Keychain.InputAction.new("", "Draw tools", false),
"shape_perfect": Keychain.InputAction.new("", "Shape tools", false),
"shape_center": Keychain.InputAction.new("", "Shape tools", false),
"shape_displace": Keychain.InputAction.new("", "Shape tools", false),
"selection_add": Keychain.InputAction.new("", "Selection tools", false),
"selection_subtract": Keychain.InputAction.new("", "Selection tools", false),
"selection_intersect": Keychain.InputAction.new("", "Selection tools", false),
"transform_snap_axis": Keychain.InputAction.new("", "Transformation tools", false),
"transform_snap_grid": Keychain.InputAction.new("", "Transformation tools", false),
"transform_move_selection_only":
Keychain.InputAction.new("", "Transformation tools", false),
"transform_copy_selection_content":
Keychain.InputAction.new("", "Transformation tools", false),
}
Keychain.groups = {
"General": Keychain.InputGroup.new("", false),
"Buttons": Keychain.InputGroup.new(),
"Tools": Keychain.InputGroup.new(),
"Left": Keychain.InputGroup.new("Tools"),
"Right": Keychain.InputGroup.new("Tools"),
"Menu": Keychain.InputGroup.new(),
"File menu": Keychain.InputGroup.new("Menu"),
"Edit menu": Keychain.InputGroup.new("Menu"),
"View menu": Keychain.InputGroup.new("Menu"),
"Select menu": Keychain.InputGroup.new("Menu"),
"Image menu": Keychain.InputGroup.new("Menu"),
"Window menu": Keychain.InputGroup.new("Menu"),
"Help menu": Keychain.InputGroup.new("Menu"),
"Tool modifiers": Keychain.InputGroup.new(),
"Draw tools": Keychain.InputGroup.new("Tool modifiers"),
"Shape tools": Keychain.InputGroup.new("Tool modifiers"),
"Selection tools": Keychain.InputGroup.new("Tool modifiers"),
"Transformation tools": Keychain.InputGroup.new("Tool modifiers"),
}
Keychain.ignore_actions = ["left_mouse", "right_mouse", "middle_mouse", "shift", "ctrl"]
Keychain.multiple_menu_accelerators = true
func notification_label(text: String) -> void: func notification_label(text: String) -> void:
var notification: Label = notification_label_node.instance() var notification: Label = notification_label_node.instance()
notification.text = tr(text) notification.text = tr(text)
@ -320,7 +470,15 @@ func change_button_texturerect(texture_button: TextureRect, new_file_name: Strin
func update_hint_tooltips() -> void: func update_hint_tooltips() -> void:
yield(get_tree(), "idle_frame")
Tools.update_hint_tooltips() Tools.update_hint_tooltips()
for tip in ui_tooltips: for tip in ui_tooltips:
tip.hint_tooltip = tr(ui_tooltips[tip]) % tip.shortcut.get_as_text() var hint := "None"
var event_type: InputEvent = tip.shortcut.shortcut
if event_type is InputEventKey:
hint = event_type.as_text()
elif event_type is InputEventAction:
var first_key: InputEventKey = Keychain.action_get_first_key(event_type.action)
hint = first_key.as_text() if first_key else "None"
tip.hint_tooltip = tr(ui_tooltips[tip]) % hint

View file

@ -6,9 +6,6 @@ var pen_pressure := 1.0
var horizontal_mirror := false var horizontal_mirror := false
var vertical_mirror := false var vertical_mirror := false
var pixel_perfect := false var pixel_perfect := false
var control := false
var shift := false
var alt := false
var tools := { var tools := {
"RectSelect": "RectSelect":
@ -95,7 +92,7 @@ var tools := {
"""Hold %s to snap the angle of the line """Hold %s to snap the angle of the line
Hold %s to center the shape on the click origin Hold %s to center the shape on the click origin
Hold %s to displace the shape's origin""", Hold %s to displace the shape's origin""",
["Shift", "configurable_ctrl", "configurable_alt"] ["Shift", "Ctrl", "Alt"]
), ),
"RectangleTool": "RectangleTool":
Tool.new( Tool.new(
@ -106,7 +103,7 @@ Hold %s to displace the shape's origin""",
"""Hold %s to create a 1:1 shape """Hold %s to create a 1:1 shape
Hold %s to center the shape on the click origin Hold %s to center the shape on the click origin
Hold %s to displace the shape's origin""", Hold %s to displace the shape's origin""",
["configurable_shift", "configurable_ctrl", "configurable_alt"] ["Shift", "Ctrl", "Alt"]
), ),
"EllipseTool": "EllipseTool":
Tool.new( Tool.new(
@ -117,7 +114,7 @@ Hold %s to displace the shape's origin""",
"""Hold %s to create a 1:1 shape """Hold %s to create a 1:1 shape
Hold %s to center the shape on the click origin Hold %s to center the shape on the click origin
Hold %s to displace the shape's origin""", Hold %s to displace the shape's origin""",
["configurable_shift", "configurable_ctrl", "configurable_alt"] ["Shift", "Ctrl", "Alt"]
), ),
} }
@ -138,7 +135,6 @@ class Tool:
var shortcut := "" var shortcut := ""
var extra_hint := "" var extra_hint := ""
var extra_shortcuts := [] # Array of String(s) var extra_shortcuts := [] # Array of String(s)
var extra_shortcuts_order := [] # Array to keep shift, ctrl, alt in order
var button_node: BaseButton var button_node: BaseButton
func _init( func _init(
@ -155,7 +151,6 @@ class Tool:
scene = _scene scene = _scene
extra_hint = _extra_hint extra_hint = _extra_hint
extra_shortcuts = _extra_shortucts extra_shortcuts = _extra_shortucts
extra_shortcuts_order = _extra_shortucts.duplicate()
icon = load("res://assets/graphics/tools/%s.png" % name.to_lower()) icon = load("res://assets/graphics/tools/%s.png" % name.to_lower())
cursor_icon = load("res://assets/graphics/tools/cursors/%s.png" % name.to_lower()) cursor_icon = load("res://assets/graphics/tools/cursors/%s.png" % name.to_lower())
@ -165,13 +160,17 @@ class Tool:
var left_text := "" var left_text := ""
var right_text := "" var right_text := ""
if InputMap.has_action("left_" + shortcut + "_tool"): if InputMap.has_action("left_" + shortcut + "_tool"):
var left_shortcut: String = InputMap.get_action_list("left_" + shortcut + "_tool")[0].as_text() var left_list := InputMap.get_action_list("left_" + shortcut + "_tool")
shortcuts.append(left_shortcut) if left_list.size() > 0:
left_text = "\n%s for left mouse button" var left_shortcut: String = left_list[0].as_text()
shortcuts.append(left_shortcut)
left_text = "\n%s for left mouse button"
if InputMap.has_action("right_" + shortcut + "_tool"): if InputMap.has_action("right_" + shortcut + "_tool"):
var right_shortcut: String = InputMap.get_action_list("right_" + shortcut + "_tool")[0].as_text() var right_list := InputMap.get_action_list("right_" + shortcut + "_tool")
shortcuts.append(right_shortcut) if right_list.size() > 0:
right_text = "\n%s for right mouse button" var right_shortcut: String = right_list[0].as_text()
shortcuts.append(right_shortcut)
right_text = "\n%s for right mouse button"
if !shortcuts.empty(): if !shortcuts.empty():
hint += "\n" + left_text + right_text hint += "\n" + left_text + right_text
@ -179,22 +178,6 @@ class Tool:
if !extra_hint.empty(): if !extra_hint.empty():
hint += "\n\n" + extra_hint hint += "\n\n" + extra_hint
# Some tools have shift,ctrl,alt "HARD CODED" in them using (InputEventWithModifiers)
# But Others only use the regular is_action_pressed() function
# their Shift, Ctrl, Alt are listed Below
var code_shift = InputMap.get_action_list("shift")[0].get_scancode_with_modifiers()
var code_ctrl = InputMap.get_action_list("ctrl")[0].get_scancode_with_modifiers()
var code_alt = InputMap.get_action_list("alt")[0].get_scancode_with_modifiers()
var configurable_shift: String = OS.get_scancode_string(code_shift)
var configurable_ctrl: String = OS.get_scancode_string(code_ctrl)
var configurable_alt: String = OS.get_scancode_string(code_alt)
for shortcut_idx in extra_shortcuts.size():
if extra_shortcuts_order[shortcut_idx] == "configurable_shift":
extra_shortcuts[shortcut_idx] = configurable_shift
if extra_shortcuts_order[shortcut_idx] == "configurable_ctrl":
extra_shortcuts[shortcut_idx] = configurable_ctrl
if extra_shortcuts_order[shortcut_idx] == "configurable_alt":
extra_shortcuts[shortcut_idx] = configurable_alt
shortcuts.append_array(extra_shortcuts) shortcuts.append_array(extra_shortcuts)
if shortcuts.empty(): if shortcuts.empty():
@ -220,6 +203,12 @@ func _ready() -> void:
_tool_buttons = Global.control.find_node("ToolButtons") _tool_buttons = Global.control.find_node("ToolButtons")
for t in tools: for t in tools:
add_tool_button(tools[t]) add_tool_button(tools[t])
var tool_shortcut: String = Tools.tools[t].shortcut
var left_tool_shortcut := "left_%s_tool" % tool_shortcut
var right_tool_shortcut := "right_%s_tool" % tool_shortcut
Keychain.actions[left_tool_shortcut] = Keychain.InputAction.new("", "Left")
Keychain.actions[right_tool_shortcut] = Keychain.InputAction.new("", "Right")
_slots[BUTTON_LEFT] = Slot.new("Left tool") _slots[BUTTON_LEFT] = Slot.new("Left tool")
_slots[BUTTON_RIGHT] = Slot.new("Right tool") _slots[BUTTON_RIGHT] = Slot.new("Right tool")
_panels[BUTTON_LEFT] = Global.control.find_node("LeftPanelContainer", true, false) _panels[BUTTON_LEFT] = Global.control.find_node("LeftPanelContainer", true, false)
@ -383,11 +372,6 @@ func handle_draw(position: Vector2, event: InputEvent) -> void:
if Global.mirror_view: if Global.mirror_view:
draw_pos.x = Global.current_project.size.x - position.x - 1 draw_pos.x = Global.current_project.size.x - position.x - 1
if event is InputEventWithModifiers:
control = event.control
shift = event.shift
alt = event.alt
if event is InputEventMouseButton: if event is InputEventMouseButton:
if event.button_index in [BUTTON_LEFT, BUTTON_RIGHT]: if event.button_index in [BUTTON_LEFT, BUTTON_RIGHT]:
if event.pressed and _active_button == -1: if event.pressed and _active_button == -1:

View file

@ -84,26 +84,6 @@ func _input(event: InputEvent) -> void:
if get_focus_owner() is LineEdit: if get_focus_owner() is LineEdit:
get_focus_owner().release_focus() get_focus_owner().release_focus()
# The section of code below is reserved for Undo and Redo!
# Do not place code for Input below, but above.
if !event.is_echo(): # Checks if the action is pressed down
if event.is_action_pressed("redo_secondary"):
# Done, so that "redo_secondary" hasn't a slight delay before it starts.
# The "redo" and "undo" action don't have a slight delay,
# because they get called as an accelerator once pressed (TopMenuContainer.gd, Line 152)
Global.current_project.commit_redo()
return
if event.is_action("redo"): # Ctrl + Y
Global.current_project.commit_redo()
if event.is_action("redo_secondary"): # Shift + Ctrl + Z
Global.current_project.commit_redo()
if event.is_action("undo") and !event.shift: # Ctrl + Z and check if shift isn't pressed
# so "undo" isn't accidentaly triggered while using "redo_secondary"
Global.current_project.commit_undo()
func _setup_application_window_size() -> void: func _setup_application_window_size() -> void:
get_tree().set_screen_stretch( get_tree().set_screen_stretch(

View file

@ -26,7 +26,6 @@ theme = ExtResource( 1 )
script = ExtResource( 2 ) script = ExtResource( 2 )
__meta__ = { __meta__ = {
"_edit_horizontal_guides_": [ ], "_edit_horizontal_guides_": [ ],
"_edit_use_anchors_": false,
"_edit_vertical_guides_": [ ] "_edit_vertical_guides_": [ ]
} }

View file

@ -1,240 +0,0 @@
extends Node
var default_shortcuts_preset := {}
var custom_shortcuts_preset := {}
var action_being_edited := ""
var shortcut_already_assigned = false
var old_input_event: InputEventKey
var new_input_event: InputEventKey
onready var shortcut_selector_popup = Global.preferences_dialog.get_node("Popups/ShortcutSelector")
onready var theme_font_color: Color = shortcut_selector_popup.get_node("EnteredShortcut").get_color(
"font_color"
)
func _ready() -> void:
# Disable input until the shortcut selector is displayed
set_process_input(false)
# Get default preset for shortcuts from project input map
# Buttons in shortcuts selector should be called the same as actions
for shortcut_grid_item in get_node("Shortcuts").get_children():
if shortcut_grid_item is Button:
var input_events = InputMap.get_action_list(shortcut_grid_item.name)
if input_events.size() > 1:
printerr(
"Every shortcut action should have just one input event assigned in input map"
)
shortcut_grid_item.text = (input_events[0] as InputEventKey).as_text()
shortcut_grid_item.connect(
"pressed", self, "_on_Shortcut_button_pressed", [shortcut_grid_item]
)
default_shortcuts_preset[shortcut_grid_item.name] = input_events[0]
# Load custom shortcuts from the config file
custom_shortcuts_preset = default_shortcuts_preset.duplicate()
for action in default_shortcuts_preset:
var saved_input_event = Global.config_cache.get_value("shortcuts", action, 0)
if saved_input_event is InputEventKey:
custom_shortcuts_preset[action] = saved_input_event
var shortcuts_preset = Global.config_cache.get_value("shortcuts", "shortcuts_preset", 0)
get_node("HBoxContainer/PresetOptionButton").select(shortcuts_preset)
_on_PresetOptionButton_item_selected(shortcuts_preset)
func _input(event: InputEvent) -> void:
if event is InputEventKey:
if event.pressed:
if event.scancode == KEY_ESCAPE:
shortcut_selector_popup.hide()
else:
# Check if shortcut was already used
for action in InputMap.get_actions():
for input_event in InputMap.get_action_list(action):
if input_event is InputEventKey:
if (
OS.get_scancode_string(input_event.get_scancode_with_modifiers())
== OS.get_scancode_string(event.get_scancode_with_modifiers())
):
shortcut_selector_popup.get_node("EnteredShortcut").text = tr(
"Already assigned"
)
shortcut_selector_popup.get_node("EnteredShortcut").add_color_override(
"font_color", Color.crimson
)
get_tree().set_input_as_handled()
shortcut_already_assigned = true
return
# Store new shortcut
shortcut_already_assigned = false
old_input_event = InputMap.get_action_list(action_being_edited)[0]
new_input_event = event
shortcut_selector_popup.get_node("EnteredShortcut").text = OS.get_scancode_string(
event.get_scancode_with_modifiers()
)
shortcut_selector_popup.get_node("EnteredShortcut").add_color_override(
"font_color", theme_font_color
)
get_tree().set_input_as_handled()
func _on_PresetOptionButton_item_selected(id: int) -> void:
# Only custom preset which is modifiable
toggle_shortcut_buttons(true if id == 1 else false)
match id:
0:
apply_shortcuts_preset(default_shortcuts_preset)
1:
apply_shortcuts_preset(custom_shortcuts_preset)
Global.config_cache.set_value("shortcuts", "shortcuts_preset", id)
Global.config_cache.save("user://cache.ini")
func apply_shortcuts_preset(preset) -> void:
for action in preset:
var preset_old_input_event: InputEventKey = InputMap.get_action_list(action)[0]
set_action_shortcut(action, preset_old_input_event, preset[action])
get_node("Shortcuts/" + action).text = OS.get_scancode_string(
preset[action].get_scancode_with_modifiers()
)
Global.update_hint_tooltips()
func toggle_shortcut_buttons(enabled: bool) -> void:
for shortcut_grid_item in get_node("Shortcuts").get_children():
if shortcut_grid_item is Button:
shortcut_grid_item.disabled = not enabled
if shortcut_grid_item.disabled:
shortcut_grid_item.mouse_default_cursor_shape = Control.CURSOR_FORBIDDEN
else:
shortcut_grid_item.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
func set_action_shortcut(action: String, oldinput: InputEventKey, newinput: InputEventKey) -> void:
# Only updates the InputMap
InputMap.action_erase_event(action, oldinput)
InputMap.action_add_event(action, newinput)
update_ui_shortcuts(action)
func update_ui_shortcuts(action: String):
# Updates UI elements according to InputMap (Tool shortcuts are updated from a seperate function)
# Set shortcut to switch colors button
if action == "switch_colors":
update_ui_shortcut("ColorSwitch", action)
# Set timeline shortcuts
if action == "go_to_first_frame":
update_ui_shortcut("FirstFrame", action)
if action == "go_to_previous_frame":
update_ui_shortcut("PreviousFrame", action)
if action == "play_backwards":
update_ui_shortcut("PlayBackwards", action)
if action == "play_forward":
update_ui_shortcut("PlayForward", action)
if action == "go_to_next_frame":
update_ui_shortcut("NextFrame", action)
if action == "go_to_last_frame":
update_ui_shortcut("LastFrame", action)
# Set shortcuts for Menu Options
var top_menu: Panel = Global.control.find_node("TopMenuContainer")
var file_menu: PopupMenu = top_menu.file_menu_button.get_popup()
var edit_menu: PopupMenu = top_menu.edit_menu_button.get_popup()
var select_menu: PopupMenu = top_menu.select_menu_button.get_popup()
var view_menu: PopupMenu = top_menu.view_menu_button.get_popup()
var window_menu: PopupMenu = top_menu.window_menu_button.get_popup()
var help_menu: PopupMenu = top_menu.help_menu_button.get_popup()
if action == "new_file":
update_menu_option(file_menu, "New...", action)
if action == "open_file":
update_menu_option(file_menu, "Open...", action)
if action == "save_file":
update_menu_option(file_menu, "Save...", action)
if action == "save_file_as":
update_menu_option(file_menu, "Save as...", action)
if action == "export_file":
update_menu_option(file_menu, "Export...", action)
if action == "export_file_as":
update_menu_option(file_menu, "Export as...", action)
if action == "quit":
update_menu_option(file_menu, "Quit", action)
if action == "undo":
update_menu_option(edit_menu, "Undo", action)
if action == "redo":
update_menu_option(edit_menu, "Redo", action)
if action == "copy":
update_menu_option(edit_menu, "Copy", action)
if action == "cut":
update_menu_option(edit_menu, "Cut", action)
if action == "paste":
update_menu_option(edit_menu, "Paste", action)
if action == "delete":
update_menu_option(edit_menu, "Delete", action)
if action == "new_brush":
update_menu_option(edit_menu, "New Brush", action)
if action == "select_all":
update_menu_option(select_menu, "All", action)
if action == "clear_selection":
update_menu_option(select_menu, "Clear", action)
if action == "invert_selection":
update_menu_option(select_menu, "Invert", action)
if action == "mirror_view":
update_menu_option(view_menu, "Mirror View", action)
if action == "show_grid":
update_menu_option(view_menu, "Show Grid", action)
if action == "show_pixel_grid":
update_menu_option(view_menu, "Show Pixel Grid", action)
if action == "show_rulers":
update_menu_option(view_menu, "Show Rulers", action)
if action == "show_guides":
update_menu_option(view_menu, "Show Guides", action)
if action == "edit_mode":
for child in window_menu.get_children():
if child.name == "panels_submenu":
update_menu_option(child, "Moveable Panels", action)
if action == "zen_mode":
update_menu_option(window_menu, "Zen Mode", action)
if action == "toggle_fullscreen":
update_menu_option(window_menu, "Fullscreen Mode", action)
if action == "open_docs":
update_menu_option(help_menu, "Online Docs", action)
func update_ui_shortcut(name: String, action: String):
var ui_button: BaseButton = Global.control.find_node(name)
ui_button.shortcut.shortcut = InputMap.get_action_list(action)[0]
func update_menu_option(menu: PopupMenu, name: String, action: String):
for idx in menu.get_item_count():
if menu.get_item_text(idx) == name:
var accel: int = InputMap.get_action_list(action)[0].get_scancode_with_modifiers()
menu.set_item_accelerator(idx, accel)
func _on_Shortcut_button_pressed(button: Button) -> void:
set_process_input(true)
action_being_edited = button.name
new_input_event = InputMap.get_action_list(button.name)[0]
shortcut_already_assigned = true
shortcut_selector_popup.popup_centered()
func _on_ShortcutSelector_popup_hide() -> void:
set_process_input(false)
shortcut_selector_popup.get_node("EnteredShortcut").text = ""
func _on_ShortcutSelector_confirmed() -> void:
if not shortcut_already_assigned:
set_action_shortcut(action_being_edited, old_input_event, new_input_event)
Global.update_hint_tooltips()
custom_shortcuts_preset[action_being_edited] = new_input_event
Global.config_cache.set_value("shortcuts", action_being_edited, new_input_event)
Global.config_cache.save("user://cache.ini")
get_node("Shortcuts/" + action_being_edited).text = OS.get_scancode_string(
new_input_event.get_scancode_with_modifiers()
)
shortcut_selector_popup.hide()

View file

@ -87,6 +87,7 @@ onready var autosave_container: Container = right_side.get_node("Backup/Autosave
onready var autosave_interval: SpinBox = autosave_container.get_node("AutosaveInterval") onready var autosave_interval: SpinBox = autosave_container.get_node("AutosaveInterval")
onready var shrink_label: Label = right_side.get_node("Interface/ShrinkContainer/ShrinkLabel") onready var shrink_label: Label = right_side.get_node("Interface/ShrinkContainer/ShrinkLabel")
onready var themes: BoxContainer = right_side.get_node("Interface/Themes") onready var themes: BoxContainer = right_side.get_node("Interface/Themes")
onready var shortcuts: Control = right_side.get_node("Shortcuts")
onready var extensions: BoxContainer = right_side.get_node("Extensions") onready var extensions: BoxContainer = right_side.get_node("Extensions")
@ -106,6 +107,12 @@ class Preference:
func _ready() -> void: func _ready() -> void:
# Replace OK since preference changes are being applied immediately, not after OK confirmation # Replace OK since preference changes are being applied immediately, not after OK confirmation
get_ok().text = tr("Close") get_ok().text = tr("Close")
for child in shortcuts.get_children():
if not child is AcceptDialog:
continue
child.connect("confirmed", Global, "update_hint_tooltips")
for child in right_side.get_children(): for child in right_side.get_children():
content_list.append(child.name) content_list.append(child.name)

File diff suppressed because it is too large Load diff

View file

@ -22,13 +22,13 @@ func _ready() -> void:
func _input(event: InputEvent) -> void: func _input(event: InputEvent) -> void:
var options: OptionButton = $FillAreaOptions var options: OptionButton = $FillAreaOptions
if event.is_action_pressed("ctrl"): if event.is_action_pressed("change_tool_mode"):
_prev_mode = options.selected _prev_mode = options.selected
if event.is_action("ctrl"): if event.is_action("change_tool_mode"):
options.selected = _prev_mode ^ 1 options.selected = _prev_mode ^ 1
_fill_area = options.selected _fill_area = options.selected
$Similarity.visible = (_fill_area == 1) $Similarity.visible = (_fill_area == 1)
if event.is_action_released("ctrl"): if event.is_action_released("change_tool_mode"):
options.selected = _prev_mode options.selected = _prev_mode
_fill_area = options.selected _fill_area = options.selected
$Similarity.visible = (_fill_area == 1) $Similarity.visible = (_fill_area == 1)
@ -138,7 +138,7 @@ func update_pattern() -> void:
func draw_start(position: Vector2) -> void: func draw_start(position: Vector2) -> void:
.draw_start(position) .draw_start(position)
if Input.is_action_pressed("alt"): if Input.is_action_pressed("draw_color_picker"):
_pick_color(position) _pick_color(position)
return return

View file

@ -7,12 +7,12 @@ var _color_slot := 0
func _input(event: InputEvent) -> void: func _input(event: InputEvent) -> void:
var options: OptionButton = $ColorPicker/Options var options: OptionButton = $ColorPicker/Options
if event.is_action_pressed("ctrl"): if event.is_action_pressed("change_tool_mode"):
_prev_mode = options.selected _prev_mode = options.selected
if event.is_action("ctrl"): if event.is_action("change_tool_mode"):
options.selected = _prev_mode ^ 1 options.selected = _prev_mode ^ 1
_color_slot = options.selected _color_slot = options.selected
if event.is_action_released("ctrl"): if event.is_action_released("change_tool_mode"):
options.selected = _prev_mode options.selected = _prev_mode
_color_slot = options.selected _color_slot = options.selected

View file

@ -523,7 +523,7 @@ func _line_angle_constraint(start: Vector2, end: Vector2) -> Dictionary:
var result := {} var result := {}
var angle := rad2deg(end.angle_to_point(start)) var angle := rad2deg(end.angle_to_point(start))
var distance := start.distance_to(end) var distance := start.distance_to(end)
if Tools.control: if Input.is_action_pressed("draw_snap_angle"):
if Tools.pixel_perfect: if Tools.pixel_perfect:
angle = stepify(angle, 22.5) angle = stepify(angle, 22.5)
if step_decimals(angle) != 0: if step_decimals(angle) != 0:

View file

@ -36,7 +36,7 @@ func set_config(config: Dictionary) -> void:
func draw_start(position: Vector2) -> void: func draw_start(position: Vector2) -> void:
.draw_start(position) .draw_start(position)
if Input.is_action_pressed("alt"): if Input.is_action_pressed("draw_color_picker"):
_picking_color = true _picking_color = true
_pick_color(position) _pick_color(position)
return return
@ -50,7 +50,7 @@ func draw_start(position: Vector2) -> void:
prepare_undo("Draw") prepare_undo("Draw")
_drawer.reset() _drawer.reset()
_draw_line = Tools.shift _draw_line = Input.is_action_pressed("draw_create_line")
if _draw_line: if _draw_line:
_line_start = position _line_start = position
_line_end = position _line_end = position
@ -65,7 +65,7 @@ func draw_start(position: Vector2) -> void:
func draw_move(position: Vector2) -> void: func draw_move(position: Vector2) -> void:
.draw_move(position) .draw_move(position)
if _picking_color: # Still return even if we released Alt if _picking_color: # Still return even if we released Alt
if Input.is_action_pressed("alt"): if Input.is_action_pressed("draw_color_picker"):
_pick_color(position) _pick_color(position)
return return

View file

@ -61,15 +61,15 @@ func _get_shape_points_filled(_size: Vector2) -> PoolVector2Array:
func _input(event: InputEvent) -> void: func _input(event: InputEvent) -> void:
if _drawing: if _drawing:
if event.is_action_pressed("alt"): if event.is_action_pressed("shape_displace"):
_displace_origin = true _displace_origin = true
elif event.is_action_released("alt"): elif event.is_action_released("shape_displace"):
_displace_origin = false _displace_origin = false
func draw_start(position: Vector2) -> void: func draw_start(position: Vector2) -> void:
.draw_start(position) .draw_start(position)
if Input.is_action_pressed("alt"): if Input.is_action_pressed("shape_displace"):
_picking_color = true _picking_color = true
_pick_color(position) _pick_color(position)
return return
@ -88,7 +88,7 @@ func draw_start(position: Vector2) -> void:
func draw_move(position: Vector2) -> void: func draw_move(position: Vector2) -> void:
.draw_move(position) .draw_move(position)
if _picking_color: # Still return even if we released Alt if _picking_color: # Still return even if we released Alt
if Input.is_action_pressed("alt"): if Input.is_action_pressed("shape_displace"):
_pick_color(position) _pick_color(position)
return return
@ -97,8 +97,8 @@ func draw_move(position: Vector2) -> void:
_original_pos += position - _offset _original_pos += position - _offset
var d = _line_angle_constraint(_original_pos, position) var d = _line_angle_constraint(_original_pos, position)
_dest = d.position _dest = d.position
# Didn't use (Tools.control) to make more consistent with shape tools
if Input.is_action_pressed("ctrl"): if Input.is_action_pressed("shape_center"):
_start = _original_pos - (_dest - _original_pos) _start = _original_pos - (_dest - _original_pos)
else: else:
_start = _original_pos _start = _original_pos
@ -202,7 +202,7 @@ func _line_angle_constraint(start: Vector2, end: Vector2) -> Dictionary:
var result := {} var result := {}
var angle := rad2deg(end.angle_to_point(start)) var angle := rad2deg(end.angle_to_point(start))
var distance := start.distance_to(end) var distance := start.distance_to(end)
if Tools.shift: if Input.is_action_pressed("shape_perfect"):
angle = stepify(angle, 22.5) angle = stepify(angle, 22.5)
if step_decimals(angle) != 0: if step_decimals(angle) != 0:
var diff := end - start var diff := end - start

View file

@ -14,7 +14,7 @@ onready var selection_node: Node2D = Global.canvas.selection
func _input(event: InputEvent) -> void: func _input(event: InputEvent) -> void:
if _start_pos != Vector2.INF: if _start_pos != Vector2.INF:
if event.is_action_pressed("ctrl"): if event.is_action_pressed("transform_snap_grid"):
_snap_to_grid = true _snap_to_grid = true
var grid_size := Vector2(Global.grid_width, Global.grid_height) var grid_size := Vector2(Global.grid_width, Global.grid_height)
_offset = _offset.snapped(grid_size) _offset = _offset.snapped(grid_size)
@ -25,7 +25,7 @@ func _input(event: InputEvent) -> void:
selection_node.big_bounding_rectangle.position selection_node.big_bounding_rectangle.position
- prev_pos - prev_pos
) )
elif event.is_action_released("ctrl"): elif event.is_action_released("transform_snap_grid"):
_snap_to_grid = false _snap_to_grid = false
@ -49,7 +49,7 @@ func draw_move(position: Vector2) -> void:
# while the content is being moved # while the content is being moved
if _content_transformation_check != selection_node.is_moving_content: if _content_transformation_check != selection_node.is_moving_content:
return return
if Tools.shift: # Snap to axis if Input.is_action_pressed("transform_snap_axis"):
var angle := position.angle_to_point(_start_pos) var angle := position.angle_to_point(_start_pos)
if abs(angle) <= PI / 4 or abs(angle) >= 3 * PI / 4: if abs(angle) <= PI / 4 or abs(angle) >= 3 * PI / 4:
position.y = _start_pos.y position.y = _start_pos.y
@ -74,7 +74,7 @@ func draw_end(position: Vector2) -> void:
_start_pos != Vector2.INF _start_pos != Vector2.INF
and _content_transformation_check == selection_node.is_moving_content and _content_transformation_check == selection_node.is_moving_content
): ):
if Tools.shift: # Snap to axis if Input.is_action_pressed("transform_snap_axis"):
var angle := position.angle_to_point(_start_pos) var angle := position.angle_to_point(_start_pos)
if abs(angle) <= PI / 4 or abs(angle) >= 3 * PI / 4: if abs(angle) <= PI / 4 or abs(angle) >= 3 * PI / 4:
position.y = _start_pos.y position.y = _start_pos.y

View file

@ -40,12 +40,12 @@ func _on_FillInside_toggled(button_pressed):
func _input(event: InputEvent) -> void: func _input(event: InputEvent) -> void:
var overwrite_button: CheckBox = $Overwrite var overwrite_button: CheckBox = $Overwrite
if event.is_action_pressed("ctrl"): if event.is_action_pressed("change_tool_mode"):
_prev_mode = overwrite_button.pressed _prev_mode = overwrite_button.pressed
if event.is_action("ctrl"): if event.is_action("change_tool_mode"):
overwrite_button.pressed = !_prev_mode overwrite_button.pressed = !_prev_mode
_overwrite = overwrite_button.pressed _overwrite = overwrite_button.pressed
if event.is_action_released("ctrl"): if event.is_action_released("change_tool_mode"):
overwrite_button.pressed = _prev_mode overwrite_button.pressed = _prev_mode
_overwrite = overwrite_button.pressed _overwrite = overwrite_button.pressed
@ -71,7 +71,7 @@ func update_config() -> void:
func draw_start(position: Vector2) -> void: func draw_start(position: Vector2) -> void:
.draw_start(position) .draw_start(position)
if Input.is_action_pressed("alt"): if Input.is_action_pressed("draw_color_picker"):
_picking_color = true _picking_color = true
_pick_color(position) _pick_color(position)
return return
@ -90,7 +90,7 @@ func draw_start(position: Vector2) -> void:
prepare_undo("Draw") prepare_undo("Draw")
_drawer.reset() _drawer.reset()
_draw_line = Tools.shift _draw_line = Input.is_action_pressed("draw_create_line")
if _draw_line: if _draw_line:
_line_start = position _line_start = position
_line_end = position _line_end = position
@ -107,7 +107,7 @@ func draw_start(position: Vector2) -> void:
func draw_move(position: Vector2) -> void: func draw_move(position: Vector2) -> void:
.draw_move(position) .draw_move(position)
if _picking_color: # Still return even if we released Alt if _picking_color: # Still return even if we released Alt
if Input.is_action_pressed("alt"): if Input.is_action_pressed("draw_color_picker"):
_pick_color(position) _pick_color(position)
return return

View file

@ -10,17 +10,17 @@ var _displace_origin = false # Mouse Click + Alt
func _input(event: InputEvent) -> void: func _input(event: InputEvent) -> void:
._input(event) ._input(event)
if !_move and !_rect.has_no_area(): if !_move and !_rect.has_no_area():
if event.is_action_pressed("shift"): if event.is_action_pressed("shape_perfect"):
_square = true _square = true
elif event.is_action_released("shift"): elif event.is_action_released("shape_perfect"):
_square = false _square = false
if event.is_action_pressed("ctrl"): if event.is_action_pressed("shape_center"):
_expand_from_center = true _expand_from_center = true
elif event.is_action_released("ctrl"): elif event.is_action_released("shape_center"):
_expand_from_center = false _expand_from_center = false
if event.is_action_pressed("alt"): if event.is_action_pressed("shape_displace"):
_displace_origin = true _displace_origin = true
elif event.is_action_released("alt"): elif event.is_action_released("shape_displace"):
_displace_origin = false _displace_origin = false

View file

@ -17,8 +17,8 @@ func _input(event: InputEvent) -> void:
append_gap(_draw_points[-1], _draw_points[0], _draw_points) append_gap(_draw_points[-1], _draw_points[0], _draw_points)
_ready_to_apply = true _ready_to_apply = true
apply_selection(Vector2.ZERO) # Argument doesn't matter apply_selection(Vector2.ZERO) # Argument doesn't matter
elif event is InputEventKey: else:
if event.is_action_pressed("escape") and _ongoing_selection: if event.is_action_pressed("cancel") and _ongoing_selection:
_ongoing_selection = false _ongoing_selection = false
_draw_points.clear() _draw_points.clear()
_ready_to_apply = false _ready_to_apply = false

View file

@ -10,17 +10,17 @@ var _displace_origin = false # Mouse Click + Alt
func _input(event: InputEvent) -> void: func _input(event: InputEvent) -> void:
._input(event) ._input(event)
if !_move and !_rect.has_no_area(): if !_move and !_rect.has_no_area():
if event.is_action_pressed("shift"): if event.is_action_pressed("shape_perfect"):
_square = true _square = true
elif event.is_action_released("shift"): elif event.is_action_released("shape_perfect"):
_square = false _square = false
if event.is_action_pressed("ctrl"): if event.is_action_pressed("shape_center"):
_expand_from_center = true _expand_from_center = true
elif event.is_action_released("ctrl"): elif event.is_action_released("shape_center"):
_expand_from_center = false _expand_from_center = false
if event.is_action_pressed("alt"): if event.is_action_pressed("shape_displace"):
_displace_origin = true _displace_origin = true
elif event.is_action_released("alt"): elif event.is_action_released("shape_displace"):
_displace_origin = false _displace_origin = false

View file

@ -33,7 +33,7 @@ func _ready() -> void:
func _input(event: InputEvent) -> void: func _input(event: InputEvent) -> void:
if _move: if _move:
if event.is_action_pressed("ctrl"): if event.is_action_pressed("transform_snap_grid"):
_snap_to_grid = true _snap_to_grid = true
var grid_size := Vector2(Global.grid_width, Global.grid_height) var grid_size := Vector2(Global.grid_width, Global.grid_height)
_offset = _offset.snapped(grid_size) _offset = _offset.snapped(grid_size)
@ -43,7 +43,7 @@ func _input(event: InputEvent) -> void:
selection_node.big_bounding_rectangle.position selection_node.big_bounding_rectangle.position
- prev_pos - prev_pos
) )
elif event.is_action_released("ctrl"): elif event.is_action_released("transform_snap_grid"):
_snap_to_grid = false _snap_to_grid = false
@ -66,9 +66,9 @@ func draw_start(position: Vector2) -> void:
return return
var project: Project = Global.current_project var project: Project = Global.current_project
undo_data = selection_node.get_undo_data(false) undo_data = selection_node.get_undo_data(false)
_intersect = Tools.shift && Tools.control _intersect = Input.is_action_pressed("selection_intersect", true)
_add = Tools.shift && !_intersect _add = Input.is_action_pressed("selection_add", true)
_subtract = Tools.control && !_intersect _subtract = Input.is_action_pressed("selection_subtract", true)
_start_pos = position _start_pos = position
_offset = position _offset = position
@ -78,59 +78,57 @@ func draw_start(position: Vector2) -> void:
offsetted_pos.x -= selection_position.x offsetted_pos.x -= selection_position.x
if selection_position.y < 0: if selection_position.y < 0:
offsetted_pos.y -= selection_position.y offsetted_pos.y -= selection_position.y
var quick_copy: bool = Input.is_action_pressed("transform_copy_selection_content", true)
if ( if (
offsetted_pos.x >= 0 offsetted_pos.x >= 0
and offsetted_pos.y >= 0 and offsetted_pos.y >= 0
and project.selection_bitmap.get_bit(offsetted_pos) and project.selection_bitmap.get_bit(offsetted_pos)
and (!Tools.control or Tools.alt) and (!_add and !_subtract and !_intersect or quick_copy)
and !Tools.shift
and !_ongoing_selection and !_ongoing_selection
): ):
if !Global.current_project.layers[Global.current_project.current_layer].can_layer_get_drawn(): if !Global.current_project.layers[Global.current_project.current_layer].can_layer_get_drawn():
return return
# Move current selection # Move current selection
_move = true _move = true
if Tools.alt: # Move selection without content if quick_copy: # Move selection without cutting it from the original position (quick copy)
if Tools.control: _move_content = true
# Move the selection without cutting it from the original position if selection_node.is_moving_content:
# (makes a quick copy of it) for image in _get_selected_draw_images():
_move_content = true image.blit_rect_mask(
if selection_node.is_moving_content: selection_node.preview_image,
for image in _get_selected_draw_images(): selection_node.preview_image,
image.blit_rect_mask( Rect2(Vector2.ZERO, project.selection_bitmap.get_size()),
selection_node.preview_image, selection_node.big_bounding_rectangle.position
selection_node.preview_image, )
Rect2(Vector2.ZERO, project.selection_bitmap.get_size()),
selection_node.big_bounding_rectangle.position
)
var selected_bitmap_copy = project.selection_bitmap.duplicate() var selected_bitmap_copy = project.selection_bitmap.duplicate()
project.move_bitmap_values(selected_bitmap_copy) project.move_bitmap_values(selected_bitmap_copy)
project.selection_bitmap = selected_bitmap_copy
selection_node.commit_undo("Move Selection", selection_node.undo_data)
selection_node.undo_data = selection_node.get_undo_data(true)
else:
selection_node.transform_content_start()
selection_node.clear_in_selected_cels = false
for image in _get_selected_draw_images():
image.blit_rect_mask(
selection_node.preview_image,
selection_node.preview_image,
Rect2(Vector2.ZERO, project.selection_bitmap.get_size()),
selection_node.big_bounding_rectangle.position
)
Global.canvas.update_selected_cels_textures()
project.selection_bitmap = selected_bitmap_copy
selection_node.commit_undo("Move Selection", selection_node.undo_data)
selection_node.undo_data = selection_node.get_undo_data(true)
else: else:
selection_node.transform_content_confirm() selection_node.transform_content_start()
_move_content = false selection_node.clear_in_selected_cels = false
selection_node.move_borders_start() for image in _get_selected_draw_images():
else: image.blit_rect_mask(
selection_node.preview_image,
selection_node.preview_image,
Rect2(Vector2.ZERO, project.selection_bitmap.get_size()),
selection_node.big_bounding_rectangle.position
)
Global.canvas.update_selected_cels_textures()
elif Input.is_action_pressed("transform_move_selection_only", true): # Doesn't move content
selection_node.transform_content_confirm()
_move_content = false
selection_node.move_borders_start()
else: # Move selection and content normally
_move_content = true _move_content = true
selection_node.transform_content_start() selection_node.transform_content_start()
else: else: # No moving
selection_node.transform_content_confirm() selection_node.transform_content_confirm()
_content_transformation_check = selection_node.is_moving_content _content_transformation_check = selection_node.is_moving_content
@ -145,7 +143,7 @@ func draw_move(position: Vector2) -> void:
if _content_transformation_check != selection_node.is_moving_content: if _content_transformation_check != selection_node.is_moving_content:
return return
if _move: if _move:
if Tools.shift: # Snap to axis if Input.is_action_pressed("transform_snap_axis"): # Snap to axis
var angle := position.angle_to_point(_start_pos) var angle := position.angle_to_point(_start_pos)
if abs(angle) <= PI / 4 or abs(angle) >= 3 * PI / 4: if abs(angle) <= PI / 4 or abs(angle) >= 3 * PI / 4:
position.y = _start_pos.y position.y = _start_pos.y

View file

@ -111,13 +111,13 @@ func _init() -> void:
func _input(event: InputEvent) -> void: func _input(event: InputEvent) -> void:
var options: OptionButton = $LightenDarken var options: OptionButton = $LightenDarken
if event.is_action_pressed("ctrl"): if event.is_action_pressed("change_tool_mode"):
_prev_mode = options.selected _prev_mode = options.selected
if event.is_action("ctrl"): if event.is_action("change_tool_mode"):
options.selected = _prev_mode ^ 1 options.selected = _prev_mode ^ 1
_mode = options.selected _mode = options.selected
_drawer.color_op.lighten_or_darken = _mode _drawer.color_op.lighten_or_darken = _mode
if event.is_action_released("ctrl"): if event.is_action_released("change_tool_mode"):
options.selected = _prev_mode options.selected = _prev_mode
_mode = options.selected _mode = options.selected
_drawer.color_op.lighten_or_darken = _mode _drawer.color_op.lighten_or_darken = _mode
@ -211,7 +211,7 @@ func update_strength() -> void:
func draw_start(position: Vector2) -> void: func draw_start(position: Vector2) -> void:
.draw_start(position) .draw_start(position)
if Input.is_action_pressed("alt"): if Input.is_action_pressed("draw_color_picker"):
_picking_color = true _picking_color = true
_pick_color(position) _pick_color(position)
return return
@ -225,7 +225,7 @@ func draw_start(position: Vector2) -> void:
prepare_undo("Draw") prepare_undo("Draw")
_drawer.reset() _drawer.reset()
_draw_line = Tools.shift _draw_line = Input.is_action_pressed("draw_create_line")
if _draw_line: if _draw_line:
_line_start = position _line_start = position
_line_end = position _line_end = position
@ -240,7 +240,7 @@ func draw_start(position: Vector2) -> void:
func draw_move(position: Vector2) -> void: func draw_move(position: Vector2) -> void:
.draw_move(position) .draw_move(position)
if _picking_color: # Still return even if we released Alt if _picking_color: # Still return even if we released Alt
if Input.is_action_pressed("alt"): if Input.is_action_pressed("draw_color_picker"):
_pick_color(position) _pick_color(position)
return return

View file

@ -76,15 +76,15 @@ func _get_shape_points_filled(_size: Vector2) -> PoolVector2Array:
func _input(event: InputEvent) -> void: func _input(event: InputEvent) -> void:
if _drawing: if _drawing:
if event.is_action_pressed("alt"): if event.is_action_pressed("shape_displace"):
_displace_origin = true _displace_origin = true
elif event.is_action_released("alt"): elif event.is_action_released("shape_displace"):
_displace_origin = false _displace_origin = false
func draw_start(position: Vector2) -> void: func draw_start(position: Vector2) -> void:
.draw_start(position) .draw_start(position)
if Input.is_action_pressed("alt"): if Input.is_action_pressed("shape_displace"):
_picking_color = true _picking_color = true
_pick_color(position) _pick_color(position)
return return
@ -102,7 +102,7 @@ func draw_start(position: Vector2) -> void:
func draw_move(position: Vector2) -> void: func draw_move(position: Vector2) -> void:
.draw_move(position) .draw_move(position)
if _picking_color: # Still return even if we released Alt if _picking_color: # Still return even if we released Alt
if Input.is_action_pressed("alt"): if Input.is_action_pressed("shape_displace"):
_pick_color(position) _pick_color(position)
return return
@ -165,15 +165,13 @@ func _draw_shape(origin: Vector2, dest: Vector2) -> void:
# Given an origin point and destination point, returns a rect representing # Given an origin point and destination point, returns a rect representing
# where the shape will be drawn and what is its size # where the shape will be drawn and what is its size
func _get_result_rect(origin: Vector2, dest: Vector2) -> Rect2: func _get_result_rect(origin: Vector2, dest: Vector2) -> Rect2:
# WARNING: Don't replace Input.is_action_pressed for Tools.control,
# it makes the preview jittery on Windows
var rect := Rect2(Vector2.ZERO, Vector2.ZERO) var rect := Rect2(Vector2.ZERO, Vector2.ZERO)
# Center the rect on the mouse # Center the rect on the mouse
if Input.is_action_pressed("ctrl"): if Input.is_action_pressed("shape_center"):
var new_size := (dest - origin).floor() var new_size := (dest - origin).floor()
# Make rect 1:1 while centering it on the mouse # Make rect 1:1 while centering it on the mouse
if Input.is_action_pressed("shift"): if Input.is_action_pressed("shape_perfect"):
var square_size := max(abs(new_size.x), abs(new_size.y)) var square_size := max(abs(new_size.x), abs(new_size.y))
new_size = Vector2(square_size, square_size) new_size = Vector2(square_size, square_size)
@ -181,7 +179,7 @@ func _get_result_rect(origin: Vector2, dest: Vector2) -> Rect2:
dest = origin + 2 * new_size dest = origin + 2 * new_size
# Make rect 1:1 while not trying to center it # Make rect 1:1 while not trying to center it
if Input.is_action_pressed("shift"): if Input.is_action_pressed("shape_perfect"):
var square_size := min(abs(origin.x - dest.x), abs(origin.y - dest.y)) var square_size := min(abs(origin.x - dest.x), abs(origin.y - dest.y))
rect.position.x = origin.x if origin.x < dest.x else origin.x - square_size rect.position.x = origin.x if origin.x < dest.x else origin.x - square_size
rect.position.y = origin.y if origin.y < dest.y else origin.y - square_size rect.position.y = origin.y if origin.y < dest.y else origin.y - square_size

View file

@ -9,12 +9,12 @@ func _input(event: InputEvent) -> void:
if event is InputEventMouseMotion: if event is InputEventMouseMotion:
_relative = event.relative _relative = event.relative
if event.is_action_pressed("ctrl"): if event.is_action_pressed("change_tool_mode"):
_prev_mode = $ModeOptions.selected _prev_mode = $ModeOptions.selected
if event.is_action("ctrl"): if event.is_action("change_tool_mode"):
$ModeOptions.selected = _prev_mode ^ 1 $ModeOptions.selected = _prev_mode ^ 1
_zoom_mode = $ModeOptions.selected _zoom_mode = $ModeOptions.selected
if event.is_action_released("ctrl"): if event.is_action_released("change_tool_mode"):
$ModeOptions.selected = _prev_mode $ModeOptions.selected = _prev_mode
_zoom_mode = $ModeOptions.selected _zoom_mode = $ModeOptions.selected

View file

@ -1,22 +1,10 @@
extends Camera2D extends Camera2D
enum Cameras { MAIN, SECOND, SMALL } enum Cameras { MAIN, SECOND, SMALL }
enum Direction { UP, DOWN, LEFT, RIGHT }
const LOW_SPEED_MOVE_RATE := 150.0 const KEY_MOVE_ACTION_NAMES := ["camera_left", "camera_right", "camera_up", "camera_down"]
const MEDIUM_SPEED_MOVE_RATE := 750.0 const CAMERA_SPEED_RATE := 15.0
const HIGH_SPEED_MOVE_RATE := 3750.0
const KEY_MOVE_ACTION_NAMES := ["ui_up", "ui_down", "ui_left", "ui_right"]
# Holds sign multipliers for the given directions nyaa
# (per the indices defined by Direction)
# UP, DOWN, LEFT, RIGHT in that order
const DIRECTIONAL_SIGN_MULTIPLIERS := [
Vector2(0.0, -1.0), Vector2(0.0, 1.0), Vector2(-1.0, 0.0), Vector2(1.0, 0.0)
]
# Indices are as in the Direction enum
# This is the total time the key for that direction has been pressed.
var key_move_press_time := [0.0, 0.0, 0.0, 0.0]
var tween: Tween var tween: Tween
var zoom_min := Vector2(0.005, 0.005) var zoom_min := Vector2(0.005, 0.005)
var zoom_max := Vector2.ONE var zoom_max := Vector2.ONE
@ -106,90 +94,6 @@ func update_transparent_checker_offset() -> void:
transparent_checker.update_offset(o, s) transparent_checker.update_offset(o, s)
# Get the speed multiplier for when you've pressed
# a movement key for the given amount of time
func _dir_move_zoom_multiplier(press_time: float) -> float:
if press_time < 0:
return 0.0
if Input.is_key_pressed(KEY_SHIFT) and Input.is_key_pressed(KEY_CONTROL):
return HIGH_SPEED_MOVE_RATE
elif Input.is_key_pressed(KEY_SHIFT):
return MEDIUM_SPEED_MOVE_RATE
elif !Input.is_key_pressed(KEY_CONTROL):
# control + right/left is used to move frames so
# we do this check to ensure that there is no conflict
return LOW_SPEED_MOVE_RATE
else:
return 0.0
func _reset_dir_move_time(direction) -> void:
key_move_press_time[direction] = 0.0
# Check if an event is a ui_up/down/left/right event-press :)
func _is_action_direction_pressed(event: InputEvent, allow_echo: bool = true) -> bool:
for slot in Tools._slots.values():
if slot.tool_node is SelectionTool:
return false
for action in KEY_MOVE_ACTION_NAMES:
if event.is_action_pressed(action, allow_echo):
return true
return false
# Check if an event is a ui_up/down/left/right event release nya
func _is_action_direction_released(event: InputEvent) -> bool:
for slot in Tools._slots.values():
if slot.tool_node is SelectionTool:
return false
for action in KEY_MOVE_ACTION_NAMES:
if event.is_action_released(action):
return true
return false
# get the Direction associated with the event.
# if not a direction event return null
func _get_action_direction(event: InputEvent): # -> Optional[Direction]
if event.is_action("ui_up"):
return Direction.UP
elif event.is_action("ui_down"):
return Direction.DOWN
elif event.is_action("ui_left"):
return Direction.LEFT
elif event.is_action("ui_right"):
return Direction.RIGHT
return null
# Process an action event for a pressed direction
# action
func _process_direction_action_pressed(event: InputEvent) -> void:
var dir = _get_action_direction(event)
if dir == null:
return
var increment := get_process_delta_time()
# Count the total time we've been doing this ^.^
key_move_press_time[dir] += increment
var this_direction_press_time: float = key_move_press_time[dir]
var move_speed := _dir_move_zoom_multiplier(this_direction_press_time)
offset = (
offset
+ move_speed * increment * DIRECTIONAL_SIGN_MULTIPLIERS[dir].rotated(rotation) * zoom
)
_update_rulers()
update_transparent_checker_offset()
# Process an action for a release direction action
func _process_direction_action_released(event: InputEvent) -> void:
var dir = _get_action_direction(event)
if dir == null:
return
_reset_dir_move_time(dir)
func _input(event: InputEvent) -> void: func _input(event: InputEvent) -> void:
if !Global.can_draw: if !Global.can_draw:
drag = false drag = false
@ -200,14 +104,26 @@ func _input(event: InputEvent) -> void:
drag = false drag = false
return return
if event.is_action_pressed("middle_mouse") || event.is_action_pressed("space"): var get_velocity := false
for action in KEY_MOVE_ACTION_NAMES:
if event.is_action(action):
get_velocity = true
if get_velocity:
var velocity := Input.get_vector("camera_left", "camera_right", "camera_up", "camera_down")
if velocity != Vector2.ZERO and !_has_selection_tool():
offset += velocity.rotated(rotation) * zoom * CAMERA_SPEED_RATE
_update_rulers()
if event.is_action_pressed("pan"):
drag = true drag = true
elif event.is_action_released("middle_mouse") || event.is_action_released("space"): elif event.is_action_released("pan"):
drag = false drag = false
elif event.is_action_pressed("zoom_in"): # Wheel Up Event elif event.is_action_pressed("zoom_in"): # Wheel Up Event
zoom_camera(-1) zoom_camera(-1)
elif event.is_action_pressed("zoom_out"): # Wheel Down Event elif event.is_action_pressed("zoom_out"): # Wheel Down Event
zoom_camera(1) zoom_camera(1)
elif event is InputEventMagnifyGesture: # Zoom Gesture on a Laptop touchpad elif event is InputEventMagnifyGesture: # Zoom Gesture on a Laptop touchpad
if event.factor < 1: if event.factor < 1:
zoom_camera(1) zoom_camera(1)
@ -220,15 +136,17 @@ func _input(event: InputEvent) -> void:
offset = offset - event.relative.rotated(rotation) * zoom offset = offset - event.relative.rotated(rotation) * zoom
update_transparent_checker_offset() update_transparent_checker_offset()
_update_rulers() _update_rulers()
elif event is InputEventKey:
if _is_action_direction_pressed(event):
_process_direction_action_pressed(event)
elif _is_action_direction_released(event):
_process_direction_action_released(event)
save_values_to_project() save_values_to_project()
func _has_selection_tool() -> bool:
for slot in Tools._slots.values():
if slot.tool_node is SelectionTool:
return true
return false
# Rotate Camera # Rotate Camera
func _rotate_camera_around_point(degrees: float, point: Vector2) -> void: func _rotate_camera_around_point(degrees: float, point: Vector2) -> void:
offset = (offset - point).rotated(deg2rad(degrees)) + point offset = (offset - point).rotated(deg2rad(degrees)) + point

View file

@ -3,12 +3,7 @@ extends Node2D
func _input(event: InputEvent) -> void: func _input(event: InputEvent) -> void:
if Global.has_focus: if Global.has_focus:
if ( if event is InputEventMouse or event is InputEventKey:
event is InputEventMouse
or event.is_action("shift")
or event.is_action("ctrl")
or event.is_action("alt")
):
update() update()

View file

@ -81,13 +81,13 @@ func _ready() -> void:
func _input(event: InputEvent) -> void: func _input(event: InputEvent) -> void:
if event is InputEventKey: if is_moving_content:
if is_moving_content: if Input.is_action_just_pressed("confirm"):
if Input.is_action_just_pressed("enter"): transform_content_confirm()
transform_content_confirm() elif Input.is_action_just_pressed("cancel"):
elif Input.is_action_just_pressed("escape"): transform_content_cancel()
transform_content_cancel()
if event is InputEventKey:
_move_with_arrow_keys(event) _move_with_arrow_keys(event)
elif event is InputEventMouse: elif event is InputEventMouse:
@ -116,10 +116,10 @@ func _input(event: InputEvent) -> void:
Global.has_focus = false Global.has_focus = false
mouse_pos_on_gizmo_drag = Global.canvas.current_pixel mouse_pos_on_gizmo_drag = Global.canvas.current_pixel
dragged_gizmo = gizmo dragged_gizmo = gizmo
if Input.is_action_pressed("alt"): if Input.is_action_pressed("transform_move_selection_only"):
transform_content_confirm() transform_content_confirm()
if !is_moving_content: if !is_moving_content:
if Input.is_action_pressed("alt"): if Input.is_action_pressed("transform_move_selection_only"):
undo_data = get_undo_data(false) undo_data = get_undo_data(false)
temp_rect = big_bounding_rectangle temp_rect = big_bounding_rectangle
temp_bitmap = Global.current_project.selection_bitmap temp_bitmap = Global.current_project.selection_bitmap
@ -301,7 +301,7 @@ func update_on_zoom(zoom: float) -> void:
func _gizmo_resize() -> void: func _gizmo_resize() -> void:
var dir := dragged_gizmo.direction var dir := dragged_gizmo.direction
if Input.is_action_pressed("ctrl"): if Input.is_action_pressed("shape_center"):
# Code inspired from https://github.com/GDQuest/godot-open-rpg # Code inspired from https://github.com/GDQuest/godot-open-rpg
if dir.x != 0 and dir.y != 0: # Border gizmos if dir.x != 0 and dir.y != 0: # Border gizmos
temp_rect.size = ((Global.canvas.current_pixel - temp_rect_pivot) * 2.0 * dir) temp_rect.size = ((Global.canvas.current_pixel - temp_rect_pivot) * 2.0 * dir)
@ -314,7 +314,7 @@ func _gizmo_resize() -> void:
else: else:
_resize_rect(Global.canvas.current_pixel, dir) _resize_rect(Global.canvas.current_pixel, dir)
if Input.is_action_pressed("shift"): # Maintain aspect ratio if Input.is_action_pressed("shape_perfect"): # Maintain aspect ratio
var end_y = temp_rect.end.y var end_y = temp_rect.end.y
if dir == Vector2(1, -1) or dir.x == 0: # Top right corner, center top and center bottom if dir == Vector2(1, -1) or dir.x == 0: # Top right corner, center top and center bottom
var size := temp_rect.size.y var size := temp_rect.size.y

View file

@ -1,10 +1,14 @@
[gd_scene load_steps=5 format=2] [gd_scene load_steps=6 format=2]
[ext_resource path="res://assets/graphics/misc/color_defaults.png" type="Texture" id=1] [ext_resource path="res://assets/graphics/misc/color_defaults.png" type="Texture" id=1]
[ext_resource path="res://assets/graphics/misc/color_switch.png" type="Texture" id=2] [ext_resource path="res://assets/graphics/misc/color_switch.png" type="Texture" id=2]
[ext_resource path="res://src/UI/ColorPickers.gd" type="Script" id=3] [ext_resource path="res://src/UI/ColorPickers.gd" type="Script" id=3]
[sub_resource type="InputEventAction" id=20]
action = "switch_colors"
[sub_resource type="ShortCut" id=19] [sub_resource type="ShortCut" id=19]
shortcut = SubResource( 20 )
[node name="ColorPickers" type="PanelContainer"] [node name="ColorPickers" type="PanelContainer"]
margin_left = 958.0 margin_left = 958.0

View file

@ -34,7 +34,7 @@ The above copyright notice and this permission notice shall be included in all c
THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.", "Portions of this software are copyright © 2021 The FreeType Project (www.freetype.org). All rights reserved.", "MIT License THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.", "Portions of this software are copyright © 2021 The FreeType Project (www.freetype.org). All rights reserved.", "MIT License
Copyright (c) 2020 Igor Santarek Copyright (c) 2022 Orama Interactive
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
@ -161,7 +161,15 @@ express Statement of Purpose.
d. Affirmer understands and acknowledges that Creative Commons is not a d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to party to this document and has no duty or obligation with respect to
this CC0 or use of the Work. this CC0 or use of the Work.
" ] ", "MIT License
Copyright (c) 2020 Igor Santarek
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." ]
[node name="AboutUI" type="VBoxContainer" parent="."] [node name="AboutUI" type="VBoxContainer" parent="."]
anchor_right = 1.0 anchor_right = 1.0
@ -376,13 +384,14 @@ mouse_default_cursor_shape = 2
group = SubResource( 1 ) group = SubResource( 1 )
text = "FreeType" text = "FreeType"
[node name="godot-gdgifexporterLicense" type="CheckBox" parent="AboutUI/Credits/Licenses/LicenseButtonsContainer"] [node name="keychainLicense" type="CheckBox" parent="AboutUI/Credits/Licenses/LicenseButtonsContainer"]
margin_left = 157.0
margin_top = 28.0 margin_top = 28.0
margin_right = 153.0 margin_right = 348.0
margin_bottom = 52.0 margin_bottom = 52.0
mouse_default_cursor_shape = 2 mouse_default_cursor_shape = 2
group = SubResource( 1 ) group = SubResource( 1 )
text = "godot-gdgifexporter" text = "Keychain"
[node name="godot-dockable-containerLicense" type="CheckBox" parent="AboutUI/Credits/Licenses/LicenseButtonsContainer"] [node name="godot-dockable-containerLicense" type="CheckBox" parent="AboutUI/Credits/Licenses/LicenseButtonsContainer"]
margin_left = 157.0 margin_left = 157.0
@ -393,6 +402,14 @@ mouse_default_cursor_shape = 2
group = SubResource( 1 ) group = SubResource( 1 )
text = "godot-dockable-container" text = "godot-dockable-container"
[node name="godot-gdgifexporterLicense" type="CheckBox" parent="AboutUI/Credits/Licenses/LicenseButtonsContainer"]
margin_top = 28.0
margin_right = 153.0
margin_bottom = 52.0
mouse_default_cursor_shape = 2
group = SubResource( 1 )
text = "godot-gdgifexporter"
[node name="HSeparator2" type="HSeparator" parent="AboutUI"] [node name="HSeparator2" type="HSeparator" parent="AboutUI"]
margin_top = 349.0 margin_top = 349.0
margin_right = 576.0 margin_right = 576.0

View file

@ -36,49 +36,41 @@ corner_radius_bottom_right = 5
corner_radius_bottom_left = 5 corner_radius_bottom_left = 5
expand_margin_bottom = 32.0 expand_margin_bottom = 32.0
[sub_resource type="InputEventKey" id=3] [sub_resource type="InputEventAction" id=21]
control = true action = "go_to_first_frame"
command = true
scancode = 16777229
[sub_resource type="ShortCut" id=4] [sub_resource type="ShortCut" id=4]
shortcut = SubResource( 3 ) shortcut = SubResource( 21 )
[sub_resource type="InputEventKey" id=5] [sub_resource type="InputEventAction" id=23]
control = true action = "go_to_previous_frame"
command = true
scancode = 16777231
[sub_resource type="ShortCut" id=6] [sub_resource type="ShortCut" id=6]
shortcut = SubResource( 5 ) shortcut = SubResource( 23 )
[sub_resource type="InputEventKey" id=7] [sub_resource type="InputEventAction" id=24]
scancode = 16777247 action = "play_backwards"
[sub_resource type="ShortCut" id=8] [sub_resource type="ShortCut" id=8]
shortcut = SubResource( 7 ) shortcut = SubResource( 24 )
[sub_resource type="InputEventKey" id=9] [sub_resource type="InputEventAction" id=25]
scancode = 16777248 action = "play_forward"
[sub_resource type="ShortCut" id=10] [sub_resource type="ShortCut" id=10]
shortcut = SubResource( 9 ) shortcut = SubResource( 25 )
[sub_resource type="InputEventKey" id=11] [sub_resource type="InputEventAction" id=26]
control = true action = "go_to_next_frame"
command = true
scancode = 16777233
[sub_resource type="ShortCut" id=12] [sub_resource type="ShortCut" id=12]
shortcut = SubResource( 11 ) shortcut = SubResource( 26 )
[sub_resource type="InputEventKey" id=13] [sub_resource type="InputEventAction" id=22]
control = true action = "go_to_last_frame"
command = true
scancode = 16777230
[sub_resource type="ShortCut" id=14] [sub_resource type="ShortCut" id=14]
shortcut = SubResource( 13 ) shortcut = SubResource( 22 )
[sub_resource type="StyleBoxEmpty" id=15] [sub_resource type="StyleBoxEmpty" id=15]

View file

@ -9,7 +9,7 @@ func _ready() -> void:
func _input(event: InputEvent) -> void: func _input(event: InputEvent) -> void:
if not Global.has_focus or not event is InputEventKey: if not Global.has_focus or not event is InputEventKey:
return return
for action in ["undo", "redo", "redo_secondary"]: for action in ["undo", "redo"]:
if event.is_action_pressed(action): if event.is_action_pressed(action):
return return

View file

@ -1,42 +1,5 @@
extends Panel extends Panel
enum FileMenuId { NEW, OPEN, OPEN_LAST_PROJECT, SAVE, SAVE_AS, EXPORT, EXPORT_AS, QUIT }
enum EditMenuId { UNDO, REDO, COPY, CUT, PASTE, DELETE, NEW_BRUSH, PREFERENCES }
enum ViewMenuId {
TILE_MODE,
GREYSCALE_VIEW,
MIRROR_VIEW,
SHOW_GRID,
SHOW_PIXEL_GRID,
SHOW_RULERS,
SHOW_GUIDES,
}
enum WindowMenuId { WINDOW_OPACITY, PANELS, LAYOUTS, ZEN_MODE, FULLSCREEN_MODE }
enum ImageMenuId {
SCALE_IMAGE,
CENTRALIZE_IMAGE,
CROP_IMAGE,
RESIZE_CANVAS,
FLIP,
ROTATE,
INVERT_COLORS,
DESATURATION,
OUTLINE,
DROP_SHADOW,
HSV,
GRADIENT,
SHADER
}
enum SelectMenuId { SELECT_ALL, CLEAR_SELECTION, INVERT }
enum HelpMenuId {
VIEW_SPLASH_SCREEN,
ONLINE_DOCS,
ISSUE_TRACKER,
OPEN_LOGS_FOLDER,
CHANGELOG,
ABOUT_PIXELORAMA
}
var file_menu: PopupMenu var file_menu: PopupMenu
var view_menu: PopupMenu var view_menu: PopupMenu
var window_menu: PopupMenu var window_menu: PopupMenu
@ -81,32 +44,32 @@ func _ready() -> void:
func _setup_file_menu() -> void: func _setup_file_menu() -> void:
var file_menu_items := { # order as in FileMenuId enum # Order as in FileMenu enum
"New...": InputMap.get_action_list("new_file")[0].get_scancode_with_modifiers(), var file_menu_items := [
"Open...": InputMap.get_action_list("open_file")[0].get_scancode_with_modifiers(), "New...",
"Open last project...": 0, "Open...",
"Recent projects": 0, "Open last project...",
"Save...": InputMap.get_action_list("save_file")[0].get_scancode_with_modifiers(), "Recent projects",
"Save as...": InputMap.get_action_list("save_file_as")[0].get_scancode_with_modifiers(), "Save...",
"Export...": InputMap.get_action_list("export_file")[0].get_scancode_with_modifiers(), "Save as...",
"Export as...": InputMap.get_action_list("export_file_as")[0].get_scancode_with_modifiers(), "Export...",
"Quit": InputMap.get_action_list("quit")[0].get_scancode_with_modifiers(), "Export as...",
} "Quit",
]
file_menu = file_menu_button.get_popup() file_menu = file_menu_button.get_popup()
var i := 0 var i := 0
for item in file_menu_items:
for item in file_menu_items.keys():
if item == "Recent projects": if item == "Recent projects":
_setup_recent_projects_submenu(item) _setup_recent_projects_submenu(item)
else: else:
file_menu.add_item(item, i, file_menu_items[item]) file_menu.add_item(item, i)
i += 1 i += 1
file_menu.connect("id_pressed", self, "file_menu_id_pressed") file_menu.connect("id_pressed", self, "file_menu_id_pressed")
if OS.get_name() == "HTML5": if OS.get_name() == "HTML5":
file_menu.set_item_disabled(FileMenuId.OPEN_LAST_PROJECT, true) file_menu.set_item_disabled(Global.FileMenu.OPEN_LAST_PROJECT, true)
file_menu.set_item_disabled(FileMenuId.SAVE, true) file_menu.set_item_disabled(Global.FileMenu.SAVE, true)
func _setup_recent_projects_submenu(item: String) -> void: func _setup_recent_projects_submenu(item: String) -> void:
@ -124,21 +87,14 @@ func update_recent_projects_submenu() -> void:
func _setup_edit_menu() -> void: func _setup_edit_menu() -> void:
var edit_menu_items := { # order as in EditMenuId enum # Order as in Global.EditMenu enum
"Undo": InputMap.get_action_list("undo")[0].get_scancode_with_modifiers(), var edit_menu_items := [
"Redo": InputMap.get_action_list("redo")[0].get_scancode_with_modifiers(), "Undo", "Redo", "Copy", "Cut", "Paste", "Delete", "New Brush", "Preferences"
"Copy": InputMap.get_action_list("copy")[0].get_scancode_with_modifiers(), ]
"Cut": InputMap.get_action_list("cut")[0].get_scancode_with_modifiers(),
"Paste": InputMap.get_action_list("paste")[0].get_scancode_with_modifiers(),
"Delete": InputMap.get_action_list("delete")[0].get_scancode_with_modifiers(),
"New Brush": InputMap.get_action_list("new_brush")[0].get_scancode_with_modifiers(),
"Preferences": 0
}
var edit_menu: PopupMenu = edit_menu_button.get_popup() var edit_menu: PopupMenu = edit_menu_button.get_popup()
var i := 0 var i := 0
for item in edit_menu_items:
for item in edit_menu_items.keys(): edit_menu.add_item(item, i)
edit_menu.add_item(item, i, edit_menu_items[item])
i += 1 i += 1
edit_menu.set_item_disabled(6, true) edit_menu.set_item_disabled(6, true)
@ -146,27 +102,26 @@ func _setup_edit_menu() -> void:
func _setup_view_menu() -> void: func _setup_view_menu() -> void:
var view_menu_items := { # order as in ViewMenuId enum # Order as in Global.ViewMenu enum
"Tile Mode": 0, var view_menu_items := [
"Greyscale View": 0, "Tile Mode",
"Mirror View": InputMap.get_action_list("mirror_view")[0].get_scancode_with_modifiers(), "Greyscale View",
"Show Grid": InputMap.get_action_list("show_grid")[0].get_scancode_with_modifiers(), "Mirror View",
"Show Pixel Grid": "Show Grid",
InputMap.get_action_list("show_pixel_grid")[0].get_scancode_with_modifiers(), "Show Pixel Grid",
"Show Rulers": InputMap.get_action_list("show_rulers")[0].get_scancode_with_modifiers(), "Show Rulers",
"Show Guides": InputMap.get_action_list("show_guides")[0].get_scancode_with_modifiers(), "Show Guides",
} ]
view_menu = view_menu_button.get_popup() view_menu = view_menu_button.get_popup()
var i := 0 var i := 0
for item in view_menu_items.keys(): for item in view_menu_items:
if item == "Tile Mode": if item == "Tile Mode":
_setup_tile_mode_submenu(item) _setup_tile_mode_submenu(item)
else: else:
view_menu.add_check_item(item, i, view_menu_items[item]) view_menu.add_check_item(item, i)
i += 1 i += 1
view_menu.set_item_checked(ViewMenuId.SHOW_RULERS, true) view_menu.set_item_checked(Global.ViewMenu.SHOW_RULERS, true)
view_menu.set_item_checked(ViewMenuId.SHOW_GUIDES, true) view_menu.set_item_checked(Global.ViewMenu.SHOW_GUIDES, true)
view_menu.hide_on_checkable_item_selection = false view_menu.hide_on_checkable_item_selection = false
view_menu.connect("id_pressed", self, "view_menu_id_pressed") view_menu.connect("id_pressed", self, "view_menu_id_pressed")
@ -186,32 +141,32 @@ func _setup_tile_mode_submenu(item: String) -> void:
func _setup_window_menu() -> void: func _setup_window_menu() -> void:
var window_menu_items := { # order as in WindowMenuId enum # Order as in Global.WindowMenu enum
"Window Opacity": 0, var window_menu_items := [
"Panels": 0, "Window Opacity",
"Layouts": 0, "Panels",
"Zen Mode": InputMap.get_action_list("zen_mode")[0].get_scancode_with_modifiers(), "Layouts",
"Fullscreen Mode": "Movable Panels",
InputMap.get_action_list("toggle_fullscreen")[0].get_scancode_with_modifiers(), "Zen Mode",
} "Fullscreen Mode",
]
window_menu = window_menu_button.get_popup() window_menu = window_menu_button.get_popup()
var i := 0 var i := 0
for item in window_menu_items.keys(): for item in window_menu_items:
if item == "Panels": if item == "Panels":
_setup_panels_submenu(item) _setup_panels_submenu(item)
elif item == "Layouts": elif item == "Layouts":
_setup_layouts_submenu(item) _setup_layouts_submenu(item)
elif item == "Window Opacity": elif item == "Window Opacity":
window_menu.add_item(item, i, window_menu_items[item]) window_menu.add_item(item, i)
else: else:
window_menu.add_check_item(item, i, window_menu_items[item]) window_menu.add_check_item(item, i)
i += 1 i += 1
window_menu.hide_on_checkable_item_selection = false window_menu.hide_on_checkable_item_selection = false
window_menu.connect("id_pressed", self, "window_menu_id_pressed") window_menu.connect("id_pressed", self, "window_menu_id_pressed")
# Disable window opacity item if per pixel transparency is not allowed # Disable window opacity item if per pixel transparency is not allowed
window_menu.set_item_disabled( window_menu.set_item_disabled(
WindowMenuId.WINDOW_OPACITY, Global.WindowMenu.WINDOW_OPACITY,
!ProjectSettings.get_setting("display/window/per_pixel_transparency/allowed") !ProjectSettings.get_setting("display/window/per_pixel_transparency/allowed")
) )
@ -219,13 +174,10 @@ func _setup_window_menu() -> void:
func _setup_panels_submenu(item: String) -> void: func _setup_panels_submenu(item: String) -> void:
panels_submenu.set_name("panels_submenu") panels_submenu.set_name("panels_submenu")
panels_submenu.hide_on_checkable_item_selection = false panels_submenu.hide_on_checkable_item_selection = false
panels_submenu.add_check_item(
"Moveable Panels", 0, InputMap.get_action_list("edit_mode")[0].get_scancode_with_modifiers()
)
for element in ui_elements: for element in ui_elements:
panels_submenu.add_check_item(element.name) panels_submenu.add_check_item(element.name)
var is_hidden: bool = ui.is_control_hidden(element) var is_hidden: bool = ui.is_control_hidden(element)
panels_submenu.set_item_checked(ui_elements.find(element) + 1, !is_hidden) panels_submenu.set_item_checked(ui_elements.find(element), !is_hidden)
panels_submenu.connect("id_pressed", self, "_panels_submenu_id_pressed") panels_submenu.connect("id_pressed", self, "_panels_submenu_id_pressed")
window_menu.add_child(panels_submenu) window_menu.add_child(panels_submenu)
@ -264,27 +216,27 @@ func populate_layouts_submenu() -> void:
func _setup_image_menu() -> void: func _setup_image_menu() -> void:
var image_menu_items := { # order as in ImageMenuId enum # Order as in Global.ImageMenu enum
"Scale Image": 0, var image_menu_items := [
"Centralize Image": 0, "Scale Image",
"Crop Image": 0, "Centralize Image",
"Resize Canvas": 0, "Crop Image",
"Mirror Image": 0, "Resize Canvas",
"Rotate Image": 0, "Mirror Image",
"Invert Colors": 0, "Rotate Image",
"Desaturation": 0, "Invert Colors",
"Outline": 0, "Desaturation",
"Drop Shadow": 0, "Outline",
"Adjust Hue/Saturation/Value": 0, "Drop Shadow",
"Gradient": 0, "Adjust Hue/Saturation/Value",
# "Shader": 0 "Gradient",
} # "Shader"
]
var image_menu: PopupMenu = image_menu_button.get_popup() var image_menu: PopupMenu = image_menu_button.get_popup()
var i := 0 var i := 0
for item in image_menu_items.keys(): for item in image_menu_items:
image_menu.add_item(item, i, image_menu_items[item]) image_menu.add_item(item, i)
if i == ImageMenuId.RESIZE_CANVAS: if i == Global.ImageMenu.RESIZE_CANVAS:
image_menu.add_separator() image_menu.add_separator()
i += 1 i += 1
@ -292,35 +244,31 @@ func _setup_image_menu() -> void:
func _setup_select_menu() -> void: func _setup_select_menu() -> void:
var select_menu_items := { # order as in EditMenuId enum # Order as in Global.SelectMenu enum
"All": InputMap.get_action_list("select_all")[0].get_scancode_with_modifiers(), var select_menu_items := ["All", "Clear", "Invert"]
"Clear": InputMap.get_action_list("clear_selection")[0].get_scancode_with_modifiers(),
"Invert": InputMap.get_action_list("invert_selection")[0].get_scancode_with_modifiers(),
}
var select_menu: PopupMenu = select_menu_button.get_popup() var select_menu: PopupMenu = select_menu_button.get_popup()
var i := 0 var i := 0
for item in select_menu_items:
for item in select_menu_items.keys(): select_menu.add_item(item, i)
select_menu.add_item(item, i, select_menu_items[item])
i += 1 i += 1
select_menu.connect("id_pressed", self, "select_menu_id_pressed") select_menu.connect("id_pressed", self, "select_menu_id_pressed")
func _setup_help_menu() -> void: func _setup_help_menu() -> void:
var help_menu_items := { # order as in HelpMenuId enum # Order as in Global.HelpMenu enum
"View Splash Screen": 0, var help_menu_items := [
"Online Docs": InputMap.get_action_list("open_docs")[0].get_scancode_with_modifiers(), "View Splash Screen",
"Issue Tracker": 0, "Online Docs",
"Open Logs Folder": 0, "Issue Tracker",
"Changelog": 0, "Open Logs Folder",
"About Pixelorama": 0 "Changelog",
} "About Pixelorama",
]
var help_menu: PopupMenu = help_menu_button.get_popup() var help_menu: PopupMenu = help_menu_button.get_popup()
var i := 0 var i := 0
for item in help_menu_items.keys(): for item in help_menu_items:
help_menu.add_item(item, i, help_menu_items[item]) help_menu.add_item(item, i)
i += 1 i += 1
help_menu.connect("id_pressed", self, "help_menu_id_pressed") help_menu.connect("id_pressed", self, "help_menu_id_pressed")
@ -337,22 +285,22 @@ func _handle_metadata(id: int, menu_button: MenuButton) -> void:
func file_menu_id_pressed(id: int) -> void: func file_menu_id_pressed(id: int) -> void:
match id: match id:
FileMenuId.NEW: Global.FileMenu.NEW:
_on_new_project_file_menu_option_pressed() _on_new_project_file_menu_option_pressed()
FileMenuId.OPEN: Global.FileMenu.OPEN:
_open_project_file() _open_project_file()
FileMenuId.OPEN_LAST_PROJECT: Global.FileMenu.OPEN_LAST_PROJECT:
_on_open_last_project_file_menu_option_pressed() _on_open_last_project_file_menu_option_pressed()
FileMenuId.SAVE: Global.FileMenu.SAVE:
_save_project_file() _save_project_file()
FileMenuId.SAVE_AS: Global.FileMenu.SAVE_AS:
_save_project_file_as() _save_project_file_as()
FileMenuId.EXPORT: Global.FileMenu.EXPORT:
_export_file() _export_file()
FileMenuId.EXPORT_AS: Global.FileMenu.EXPORT_AS:
Global.export_dialog.popup_centered() Global.export_dialog.popup_centered()
Global.dialog_open(true) Global.dialog_open(true)
FileMenuId.QUIT: Global.FileMenu.QUIT:
Global.control.show_quit_dialog() Global.control.show_quit_dialog()
_: _:
_handle_metadata(id, file_menu_button) _handle_metadata(id, file_menu_button)
@ -425,21 +373,21 @@ func _on_recent_projects_submenu_id_pressed(id: int) -> void:
func edit_menu_id_pressed(id: int) -> void: func edit_menu_id_pressed(id: int) -> void:
match id: match id:
EditMenuId.UNDO: Global.EditMenu.UNDO:
Global.current_project.commit_undo() Global.current_project.commit_undo()
EditMenuId.REDO: Global.EditMenu.REDO:
Global.current_project.commit_redo() Global.current_project.commit_redo()
EditMenuId.COPY: Global.EditMenu.COPY:
Global.canvas.selection.copy() Global.canvas.selection.copy()
EditMenuId.CUT: Global.EditMenu.CUT:
Global.canvas.selection.cut() Global.canvas.selection.cut()
EditMenuId.PASTE: Global.EditMenu.PASTE:
Global.canvas.selection.paste() Global.canvas.selection.paste()
EditMenuId.DELETE: Global.EditMenu.DELETE:
Global.canvas.selection.delete() Global.canvas.selection.delete()
EditMenuId.NEW_BRUSH: Global.EditMenu.NEW_BRUSH:
Global.canvas.selection.new_brush() Global.canvas.selection.new_brush()
EditMenuId.PREFERENCES: Global.EditMenu.PREFERENCES:
Global.preferences_dialog.popup_centered(Vector2(400, 280)) Global.preferences_dialog.popup_centered(Vector2(400, 280))
Global.dialog_open(true) Global.dialog_open(true)
_: _:
@ -448,17 +396,17 @@ func edit_menu_id_pressed(id: int) -> void:
func view_menu_id_pressed(id: int) -> void: func view_menu_id_pressed(id: int) -> void:
match id: match id:
ViewMenuId.GREYSCALE_VIEW: Global.ViewMenu.GREYSCALE_VIEW:
_toggle_greyscale_view() _toggle_greyscale_view()
ViewMenuId.MIRROR_VIEW: Global.ViewMenu.MIRROR_VIEW:
_toggle_mirror_view() _toggle_mirror_view()
ViewMenuId.SHOW_GRID: Global.ViewMenu.SHOW_GRID:
_toggle_show_grid() _toggle_show_grid()
ViewMenuId.SHOW_PIXEL_GRID: Global.ViewMenu.SHOW_PIXEL_GRID:
_toggle_show_pixel_grid() _toggle_show_pixel_grid()
ViewMenuId.SHOW_RULERS: Global.ViewMenu.SHOW_RULERS:
_toggle_show_rulers() _toggle_show_rulers()
ViewMenuId.SHOW_GUIDES: Global.ViewMenu.SHOW_GUIDES:
_toggle_show_guides() _toggle_show_guides()
_: _:
_handle_metadata(id, view_menu_button) _handle_metadata(id, view_menu_button)
@ -478,26 +426,26 @@ func _tile_mode_submenu_id_pressed(id: int) -> void:
func window_menu_id_pressed(id: int) -> void: func window_menu_id_pressed(id: int) -> void:
match id: match id:
WindowMenuId.WINDOW_OPACITY: Global.WindowMenu.WINDOW_OPACITY:
window_opacity_dialog.popup_centered() window_opacity_dialog.popup_centered()
Global.dialog_open(true) Global.dialog_open(true)
WindowMenuId.ZEN_MODE: Global.WindowMenu.MOVABLE_PANELS:
ui.tabs_visible = !ui.tabs_visible
window_menu.set_item_checked(id, ui.tabs_visible)
Global.WindowMenu.ZEN_MODE:
_toggle_zen_mode() _toggle_zen_mode()
WindowMenuId.FULLSCREEN_MODE: Global.WindowMenu.FULLSCREEN_MODE:
_toggle_fullscreen() _toggle_fullscreen()
_: _:
_handle_metadata(id, window_menu_button) _handle_metadata(id, window_menu_button)
func _panels_submenu_id_pressed(id: int) -> void: func _panels_submenu_id_pressed(id: int) -> void:
if id == 0: if zen_mode:
ui.tabs_visible = !ui.tabs_visible
panels_submenu.set_item_checked(0, ui.tabs_visible)
if zen_mode or id == 0:
return return
var element_visible = panels_submenu.is_item_checked(id) var element_visible = panels_submenu.is_item_checked(id)
ui.set_control_hidden(ui_elements[id - 1], element_visible) ui.set_control_hidden(ui_elements[id], element_visible)
panels_submenu.set_item_checked(id, !element_visible) panels_submenu.set_item_checked(id, !element_visible)
@ -520,12 +468,12 @@ func set_layout(id: int) -> void:
for i in ui_elements.size(): for i in ui_elements.size():
var is_hidden: bool = ui.is_control_hidden(ui_elements[i]) var is_hidden: bool = ui.is_control_hidden(ui_elements[i])
panels_submenu.set_item_checked(i + 1, !is_hidden) panels_submenu.set_item_checked(i, !is_hidden)
if zen_mode: # Turn zen mode off if zen_mode: # Turn zen mode off
Global.control.find_node("TabsContainer").visible = true Global.control.find_node("TabsContainer").visible = true
zen_mode = false zen_mode = false
window_menu.set_item_checked(WindowMenuId.ZEN_MODE, false) window_menu.set_item_checked(Global.WindowMenu.ZEN_MODE, false)
# Hacky but without 2 idle frames it doesn't work properly. Should be replaced eventually # Hacky but without 2 idle frames it doesn't work properly. Should be replaced eventually
yield(get_tree(), "idle_frame") yield(get_tree(), "idle_frame")
@ -537,7 +485,7 @@ func set_layout(id: int) -> void:
func _toggle_greyscale_view() -> void: func _toggle_greyscale_view() -> void:
Global.greyscale_view = !Global.greyscale_view Global.greyscale_view = !Global.greyscale_view
greyscale_vision.visible = Global.greyscale_view greyscale_vision.visible = Global.greyscale_view
view_menu.set_item_checked(ViewMenuId.GREYSCALE_VIEW, Global.greyscale_view) view_menu.set_item_checked(Global.ViewMenu.GREYSCALE_VIEW, Global.greyscale_view)
func _toggle_mirror_view() -> void: func _toggle_mirror_view() -> void:
@ -552,31 +500,31 @@ func _toggle_mirror_view() -> void:
else: else:
Global.canvas.selection.marching_ants_outline.position.x = 0 Global.canvas.selection.marching_ants_outline.position.x = 0
Global.canvas.selection.update() Global.canvas.selection.update()
view_menu.set_item_checked(ViewMenuId.MIRROR_VIEW, Global.mirror_view) view_menu.set_item_checked(Global.ViewMenu.MIRROR_VIEW, Global.mirror_view)
func _toggle_show_grid() -> void: func _toggle_show_grid() -> void:
Global.draw_grid = !Global.draw_grid Global.draw_grid = !Global.draw_grid
view_menu.set_item_checked(ViewMenuId.SHOW_GRID, Global.draw_grid) view_menu.set_item_checked(Global.ViewMenu.SHOW_GRID, Global.draw_grid)
Global.canvas.grid.update() Global.canvas.grid.update()
func _toggle_show_pixel_grid() -> void: func _toggle_show_pixel_grid() -> void:
Global.draw_pixel_grid = !Global.draw_pixel_grid Global.draw_pixel_grid = !Global.draw_pixel_grid
view_menu.set_item_checked(ViewMenuId.SHOW_PIXEL_GRID, Global.draw_pixel_grid) view_menu.set_item_checked(Global.ViewMenu.SHOW_PIXEL_GRID, Global.draw_pixel_grid)
Global.canvas.pixel_grid.update() Global.canvas.pixel_grid.update()
func _toggle_show_rulers() -> void: func _toggle_show_rulers() -> void:
Global.show_rulers = !Global.show_rulers Global.show_rulers = !Global.show_rulers
view_menu.set_item_checked(ViewMenuId.SHOW_RULERS, Global.show_rulers) view_menu.set_item_checked(Global.ViewMenu.SHOW_RULERS, Global.show_rulers)
Global.horizontal_ruler.visible = Global.show_rulers Global.horizontal_ruler.visible = Global.show_rulers
Global.vertical_ruler.visible = Global.show_rulers Global.vertical_ruler.visible = Global.show_rulers
func _toggle_show_guides() -> void: func _toggle_show_guides() -> void:
Global.show_guides = !Global.show_guides Global.show_guides = !Global.show_guides
view_menu.set_item_checked(ViewMenuId.SHOW_GUIDES, Global.show_guides) view_menu.set_item_checked(Global.ViewMenu.SHOW_GUIDES, Global.show_guides)
for guide in Global.canvas.get_children(): for guide in Global.canvas.get_children():
if guide is Guide and guide in Global.current_project.guides: if guide is Guide and guide in Global.current_project.guides:
guide.visible = Global.show_guides guide.visible = Global.show_guides
@ -591,64 +539,64 @@ func _toggle_zen_mode() -> void:
for i in ui_elements.size(): for i in ui_elements.size():
if ui_elements[i].name == "Main Canvas": if ui_elements[i].name == "Main Canvas":
continue continue
if !panels_submenu.is_item_checked(i + 1): if !panels_submenu.is_item_checked(i):
continue continue
ui.set_control_hidden(ui_elements[i], !zen_mode) ui.set_control_hidden(ui_elements[i], !zen_mode)
Global.control.find_node("TabsContainer").visible = zen_mode Global.control.find_node("TabsContainer").visible = zen_mode
zen_mode = !zen_mode zen_mode = !zen_mode
window_menu.set_item_checked(WindowMenuId.ZEN_MODE, zen_mode) window_menu.set_item_checked(Global.WindowMenu.ZEN_MODE, zen_mode)
func _toggle_fullscreen() -> void: func _toggle_fullscreen() -> void:
OS.window_fullscreen = !OS.window_fullscreen OS.window_fullscreen = !OS.window_fullscreen
window_menu.set_item_checked(WindowMenuId.FULLSCREEN_MODE, OS.window_fullscreen) window_menu.set_item_checked(Global.WindowMenu.FULLSCREEN_MODE, OS.window_fullscreen)
if OS.window_fullscreen: # If window is fullscreen then reset transparency if OS.window_fullscreen: # If window is fullscreen then reset transparency
window_opacity_dialog.set_window_opacity(1.0) window_opacity_dialog.set_window_opacity(1.0)
func image_menu_id_pressed(id: int) -> void: func image_menu_id_pressed(id: int) -> void:
match id: match id:
ImageMenuId.SCALE_IMAGE: Global.ImageMenu.SCALE_IMAGE:
_show_scale_image_popup() _show_scale_image_popup()
ImageMenuId.CENTRALIZE_IMAGE: Global.ImageMenu.CENTRALIZE_IMAGE:
DrawingAlgos.centralize() DrawingAlgos.centralize()
ImageMenuId.CROP_IMAGE: Global.ImageMenu.CROP_IMAGE:
DrawingAlgos.crop_image() DrawingAlgos.crop_image()
ImageMenuId.RESIZE_CANVAS: Global.ImageMenu.RESIZE_CANVAS:
_show_resize_canvas_popup() _show_resize_canvas_popup()
ImageMenuId.FLIP: Global.ImageMenu.FLIP:
Global.control.get_node("Dialogs/ImageEffects/FlipImageDialog").popup_centered() Global.control.get_node("Dialogs/ImageEffects/FlipImageDialog").popup_centered()
Global.dialog_open(true) Global.dialog_open(true)
ImageMenuId.ROTATE: Global.ImageMenu.ROTATE:
_show_rotate_image_popup() _show_rotate_image_popup()
ImageMenuId.INVERT_COLORS: Global.ImageMenu.INVERT_COLORS:
Global.control.get_node("Dialogs/ImageEffects/InvertColorsDialog").popup_centered() Global.control.get_node("Dialogs/ImageEffects/InvertColorsDialog").popup_centered()
Global.dialog_open(true) Global.dialog_open(true)
ImageMenuId.DESATURATION: Global.ImageMenu.DESATURATION:
Global.control.get_node("Dialogs/ImageEffects/DesaturateDialog").popup_centered() Global.control.get_node("Dialogs/ImageEffects/DesaturateDialog").popup_centered()
Global.dialog_open(true) Global.dialog_open(true)
ImageMenuId.OUTLINE: Global.ImageMenu.OUTLINE:
_show_add_outline_popup() _show_add_outline_popup()
ImageMenuId.DROP_SHADOW: Global.ImageMenu.DROP_SHADOW:
_show_drop_shadow_popup() _show_drop_shadow_popup()
ImageMenuId.HSV: Global.ImageMenu.HSV:
_show_hsv_configuration_popup() _show_hsv_configuration_popup()
ImageMenuId.GRADIENT: Global.ImageMenu.GRADIENT:
Global.control.get_node("Dialogs/ImageEffects/GradientDialog").popup_centered() Global.control.get_node("Dialogs/ImageEffects/GradientDialog").popup_centered()
Global.dialog_open(true) Global.dialog_open(true)
# ImageMenuId.SHADER: # Global.ImageMenu.SHADER:
# Global.control.get_node("Dialogs/ImageEffects/ShaderEffect").popup_centered() # Global.control.get_node("Dialogs/ImageEffects/ShaderEffect").popup_centered()
# Global.dialog_open(true) # Global.dialog_open(true)
@ -688,11 +636,11 @@ func _show_hsv_configuration_popup() -> void:
func select_menu_id_pressed(id: int) -> void: func select_menu_id_pressed(id: int) -> void:
match id: match id:
SelectMenuId.SELECT_ALL: Global.SelectMenu.SELECT_ALL:
Global.canvas.selection.select_all() Global.canvas.selection.select_all()
SelectMenuId.CLEAR_SELECTION: Global.SelectMenu.CLEAR_SELECTION:
Global.canvas.selection.clear_selection(true) Global.canvas.selection.clear_selection(true)
SelectMenuId.INVERT: Global.SelectMenu.INVERT:
Global.canvas.selection.invert() Global.canvas.selection.invert()
_: _:
_handle_metadata(id, select_menu_button) _handle_metadata(id, select_menu_button)
@ -700,22 +648,22 @@ func select_menu_id_pressed(id: int) -> void:
func help_menu_id_pressed(id: int) -> void: func help_menu_id_pressed(id: int) -> void:
match id: match id:
HelpMenuId.VIEW_SPLASH_SCREEN: Global.HelpMenu.VIEW_SPLASH_SCREEN:
Global.control.get_node("Dialogs/SplashDialog").popup_centered() Global.control.get_node("Dialogs/SplashDialog").popup_centered()
Global.dialog_open(true) Global.dialog_open(true)
HelpMenuId.ONLINE_DOCS: Global.HelpMenu.ONLINE_DOCS:
OS.shell_open("https://orama-interactive.github.io/Pixelorama-Docs/") OS.shell_open("https://orama-interactive.github.io/Pixelorama-Docs/")
HelpMenuId.ISSUE_TRACKER: Global.HelpMenu.ISSUE_TRACKER:
OS.shell_open("https://github.com/Orama-Interactive/Pixelorama/issues") OS.shell_open("https://github.com/Orama-Interactive/Pixelorama/issues")
HelpMenuId.OPEN_LOGS_FOLDER: Global.HelpMenu.OPEN_LOGS_FOLDER:
var dir = Directory.new() var dir = Directory.new()
dir.make_dir_recursive("user://logs") # In case someone deleted it dir.make_dir_recursive("user://logs") # In case someone deleted it
OS.shell_open(ProjectSettings.globalize_path("user://logs")) OS.shell_open(ProjectSettings.globalize_path("user://logs"))
HelpMenuId.CHANGELOG: Global.HelpMenu.CHANGELOG:
OS.shell_open( OS.shell_open(
"https://github.com/Orama-Interactive/Pixelorama/blob/master/CHANGELOG.md#v010---2022-04-15" "https://github.com/Orama-Interactive/Pixelorama/blob/master/CHANGELOG.md#v010---2022-04-15"
) )
HelpMenuId.ABOUT_PIXELORAMA: Global.HelpMenu.ABOUT_PIXELORAMA:
Global.control.get_node("Dialogs/AboutDialog").popup_centered() Global.control.get_node("Dialogs/AboutDialog").popup_centered()
Global.dialog_open(true) Global.dialog_open(true)
_: _:

View file

@ -285,6 +285,7 @@ script = ExtResource( 23 )
[node name="Viewport" type="Viewport" parent="DockableContainer/Main Canvas/ViewportandVerticalRuler/ViewportContainer"] [node name="Viewport" type="Viewport" parent="DockableContainer/Main Canvas/ViewportandVerticalRuler/ViewportContainer"]
size = Vector2( 862, 466 ) size = Vector2( 862, 466 )
handle_input_locally = false
usage = 0 usage = 0
render_target_update_mode = 3 render_target_update_mode = 3