372 lines
12 KiB
GDScript
372 lines
12 KiB
GDScript
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)
|