Initial commit
Half-baked state. Most of the UI is mostly done, but the integration with Grist (or any other data provider) is still missing.
This commit is contained in:
470
addons/onscreenkeyboard/onscreen_keyboard.gd
Normal file
470
addons/onscreenkeyboard/onscreen_keyboard.gd
Normal file
@@ -0,0 +1,470 @@
|
||||
@tool
|
||||
extends PanelContainer
|
||||
|
||||
###########################
|
||||
## SETTINGS
|
||||
###########################
|
||||
|
||||
@export var auto_show:bool = true
|
||||
@export var animate:bool = true
|
||||
|
||||
@export_file var custom_layout_file
|
||||
@export var set_tool_tip := true
|
||||
@export_group("Style")
|
||||
@export var separation:Vector2i = Vector2i(0,0)
|
||||
var style_background:StyleBoxFlat = null
|
||||
@export var background:StyleBoxFlat = null:
|
||||
set(new_val):
|
||||
style_background = new_val
|
||||
background = new_val
|
||||
get:
|
||||
return style_background
|
||||
var style_normal:StyleBoxFlat = null
|
||||
@export var normal:StyleBoxFlat = null:
|
||||
set(new_val):
|
||||
style_normal = new_val
|
||||
normal = new_val
|
||||
get:
|
||||
return style_normal
|
||||
var style_hover:StyleBoxFlat = null
|
||||
@export var hover:StyleBoxFlat = null:
|
||||
set(new_val):
|
||||
style_hover = new_val
|
||||
hover = new_val
|
||||
get:
|
||||
return style_hover
|
||||
var style_pressed:StyleBoxFlat = null
|
||||
@export var pressed:StyleBoxFlat = null:
|
||||
set(new_val):
|
||||
style_pressed = new_val
|
||||
pressed = new_val
|
||||
get:
|
||||
return style_pressed
|
||||
var style_special_keys:StyleBoxFlat = null
|
||||
@export var special_keys:StyleBoxFlat = null:
|
||||
set(new_val):
|
||||
style_special_keys = new_val
|
||||
special_keys = new_val
|
||||
get:
|
||||
return style_special_keys
|
||||
@export_group("Font")
|
||||
@export var font:FontFile
|
||||
@export var font_size:int = 20
|
||||
@export var font_color_normal:Color = Color(1,1,1)
|
||||
@export var font_color_hover:Color = Color(1,1,1)
|
||||
@export var font_color_pressed:Color = Color(1,1,1)
|
||||
|
||||
###########################
|
||||
## SIGNALS
|
||||
###########################
|
||||
|
||||
signal layout_changed
|
||||
|
||||
###########################
|
||||
## PANEL
|
||||
###########################
|
||||
|
||||
func _enter_tree():
|
||||
if not get_tree().get_root().size_changed.is_connected(size_changed):
|
||||
get_tree().get_root().size_changed.connect(size_changed)
|
||||
_init_keyboard()
|
||||
|
||||
#func _exit_tree():
|
||||
# pass
|
||||
|
||||
#func _process(delta):
|
||||
# pass
|
||||
|
||||
func _input(event):
|
||||
_update_auto_display_on_input(event)
|
||||
|
||||
|
||||
func size_changed():
|
||||
if auto_show and visible:
|
||||
_hide_keyboard()
|
||||
|
||||
|
||||
###########################
|
||||
## INIT
|
||||
###########################
|
||||
var KeyboardButton
|
||||
var KeyListHandler
|
||||
|
||||
var layouts = []
|
||||
var keys = []
|
||||
var capslock_keys = []
|
||||
var uppercase = false
|
||||
|
||||
var tween_position
|
||||
var tween_speed = .2
|
||||
|
||||
var hide_position = Vector2()
|
||||
|
||||
func _init_keyboard():
|
||||
if custom_layout_file == null:
|
||||
var default_layout = preload("default_layout.gd").new()
|
||||
_create_keyboard(default_layout.data)
|
||||
else:
|
||||
_create_keyboard(_load_json(custom_layout_file))
|
||||
|
||||
# init positioning without animation
|
||||
var tmp_anim = animate
|
||||
animate = false
|
||||
if visible:
|
||||
_hide_keyboard()
|
||||
elif visible:
|
||||
_show_keyboard()
|
||||
animate = tmp_anim
|
||||
|
||||
|
||||
###########################
|
||||
## HIDE/SHOW
|
||||
###########################
|
||||
|
||||
var focus_object = null
|
||||
|
||||
func show():
|
||||
_show_keyboard()
|
||||
|
||||
func hide():
|
||||
_hide_keyboard()
|
||||
|
||||
var released = true
|
||||
func _update_auto_display_on_input(event):
|
||||
if auto_show == false:
|
||||
return
|
||||
|
||||
if event is InputEventMouseButton:
|
||||
released = !released
|
||||
if released == false:
|
||||
return
|
||||
|
||||
var focus_object = get_viewport().gui_get_focus_owner()
|
||||
if focus_object != null:
|
||||
var click_on_input = Rect2(focus_object.global_position, focus_object.size).has_point(get_global_mouse_position())
|
||||
var click_on_keyboard = Rect2(global_position, size).has_point(get_global_mouse_position())
|
||||
|
||||
if click_on_input:
|
||||
if is_keyboard_focus_object(focus_object):
|
||||
_show_keyboard()
|
||||
elif click_on_keyboard:
|
||||
_show_keyboard()
|
||||
else:
|
||||
_hide_keyboard()
|
||||
|
||||
if event is InputEventKey:
|
||||
var focus_object = get_viewport().gui_get_focus_owner()
|
||||
if focus_object != null:
|
||||
if event.keycode == KEY_ENTER:
|
||||
if is_keyboard_focus_object_complete_on_enter(focus_object):
|
||||
focus_object.release_focus()
|
||||
_hide_keyboard()
|
||||
|
||||
|
||||
func _hide_keyboard(key_data=null):
|
||||
if animate:
|
||||
var new_y_pos = get_viewport().get_visible_rect().size.y + 10
|
||||
animate_position(Vector2(position.x, new_y_pos), true)
|
||||
else:
|
||||
change_visibility(false)
|
||||
|
||||
|
||||
func _show_keyboard(key_data=null):
|
||||
change_visibility(true)
|
||||
if animate:
|
||||
var new_y_pos = get_viewport().get_visible_rect().size.y - size.y
|
||||
animate_position(Vector2(position.x, new_y_pos))
|
||||
|
||||
|
||||
func animate_position(new_position, trigger_visibility:bool=false):
|
||||
var tween = get_tree().create_tween()
|
||||
if trigger_visibility:
|
||||
tween.finished.connect(change_visibility.bind(!visible))
|
||||
tween.tween_property(
|
||||
self,"position",
|
||||
Vector2(new_position),
|
||||
tween_speed
|
||||
).set_trans(Tween.TRANS_SINE)
|
||||
|
||||
|
||||
func change_visibility(value):
|
||||
if value:
|
||||
super.show()
|
||||
else:
|
||||
_set_caps_lock(false)
|
||||
super.hide()
|
||||
visibility_changed.emit()
|
||||
|
||||
|
||||
###########################
|
||||
## KEY LAYOUT
|
||||
###########################
|
||||
|
||||
var prev_prev_layout = null
|
||||
var previous_layout = null
|
||||
var current_layout = null
|
||||
|
||||
func set_active_layout_by_name(name):
|
||||
for layout in layouts:
|
||||
if layout.get_meta("layout_name") == str(name):
|
||||
_show_layout(layout)
|
||||
else:
|
||||
_hide_layout(layout)
|
||||
|
||||
|
||||
func _show_layout(layout):
|
||||
layout.show()
|
||||
current_layout = layout
|
||||
|
||||
|
||||
func _hide_layout(layout):
|
||||
layout.hide()
|
||||
|
||||
|
||||
func _switch_layout(key_data):
|
||||
prev_prev_layout = previous_layout
|
||||
previous_layout = current_layout
|
||||
layout_changed.emit(key_data.get("layout-name"))
|
||||
|
||||
for layout in layouts:
|
||||
_hide_layout(layout)
|
||||
|
||||
if key_data.get("layout-name") == "PREVIOUS-LAYOUT":
|
||||
if prev_prev_layout != null:
|
||||
_show_layout(prev_prev_layout)
|
||||
return
|
||||
|
||||
for layout in layouts:
|
||||
if layout.get_meta("layout_name") == key_data.get("layout-name"):
|
||||
_show_layout(layout)
|
||||
return
|
||||
|
||||
_set_caps_lock(false)
|
||||
|
||||
|
||||
###########################
|
||||
## KEY EVENTS
|
||||
###########################
|
||||
|
||||
func _set_caps_lock(value: bool):
|
||||
uppercase = value
|
||||
for key in capslock_keys:
|
||||
if value:
|
||||
if key.get_draw_mode() != BaseButton.DRAW_PRESSED:
|
||||
key.button_pressed = !key.button_pressed
|
||||
else:
|
||||
if key.get_draw_mode() == BaseButton.DRAW_PRESSED:
|
||||
key.button_pressed = !key.button_pressed
|
||||
|
||||
for key in keys:
|
||||
key.change_uppercase(value)
|
||||
|
||||
|
||||
func _trigger_uppercase(key_data):
|
||||
uppercase = !uppercase
|
||||
_set_caps_lock(uppercase)
|
||||
|
||||
|
||||
func _key_released(key_data):
|
||||
if key_data.has("output"):
|
||||
var key_value = key_data.get("output")
|
||||
|
||||
###########################
|
||||
## DISPATCH InputEvent
|
||||
###########################
|
||||
|
||||
var input_event_key = InputEventKey.new()
|
||||
input_event_key.shift_pressed = uppercase
|
||||
input_event_key.alt_pressed = false
|
||||
input_event_key.meta_pressed = false
|
||||
input_event_key.ctrl_pressed = false
|
||||
input_event_key.pressed = true
|
||||
|
||||
var key = KeyListHandler.get_key_from_string(key_value)
|
||||
if !uppercase && KeyListHandler.has_lowercase(key_value):
|
||||
key +=32
|
||||
|
||||
input_event_key.keycode = key
|
||||
input_event_key.unicode = key
|
||||
|
||||
Input.parse_input_event(input_event_key)
|
||||
|
||||
###########################
|
||||
## DISABLE CAPSLOCK AFTER
|
||||
###########################
|
||||
_set_caps_lock(false)
|
||||
|
||||
|
||||
###########################
|
||||
## CONSTRUCT KEYBOARD
|
||||
###########################
|
||||
|
||||
func _set_key_style(style_name:String, key: Control, style:StyleBoxFlat):
|
||||
if style != null:
|
||||
key.add_theme_stylebox_override(style_name, style)
|
||||
|
||||
|
||||
func _create_keyboard(layout_data):
|
||||
if layout_data == null:
|
||||
print("ERROR. No layout file found")
|
||||
return
|
||||
|
||||
KeyListHandler = preload("keylist.gd").new()
|
||||
KeyboardButton = preload("keyboard_button.gd")
|
||||
|
||||
var ICON_DELETE = preload("icons/delete.png")
|
||||
var ICON_SHIFT = preload("icons/shift.png")
|
||||
var ICON_LEFT = preload("icons/left.png")
|
||||
var ICON_RIGHT = preload("icons/right.png")
|
||||
var ICON_HIDE = preload("icons/hide.png")
|
||||
var ICON_ENTER = preload("icons/enter.png")
|
||||
|
||||
var data = layout_data
|
||||
|
||||
if style_background != null:
|
||||
add_theme_stylebox_override('panel', style_background)
|
||||
|
||||
var index = 0
|
||||
for layout in data.get("layouts"):
|
||||
|
||||
var layout_container = PanelContainer.new()
|
||||
|
||||
if style_background != null:
|
||||
layout_container.add_theme_stylebox_override('panel', style_background)
|
||||
|
||||
# SHOW FIRST LAYOUT ON DEFAULT
|
||||
if index > 0:
|
||||
layout_container.hide()
|
||||
else:
|
||||
current_layout = layout_container
|
||||
|
||||
var layout_name = layout.get("name")
|
||||
layout_container.set_meta("layout_name", layout_name)
|
||||
if set_tool_tip:
|
||||
layout_container.tooltip_text = layout_name
|
||||
layouts.push_back(layout_container)
|
||||
add_child(layout_container)
|
||||
|
||||
var base_vbox = VBoxContainer.new()
|
||||
base_vbox.size_flags_horizontal = SIZE_EXPAND_FILL
|
||||
base_vbox.size_flags_vertical = SIZE_EXPAND_FILL
|
||||
# theme override for spacing
|
||||
base_vbox.add_theme_constant_override("separation", separation.y)
|
||||
|
||||
for row in layout.get("rows"):
|
||||
|
||||
var key_row = HBoxContainer.new()
|
||||
key_row.size_flags_horizontal = SIZE_EXPAND_FILL
|
||||
key_row.size_flags_vertical = SIZE_EXPAND_FILL
|
||||
key_row.add_theme_constant_override("separation", separation.x)
|
||||
|
||||
for key in row.get("keys"):
|
||||
var new_key = KeyboardButton.new(key)
|
||||
|
||||
_set_key_style("normal",new_key, style_normal)
|
||||
_set_key_style("hover",new_key, style_hover)
|
||||
_set_key_style("pressed",new_key, style_pressed)
|
||||
|
||||
new_key.set('theme_override_font_sizes/font_size', font_size)
|
||||
if font != null:
|
||||
new_key.set('theme_override_fonts/font', font)
|
||||
if font_color_normal != null:
|
||||
new_key.set('theme_override_colors/font_color', font_color_normal)
|
||||
new_key.set('theme_override_colors/font_hover_color', font_color_hover)
|
||||
new_key.set('theme_override_colors/font_pressed_color', font_color_pressed)
|
||||
new_key.set('theme_override_colors/font_disabled_color', font_color_normal)
|
||||
|
||||
new_key.released.connect(_key_released)
|
||||
|
||||
if key.has("type"):
|
||||
if key.get("type") == "switch-layout":
|
||||
new_key.released.connect(_switch_layout)
|
||||
_set_key_style("normal",new_key, style_special_keys)
|
||||
elif key.get("type") == "special":
|
||||
_set_key_style("normal",new_key, style_special_keys)
|
||||
elif key.get("type") == "special-shift":
|
||||
new_key.released.connect(_trigger_uppercase)
|
||||
new_key.toggle_mode = true
|
||||
capslock_keys.push_back(new_key)
|
||||
_set_key_style("normal",new_key, style_special_keys)
|
||||
elif key.get("type") == "special-hide-keyboard":
|
||||
new_key.released.connect(_hide_keyboard)
|
||||
_set_key_style("normal",new_key, style_special_keys)
|
||||
|
||||
# SET ICONS
|
||||
if key.has("display-icon"):
|
||||
var icon_data = str(key.get("display-icon")).split(":")
|
||||
# PREDEFINED
|
||||
if str(icon_data[0])=="PREDEFINED":
|
||||
if str(icon_data[1])=="DELETE":
|
||||
new_key.set_icon(ICON_DELETE)
|
||||
elif str(icon_data[1])=="SHIFT":
|
||||
new_key.set_icon(ICON_SHIFT)
|
||||
elif str(icon_data[1])=="LEFT":
|
||||
new_key.set_icon(ICON_LEFT)
|
||||
elif str(icon_data[1])=="RIGHT":
|
||||
new_key.set_icon(ICON_RIGHT)
|
||||
elif str(icon_data[1])=="HIDE":
|
||||
new_key.set_icon(ICON_HIDE)
|
||||
elif str(icon_data[1])=="ENTER":
|
||||
new_key.set_icon(ICON_ENTER)
|
||||
# CUSTOM
|
||||
if str(icon_data[0])=="res":
|
||||
var texture = load(key.get("display-icon"))
|
||||
new_key.set_icon(texture)
|
||||
|
||||
if font_color_normal != null:
|
||||
new_key.set_icon_color(font_color_normal)
|
||||
|
||||
key_row.add_child(new_key)
|
||||
keys.push_back(new_key)
|
||||
|
||||
base_vbox.add_child(key_row)
|
||||
|
||||
layout_container.add_child(base_vbox)
|
||||
index+=1
|
||||
|
||||
|
||||
###########################
|
||||
## LOAD SETTINGS
|
||||
###########################
|
||||
|
||||
func _load_json(file_path) -> Variant:
|
||||
var json = JSON.new()
|
||||
var json_string = _load_file(file_path)
|
||||
var error = json.parse(json_string)
|
||||
if error == OK:
|
||||
var data_received = json.data
|
||||
# print(data_received)
|
||||
if typeof(data_received) == TYPE_DICTIONARY:
|
||||
return data_received
|
||||
else:
|
||||
return {"msg": "unexpected data => expected DICTIONARY"}
|
||||
else:
|
||||
print("JSON Parse Error: ", json.get_error_message(), " in ", json_string, " at line ", json.get_error_line())
|
||||
return {"msg":json.get_error_message()}
|
||||
|
||||
|
||||
func _load_file(file_path):
|
||||
var file = FileAccess.open(file_path, FileAccess.READ)
|
||||
if not file:
|
||||
print("Error loading File. Error: ")
|
||||
|
||||
var content = file.get_as_text()
|
||||
file.close()
|
||||
return content
|
||||
|
||||
|
||||
###########################
|
||||
## HELPER
|
||||
###########################
|
||||
|
||||
func is_keyboard_focus_object_complete_on_enter(focus_object):
|
||||
if focus_object is LineEdit:
|
||||
return true
|
||||
return false
|
||||
|
||||
func is_keyboard_focus_object(focus_object):
|
||||
if focus_object is LineEdit or focus_object is TextEdit:
|
||||
return true
|
||||
return false
|
||||
Reference in New Issue
Block a user