Initial commit

This commit is contained in:
2021-08-07 21:15:07 +02:00
commit 68ee4b37ed
91 changed files with 5387 additions and 0 deletions

View File

@@ -0,0 +1,174 @@
extends Control
enum CloseButtonPolicy {
SHOW_NEVER = 0,
SHOW_ACTIVE_ONLY = 1,
SHOW_ALWAYS = 2
}
enum TabAlign {
ALIGN_LEFT = 0,
ALIGN_CENTER = 1,
ALIGN_RIGHT = 2
}
export var current_tab := 0 setget _set_current_tab, _get_current_tab
export var drag_to_rearrange_enabled := false setget _set_drag_to_rearrange_enabled
export var drag_out_enabled := false
export var show_tabs := true setget _set_show_tabs
export(CloseButtonPolicy) var tab_close_display_policy := CloseButtonPolicy.SHOW_NEVER setget _set_tab_close_display_policy
export(TabAlign) var tab_align := TabAlign.ALIGN_CENTER setget _set_tab_align
var __mouse_was_inside := false
#############
# overrides #
#############
func _ready() -> void:
$tabs.connect("tab_changed", self, "_on_tabs_tab_changed")
$tabs.connect("tab_close", self, "_on_tabs_tab_close")
$tabs.connect("reposition_active_tab_request", self, "_on_tabs_reposition_active_tab_request")
$tabs.connect("gui_input", self, "_on_tabs_gui_input")
set_process(false) # only enabled for dragging
func _process(delta : float) -> void:
if !Input.is_mouse_button_pressed(BUTTON_LEFT):
__stop_tab_dragging()
return
var mouse_pos := get_viewport().get_mouse_position()
var mouse_is_inside : bool = $tabs.get_global_rect().has_point(mouse_pos)
if mouse_is_inside == __mouse_was_inside:
return
__mouse_was_inside = mouse_is_inside
if mouse_is_inside:
emit_signal("tab_dragged_in", _get_current_tab(), mouse_pos)
else:
emit_signal("tab_dragged_out", _get_current_tab(), mouse_pos)
################
# public stuff #
################
func add_tab(title : String, control : Control, icon : Texture = null) -> int:
$tabs.add_tab(title, icon)
$container.add_child(control)
var tab := control.get_position_in_parent()
emit_signal("tab_added", tab)
return tab
func move_tab(from : int, to : int) -> void:
$tabs.move_tab(from, to)
$container.move_child($container.get_child(from), to)
func remove_tab(tab : int) -> void:
if tab == current_tab && __is_tab_dragging():
__stop_tab_dragging()
$tabs.remove_tab(tab)
$container.remove_child($container.get_child(tab))
emit_signal("tab_removed", tab)
func find_tab(control : Control) -> int:
for tab in range(get_tab_count()):
if get_tab_control(tab) == control:
return tab
return -1
func remove_all_tabs() -> void:
while $tabs.get_tab_count() > 0:
$tabs.remove_tab(0)
while $container.get_child_count() > 0:
$container.remove_child($container.get_child(0))
func get_tab_control(tab : int) -> Control:
return $container.get_tab_control(tab)
func get_tab_title(tab : int) -> String:
return $tabs.get_tab_title(tab)
func get_tabs() -> Array:
return $container.get_children()
func get_tab_count() -> int:
return $container.get_child_count()
func get_tab_rect(tab : int) -> Rect2:
var rect : Rect2 = $tabs.get_tab_rect(tab)
return $tabs.get_transform().xform_inv(rect)
#################
# private stuff #
#################
func __handle_tabs_gui_input(event : InputEvent) -> void:
if drag_out_enabled \
&& event is InputEventMouseButton \
&& event.pressed \
&& event.button_index == BUTTON_LEFT:
yield(get_tree(), "idle_frame") # wait for the original control to handle the input
var tab_rect : Rect2 = $tabs.get_tab_rect(_get_current_tab())
if tab_rect.has_point(event.position):
__start_tab_dragging()
func __start_tab_dragging() -> void:
__mouse_was_inside = true
set_process(true)
func __stop_tab_dragging() -> void:
set_process(false)
func __is_tab_dragging() -> bool:
return is_processing()
###########
# setters #
###########
func _set_current_tab(tab : int) -> void:
current_tab = tab
$tabs.current_tab = current_tab
func _get_current_tab() -> int:
return $tabs.current_tab
func _set_drag_to_rearrange_enabled(enabled : bool) -> void:
drag_to_rearrange_enabled = enabled
$tabs.drag_to_rearrange_enabled = enabled
func _set_tab_close_display_policy(policy : int) -> void:
tab_close_display_policy = policy
$tabs.tab_close_display_policy = policy
func _set_tab_align(align : int) -> void:
tab_align = align
$tabs.tab_align = align
func _set_show_tabs(show : bool) -> void:
show_tabs = show
$tabs.visible = show
###################
# signal handlers #
###################
func _on_tabs_tab_changed(tab : int) -> void:
current_tab = tab
$container.current_tab = tab
func _on_tabs_tab_close(tab : int) -> void:
emit_signal("tab_close", tab)
func _on_tabs_reposition_active_tab_request(to : int) -> void:
$container.move_child($container.get_child(_get_current_tab()), to)
current_tab = to
func _on_tabs_gui_input(event : InputEvent) -> void:
__handle_tabs_gui_input(event)
###########
# signals #
###########
signal tab_added(tab)
signal tab_removed(tab)
signal tab_close(tab)
signal tab_dragged_out(tab, position)
signal tab_dragged_in(tab, position)

View File

@@ -0,0 +1,31 @@
tool
extends Control
onready var line_edit := $line_edit
onready var button := $button
onready var file_dialog := $file_dialog
export(String, FILE) var selected_file setget _set_selected_file
var file_filter = ""
func _ready():
line_edit.text = selected_file
func _set_selected_file(value : String):
if value != selected_file:
selected_file = value
if line_edit:
line_edit.text = selected_file
func _on_button_pressed():
file_dialog.clear_filters()
file_dialog.add_filter(file_filter)
file_dialog.filename = selected_file
file_dialog.popup_centered_ratio(0.5)
func _on_file_dialog_file_selected(path : String):
if path != selected_file:
_set_selected_file(path)
emit_signal("selected_file_changed")
signal selected_file_changed()

View File

@@ -0,0 +1,31 @@
tool
extends Control
onready var line_edit := $line_edit
onready var button := $button
onready var node_selection_dialog := $node_selection_dialog
export(NodePath) var selected_node setget _set_selected_node
var node_type_filter = Node
func _ready():
line_edit.text = str(selected_node)
func _set_selected_node(value : NodePath):
if value != selected_node:
selected_node = value
if line_edit:
line_edit.text = str(selected_node)
func _on_button_pressed():
node_selection_dialog.get_editor_node_tree().node_type_filter = node_type_filter
node_selection_dialog.get_editor_node_tree().selected_node = selected_node
node_selection_dialog.popup_centered_ratio(0.5)
func _on_node_selection_dialog_confirmed():
var tree = node_selection_dialog.get_editor_node_tree()
if tree.selected_node != selected_node:
_set_selected_node(tree.selected_node)
emit_signal("selected_node_changed")
signal selected_node_changed()

371
scenes/scripts/flexview.gd Normal file
View File

@@ -0,0 +1,371 @@
extends Control
enum SplitLocation {
TOP,
BOTTOM,
LEFT,
RIGHT
}
enum DropPosition {
TOP,
BOTTOM,
LEFT,
RIGHT,
CENTER
}
const FlexViewSplitter = preload("res://addons/de.mewin.gduibasics/scenes/scripts/flexview_splitter.gd")
const META_UNIQUE_TYPE = "__flexview_unique_type__"
const META_UNIQUE_KEY = "__flexview_unique_key__"
const META_VIEW_TITLE = "__flexview_title__"
const META_LAST_DROP_TARGET = "__flexview_last_drop_target__"
const GROUP_NAME = "__flexview__"
const INDICATOR_ANIM_DURATION = 0.1
const __HANDLERS_TO_COPY = ["view_added", "view_removed", "view_closing"]
onready var _rect_left := $drop_indicator/rect_left
onready var _rect_right := $drop_indicator/rect_right
onready var _rect_top := $drop_indicator/rect_top
onready var _rect_bottom := $drop_indicator/rect_bottom
onready var _rect_center := $drop_indicator/rect_center
onready var _drop_indicator := $drop_indicator/drop_indicator
onready var _tween := $drop_indicator/tween
var current_view := 0 setget _set_current_view, _get_current_view
export var show_tabs := true setget _set_show_tabs
var __drop_position := -1
#############
# overrides #
#############
func _ready() -> void:
set_process(false) # only used for the drop indicator
add_to_group(GROUP_NAME)
$tabs.show_tabs = show_tabs
func _process(delta : float) -> void:
var mouse_pos := get_viewport().get_mouse_position()
var drop_position : int = DropPosition.CENTER
if get_view_count() > 0: # only allow splitting if there is at least one view
var my_rect := get_global_rect()
var distances := [
(mouse_pos.y - my_rect.position.y) / my_rect.size.y,
(my_rect.position.y + my_rect.size.y - mouse_pos.y) / my_rect.size.y,
(mouse_pos.x - my_rect.position.x) / my_rect.size.x,
(my_rect.position.x + my_rect.size.x - mouse_pos.x) / my_rect.size.x
]
var idx := GDBMath.min_element(distances)
if distances[idx] < 0.25:
drop_position = idx
if __drop_position == drop_position:
return
__drop_position = drop_position
var new_rect := __get_drop_rect(drop_position)
_tween.stop(_drop_indicator, "rect_size")
_tween.stop(_drop_indicator, "rect_position")
_tween.interpolate_property(_drop_indicator, "rect_size", _drop_indicator.rect_size, new_rect.size, INDICATOR_ANIM_DURATION)
_tween.interpolate_property(_drop_indicator, "rect_position", _drop_indicator.rect_position, new_rect.position, INDICATOR_ANIM_DURATION)
_tween.start()
################
# public stuff #
################
func add_view(title : String, control : Control, make_current := true) -> int:
control.set_meta(META_VIEW_TITLE, title)
var view : int = $tabs.add_tab(get_view_title_from_control(control), control)
emit_signal("view_added", view, self)
if make_current:
_set_current_view(view)
# support for control-controlled (lel) titles
if control.has_signal("flexview_title_changed"):
control.connect("flexview_title_changed", self, "_on_flexview_title_changed")
if control.has_signal("flexview_close_me"):
control.connect("flexview_close_me", self, "_on_flexview_close_me")
return view
func remove_view(view : int) -> void:
var control := get_view_control(view)
if control:
GDBUtility.disconnect_all(control, self)
$tabs.remove_tab(view)
emit_signal("view_removed", view, self)
if get_view_count() == 0 && get_parent() is FlexViewSplitter:
self.queue_free()
func close_view(view : int) -> void:
emit_signal("view_closing", view, self)
func remove_all_views() -> void:
for view in range(get_view_count() - 1, -1, -1):
remove_view(view)
func find_view(control : Control) -> int:
return $tabs.find_tab(control)
func close_all_views() -> void:
for view in range(get_view_count() - 1, -1, -1):
close_view(view)
func set_view_title(view : int, title : String) -> void:
$tabs.set_tab_title(view, title)
func get_view_control(view : int) -> Control:
return $tabs.get_tab_control(view)
func get_view_title(view : int) -> String:
return $tabs.get_tab_title(view)
func get_views() -> Array:
return $tabs.get_tabs()
func get_view_count() -> int:
return $tabs.get_tab_count()
func split_view(placement : int, title : String, view : Control) -> Control:
var parent := get_parent()
var self_first : bool = (placement == SplitLocation.BOTTOM || placement == SplitLocation.RIGHT)
# add splitter
var splitter : SplitContainer
if placement == SplitLocation.TOP || placement == SplitLocation.BOTTOM:
splitter = preload("res://addons/de.mewin.gduibasics/scenes/flexview_vspliter.tscn").instance()
else:
splitter = preload("res://addons/de.mewin.gduibasics/scenes/flexview_hspliter.tscn").instance()
parent.add_child_below_node(self, splitter)
parent.remove_child(self)
GDBUIUtility.copy_size(splitter, self)
# add self first (if applicable)
if self_first:
splitter.add_child(self)
# create new flex container for other half
var new_container : Control = load("res://addons/de.mewin.gduibasics/scenes/flexview.tscn").instance()
new_container.add_view(title, view)
__copy_signal_handlers(new_container)
splitter.add_child(new_container)
# add self second (if applicable)
if !self_first:
splitter.add_child(self)
if placement == SplitLocation.TOP || placement == SplitLocation.BOTTOM:
splitter.split_offset = 0.5 * splitter.rect_size.y
else:
splitter.split_offset = 0.5 * splitter.rect_size.x
splitter.clamp_split_offset()
return new_container
################
# static stuff #
################
static func get_view_title_from_control(control : Control) -> String:
var prop = control.get("flexview_title")
if prop is String:
return prop
var title = control.get_meta(META_VIEW_TITLE)
if title is String:
return title
return control.name
static func get_all_flex_views() -> Array:
return GDBUtility.get_scene_tree().get_nodes_in_group(GROUP_NAME)
static func get_all_views() -> Array:
var views := []
for flex_view in get_all_flex_views():
views.append_array(flex_view.get_views())
return views
static func get_top_flex_view() -> Control:
# can't use GDBUtility.find_node_by_type as we cannot use our own type
var all_views := get_all_flex_views()
var top_view : Control = null
for flex_view in all_views:
if top_view == null || top_view.is_greater_than(flex_view):
top_view = flex_view
return top_view
static func add_view_anywhere(title : String, view : Control, make_current := true) -> Control:
var flex_view := _get_last_drop_target()
if flex_view == null:
flex_view = get_top_flex_view()
if flex_view != null:
flex_view.add_view(title, view, make_current)
return flex_view
static func add_unique_view_anywhere(title : String, view : Control, type : String, key, make_current := true) -> Control:
var flex_view := add_view_anywhere(title, view, make_current)
if flex_view == null: # failed
return null
view.set_meta(META_UNIQUE_TYPE, type)
view.set_meta(META_UNIQUE_KEY, key)
return flex_view
static func remove_all_views_anywhere() -> void:
for flex_view in get_all_flex_views():
flex_view.remove_all_views()
static func find_unique_view(type : String, key) -> Control:
for view_ctrl in get_all_views():
if view_ctrl.has_meta(META_UNIQUE_TYPE) && view_ctrl.has_meta(META_UNIQUE_KEY)\
&& view_ctrl.get_meta(META_UNIQUE_TYPE) == type && view_ctrl.get_meta(META_UNIQUE_KEY) == key:
return view_ctrl
return null
static func find_flex_view(view_control : Control) -> Control:
var my_script : Script = load("res://addons/de.mewin.gduibasics/scripts/types/controls/flexview.gd")
return GDBUtility.find_parent_with_type(view_control, my_script) as Control
static func make_view_current(view_control : Control) -> void:
var flex_view := find_flex_view(view_control)
if flex_view:
flex_view.current_view = view_control.get_position_in_parent()
static func _set_last_drop_target(flex_view : Control) -> void:
flex_view.get_tree().set_meta(META_LAST_DROP_TARGET, flex_view.get_path())
static func _get_last_drop_target() -> Control:
var tree := GDBUtility.get_scene_tree()
if tree.has_meta(META_LAST_DROP_TARGET):
var flex_view_path := tree.get_meta(META_LAST_DROP_TARGET) as NodePath
if flex_view_path:
var flex_view := tree.root.get_node_or_null(flex_view_path) as Control
return flex_view
return null
#################
# private stuff #
#################
func _start_dropping() -> void:
__drop_position = -1
_drop_indicator.visible = true
_tween.stop_all()
_tween.interpolate_property(_drop_indicator, "modulate", Color.transparent, Color.white, INDICATOR_ANIM_DURATION)
_tween.start()
set_process(true)
func _stop_dropping() -> void:
_tween.stop_all()
_tween.interpolate_property(_drop_indicator, "modulate", Color.white, Color.transparent, INDICATOR_ANIM_DURATION)
_tween.start()
set_process(false)
yield(get_tree().create_timer(INDICATOR_ANIM_DURATION), "timeout")
if !is_processing(): # mOybe we are dragging again
_drop_indicator.visible = false
func _drop(view : Control) -> void:
_stop_dropping()
var title := get_view_title_from_control(view)
if __drop_position != DropPosition.CENTER:
var new_container := split_view(__drop_position, title, view)
_set_last_drop_target(new_container)
else:
add_view(title, view)
_set_last_drop_target(self)
func __make_floating_tab(tab : int) -> void:
var window : WindowDialog = load("res://addons/de.mewin.gduibasics/scenes/flexview_window.tscn").instance()
get_tree().root.add_child(window)
# remove tab
var tab_ctrl : Control = $tabs.get_tab_control(tab)
remove_view(tab)
# add to new window
var title = get_view_title_from_control(tab_ctrl)
window.flex_view.add_view(title, tab_ctrl)
__copy_signal_handlers(window.flex_view)
# place window
var vp_size := get_viewport().size
var mouse_pos := get_viewport().get_mouse_position()
var win_size := vp_size * Vector2(0.5, 0.5)
var min_size := window.get_combined_minimum_size()
var title_height := window.get_constant("title_height")
window.rect_size = Vector2(max(win_size.x, min_size.x), max(win_size.y, min_size.y))
window.rect_position = mouse_pos - Vector2(0.5 * window.rect_size.x, -0.5 * title_height)
window.visible = true
# emulate mouse down for dragging the window
var event := InputEventMouseButton.new()
event.button_index = BUTTON_LEFT
event.pressed = true
event.button_mask = BUTTON_MASK_LEFT
event.position = mouse_pos
event.global_position = mouse_pos
Input.parse_input_event(event)
func __get_drop_rect(drop_position : int) -> Rect2:
var full_rect := Rect2(Vector2(), rect_size)
match drop_position:
DropPosition.TOP:
return full_rect.grow_individual(0.0, 0.0, 0.0, -0.5 * full_rect.size.y)
DropPosition.BOTTOM:
return full_rect.grow_individual(0.0, -0.5 * full_rect.size.y, 0.0, 0.0)
DropPosition.LEFT:
return full_rect.grow_individual(0.0, 0.0, -0.5 * full_rect.size.x, 0.0)
DropPosition.RIGHT:
return full_rect.grow_individual(-0.5 * full_rect.size.x, 0.0, 0.0, 0.0)
return full_rect
func __copy_signal_handlers(other_view : Control) -> void:
GDBUtility.copy_all_signal_handlers(other_view, self, __HANDLERS_TO_COPY)
#####################
# setters & getters #
#####################
func _set_current_view(val : int) -> void:
$tabs.current_tab = val
emit_signal("current_view_changed", self)
func _get_current_view() -> int:
return $tabs.current_tab
func _set_show_tabs(show : bool) -> void:
show_tabs = show
if $tabs:
$tabs.show_tabs = show
###################
# signal handlers #
###################
func _on_tabs_tab_dragged_out(tab : int, position : Vector2) -> void:
__make_floating_tab(tab)
func _on_tabs_tab_close(tab : int):
close_view(tab)
func _on_flexview_title_changed(control : Control) -> void:
var view := find_view(control)
var title = control.get("flexview_title")
if view >= 0 && title is String:
set_view_title(view, title)
func _on_flexview_close_me(control : Control) -> void:
var view := find_view(control)
if view >= 0:
remove_view(view)
###########
# signals #
###########
signal view_added(view, flex_view)
signal view_removed(view, flex_view)
signal view_closing(view, flex_view)
signal current_view_changed(flex_view)

View File

@@ -0,0 +1,38 @@
extends SplitContainer
#############
# overrides #
#############
func add_child(child : Node, legible_unique_name := false) -> void:
.add_child(child, legible_unique_name)
child.connect("tree_exited", self, "_on_child_tree_exited")
#################
# private stuff #
#################
func __try_collapse() -> void:
var count := get_child_count()
if count == 0: # nothing left? I can go now
self.queue_free()
return
elif count > 1: # more than one? I have to stay
return
# exactly one? replace me with them
var child := get_child(0)
remove_child(child)
get_parent().add_child_below_node(self, child)
GDBUIUtility.copy_size(child, self)
self.queue_free()
###################
# signal handlers #
###################
func _on_child_tree_exited() -> void:
if !get_tree():
return
yield(get_tree(), "idle_frame") # wait until child is actually gone
__try_collapse()

View File

@@ -0,0 +1,141 @@
extends WindowDialog
const FlexView = preload("res://addons/de.mewin.gduibasics/scenes/scripts/flexview.gd")
var flex_view : FlexView setget _set_flex_view, _get_flex_view
var __drop_target : FlexView = null
var __is_dragging := false
#############
# overrides #
#############
func _ready() -> void:
set_process(false) # only used to stop dropping when mouse button is lifted
__update_title()
func _process(delta : float) -> void:
if !Input.is_mouse_button_pressed(BUTTON_LEFT):
__drop()
func _input(event : InputEvent) -> void:
if event is InputEventKey && event.scancode == KEY_SHIFT && __is_dragging:
__update_dragging()
#################
# private stuff #
#################
func __update_view_count() -> void:
var child := __get_flex_child()
if !(child is FlexView):
return
var count : int = child.get_view_count()
if count == 0:
self.queue_free()
return
else:
# child.show_tabs = (count > 1)
pass
func __update_dragging() -> void:
var child := __get_flex_child()
if !(child is FlexView) || child.get_view_count() > 1:
set_process(false)
__is_dragging = false
return
__is_dragging = true
set_process(true)
# dragging with shift disables dropping
if Input.is_key_pressed(KEY_SHIFT):
__set_drop_target(null)
return
var mouse_pos := get_viewport().get_mouse_position()
var flex_views := get_tree().get_nodes_in_group(FlexView.GROUP_NAME)
var views_under_cursor := []
for view in flex_views:
if !self.is_a_parent_of(view) && view.visible && view.get_global_rect().has_point(mouse_pos):
views_under_cursor.append(view)
if views_under_cursor.empty():
__set_drop_target(null)
return
# find first in tree
var target : FlexView = views_under_cursor[0]
for i in range(1, views_under_cursor.size()):
var node : FlexView = views_under_cursor[i]
if GDBUtility.control_is_in_front_of(node, target):
target = node
__set_drop_target(target)
func __set_drop_target(target : FlexView) -> void:
if target == __drop_target:
return
set_process(is_instance_valid(__drop_target)) # process to check if mouse button is lifted
if is_instance_valid(__drop_target):
__drop_target._stop_dropping()
__drop_target = target
if is_instance_valid(__drop_target):
__drop_target._start_dropping()
func __drop() -> void:
var child := __get_flex_child()
set_process(false)
__is_dragging = false
if !is_instance_valid(__drop_target) || !(child is FlexView) || child.get_view_count() != 1:
return
var control : Control = child.get_view_control(0)
child.remove_view(0)
__drop_target._drop(control)
self.queue_free()
func __update_title() -> void:
var child := __get_flex_child()
if !(child is FlexView):
window_title = ""
elif child.get_view_count() > 0:
window_title = child.get_view_title(child.current_view)
func __get_flex_child() -> Control:
return get_child(1) as Control # TODO: something better (and safer) than a fixed index
func __close_requested() -> void:
for flex_view in GDBUtility.find_nodes_by_type(self, FlexView):
flex_view.close_all_views()
#####################
# setters & getters #
#####################
func _set_flex_view(val) -> void:
assert(0, "Cannot set this.")
func _get_flex_view() -> FlexView:
return $flex_view as FlexView
###################
# signal handlers #
###################
func _on_flex_view_view_added(view : int, flex_view : FlexView) -> void:
__update_view_count()
func _on_flex_view_view_removed(view : int, flex_view : FlexView) -> void:
__update_view_count()
func _on_flexview_window_item_rect_changed() -> void:
__update_dragging()
func _on_flex_view_current_view_changed(flex_view : FlexView):
__update_title()
func _on_flexview_window_visibility_changed():
if !visible:
__close_requested()
visible = true

View File

@@ -0,0 +1,100 @@
extends Popup
enum Position {
ABOVE,
BELOW
}
var line_edit : LineEdit setget _set_line_edit
export(Position) var position = Position.BELOW
#############
# overrides #
#############
func _ready():
call_deferred("__setup")
#################
# private stuff #
#################
func __setup():
__connect()
func __connect():
if !line_edit:
return
line_edit.connect("focus_entered", self, "_on_line_edit_focus_entered")
line_edit.connect("focus_exited", self, "_on_line_edit_focus_exited")
line_edit.connect("item_rect_changed", self, "_on_line_edit_item_rect_changed")
line_edit.connect("resized", self, "_on_line_edit_resized")
func __disconnect():
if !line_edit:
return
line_edit.disconnect("focus_entered", self, "_on_line_edit_focus_entered")
line_edit.disconnect("focus_exited", self, "_on_line_edit_focus_exited")
line_edit.disconnect("item_rect_changed", self, "_on_line_edit_item_rect_changed")
line_edit.disconnect("resized", self, "_on_line_edit_resized")
func __calc_rect() -> Rect2:
var position_ = position
var pos := line_edit.rect_global_position
var vp_pos = get_viewport_rect().position
var vp_size = get_viewport_rect().size
var size := rect_min_size
var le_size := line_edit.rect_size
var max_height : float
if pos.y + size.y > vp_size.y:
position_ = Position.ABOVE
elif pos.y < vp_pos.y:
position_ = Position.BELOW
if position_ == Position.ABOVE:
max_height = pos.y - vp_pos.y
else:
max_height = (vp_pos.y + vp_size.y) - (pos.y + size.y)
var res_size = Vector2(le_size.x, 100.0)
if position_ == Position.ABOVE:
return Rect2(pos - Vector2(0.0, res_size.y), res_size)
else:
return Rect2(pos + Vector2(0.0, size.y), res_size)
func __update():
var should_be_visible = line_edit.has_focus()
if !should_be_visible:
hide()
else:
var rect := __calc_rect()
if visible:
rect_position = rect.position
rect_size = rect.size
else:
popup(rect)
############
# handlers #
############
func _on_line_edit_focus_entered():
__update()
func _on_line_edit_focus_exited():
hide()
func _on_line_edit_item_rect_changed():
__update()
func _on_line_edit_resized():
__update()
###########
# setters #
###########
func _set_line_edit(value : LineEdit):
if value != line_edit:
__disconnect()
line_edit = value
__connect()

View File

@@ -0,0 +1,47 @@
tool
extends ConfirmationDialog
export(bool) var allow_empty := true setget _set_allow_empty
################
# public stuff #
################
func get_line_edit() -> LineEdit:
return $container/line_edit as LineEdit
#################
# private stuff #
#################
func __update():
var valid := true
if !allow_empty && !$container/line_edit.text:
valid = false
get_ok().disabled = !valid
###########
# setters #
###########
func _set_allow_empty(value : bool):
if value != allow_empty:
allow_empty = value
__update()
############
# handlers #
############
func _on_line_input_dialog_about_to_show():
__update()
func _on_line_edit_text_changed(new_text : String):
__update()
func _on_line_edit_text_entered(new_text):
if !get_ok().disabled:
visible = false
emit_signal("confirmed")
func _on_line_input_dialog_visibility_changed():
if visible:
yield(get_tree(), "idle_frame")
$container/line_edit.grab_focus()
$container/line_edit.caret_position = $container/line_edit.text.length()

View File

@@ -0,0 +1,5 @@
tool
extends ConfirmationDialog
func get_editor_node_tree() -> UIB_EditorNodeTree:
return $tree as UIB_EditorNodeTree

View File

@@ -0,0 +1,174 @@
extends PopupPanel
class_name SelectorPopup
class _Item:
var label : String
var icon : Texture
var metadata
var current_list_index := -1
func _init(label_ : String, icon_ : Texture, metadata_):
self.label = label_
self.icon = icon_
self.metadata = metadata_
export var match_case := false
onready var _ledit_search : LineEdit = GDBUtility.find_node_by_name(self, "ledit_search")
onready var _ilist_content : ItemList = GDBUtility.find_node_by_name(self, "ilist_content")
var _items := []
var __needs_update := false
var __caret := 0
var __submitted := false
################
# overridables #
################
func _should_show(item : _Item) -> bool:
var search_text := _ledit_search.text.strip_edges()
if search_text == "":
return true
var item_text := item.label
if !match_case:
search_text = search_text.to_lower()
item_text = item_text.to_lower()
return search_text in item_text
################
# public stuff #
################
func add_item(label : String, icon : Texture = null, metadata = null) -> void:
_items.append(_Item.new(label, icon, metadata))
invalidate()
func clear_items() -> void:
_items.clear()
invalidate()
func get_item_label(idx : int) -> String:
if idx < 0 || idx >= _items.size():
return ""
return _items[idx].label
func get_item_metadata(idx : int):
if idx < 0 || idx >= _items.size():
return null
return _items[idx].metadata
func invalidate() -> void:
# only updates once if multiple calls are done in a single frame
__needs_update = true
yield(get_tree(), "idle_frame")
if __needs_update:
__needs_update = false
__update()
#################
# private stuff #
#################
func __update() -> void:
var idx_increment := 0
var insert_index := 0
for item in self._items:
var should_show := self._should_show(item)
var is_visible : bool = (item.current_list_index > -1)
if is_visible:
item.current_list_index += idx_increment
if should_show && !is_visible:
self.__add_item(item, insert_index)
item.current_list_index = insert_index
idx_increment += 1
elif !should_show && is_visible:
_ilist_content.remove_item(item.current_list_index)
item.current_list_index = -1
idx_increment -= 1
if should_show:
insert_index += 1
if !_ilist_content.is_anything_selected() && _ilist_content.get_item_count() > 0:
__select(0)
func __add_item(item : _Item, insert_index : int) -> void:
var new_idx := _ilist_content.get_item_count()
_ilist_content.add_item(item.label, item.icon)
_ilist_content.set_item_metadata(new_idx, item)
if insert_index != new_idx:
_ilist_content.move_item(new_idx, insert_index)
func __select(idx : int) -> void:
_ilist_content.select(idx)
_ilist_content.ensure_current_is_visible()
func __select_next() -> void:
var selected := _ilist_content.get_selected_items()
var idx := 0
if !selected.empty():
idx = selected[0] + 1
if idx >= _ilist_content.get_item_count():
return
__select(idx)
func __select_prev() -> void:
var selected := _ilist_content.get_selected_items()
var idx := 0
if !selected.empty():
idx = selected[0] - 1
if idx < 0:
return
__select(idx)
func __item_from_list_index(idx : int) -> _Item:
return _ilist_content.get_item_metadata(idx) as _Item
func __item_index(itm : _Item) -> int:
return _items.find(itm)
###################
# signal handlers #
###################
func _on_selector_popup_about_to_show() -> void:
__submitted = false
_ledit_search.clear()
_ledit_search.grab_focus()
_ilist_content.unselect_all()
__update()
func _on_ledit_search_text_changed(new_text : String) -> void:
__update()
func _on_ledit_search_gui_input(event : InputEvent) -> void:
if !event.is_pressed():
return
if event.is_action("ui_down"):
__select_next()
elif event.is_action("ui_up"):
__select_prev()
else:
__caret = _ledit_search.caret_position
return
_ledit_search.caret_position = __caret
func _on_ledit_search_text_entered(new_text : String) -> void:
var selected := _ilist_content.get_selected_items()
if selected.empty():
return
var idx := __item_index(__item_from_list_index(selected[0]))
__submitted = true
emit_signal("selected", idx)
hide()
func _on_selector_popup_popup_hide() -> void:
if !__submitted:
emit_signal("selected", -1)
###########
# signals #
###########
signal selected(index)

View File

@@ -0,0 +1,77 @@
tool
extends Control
enum Type {
STRING,
INT,
REAL
}
onready var edt_text := $edt_text
onready var spin_number := $spin_number
export var value = "" setget _set_value
export(Type) var type = Type.STRING setget _set_type
func _ready():
__setup_type()
func __setup_type():
match type:
Type.STRING:
edt_text.visible = true
Type.INT, Type.REAL:
spin_number.visible = true
_set_value(convert(value, __type_id()))
__fill_value()
emit_signal("value_changed")
func __fill_value():
match type:
Type.STRING:
edt_text.text = str(value)
Type.INT, Type.REAL:
spin_number.value = float(value)
func __type_id() -> int:
match type:
Type.STRING:
return TYPE_STRING
Type.INT:
return TYPE_INT
Type.REAL:
return TYPE_REAL
_:
assert(false)
return TYPE_STRING
func _set_value(val):
val = convert(val, __type_id())
if typeof(value) != __type_id() || value != val:
value = val
__fill_value()
func _set_type(value : int):
if value != type:
type = value
for child in get_children():
child.visible = false
__setup_type()
func _on_edt_text_text_changed(new_text : String):
assert(type == Type.STRING)
if !value is String || value != new_text:
value = new_text
emit_signal("value_changed")
func _on_spin_number_value_changed(value_ : float):
assert(type == Type.INT || type == Type.REAL)
if type == Type.INT:
value_ = int(value_)
if typeof(value) != __type_id() || value != value_:
value = value_
emit_signal("value_changed")
signal value_changed()