222 lines
6.5 KiB
GDScript
222 lines
6.5 KiB
GDScript
class_name TreePanel
|
|
extends Control
|
|
## Дерево дисков и папок: светлая поверхность, строки 24 px, скролл 14 px,
|
|
## кнопка «Новая папка» и контекстное меню по правой кнопке.
|
|
|
|
signal folder_selected(folder_id: String)
|
|
signal file_dropped(folder_id: String, payload: Dictionary)
|
|
signal context_menu_requested(folder_id: String, global_pos: Vector2)
|
|
signal new_folder_requested
|
|
|
|
var _view: Control
|
|
var _content: Control
|
|
var _bar: ScrollBar90
|
|
var _new_btn: Button90
|
|
var _rows: Array[Dictionary] = []
|
|
var _selected := ""
|
|
|
|
|
|
func _ready() -> void:
|
|
mouse_filter = Control.MOUSE_FILTER_STOP
|
|
_view = Control.new()
|
|
_view.name = "View"
|
|
_view.mouse_filter = Control.MOUSE_FILTER_PASS
|
|
_view.clip_contents = true
|
|
add_child(_view)
|
|
|
|
_content = Control.new()
|
|
_content.name = "Content"
|
|
_content.mouse_filter = Control.MOUSE_FILTER_PASS
|
|
_view.add_child(_content)
|
|
|
|
_bar = ScrollBar90.new()
|
|
_bar.name = "Scroll"
|
|
add_child(_bar)
|
|
_bar.value_changed.connect(_on_scroll)
|
|
|
|
_new_btn = Button90.new()
|
|
_new_btn.name = "NewFolderButton"
|
|
_new_btn.caption = "НОВАЯ ПАПКА"
|
|
_new_btn.icon = Button90.Icon.PLUS
|
|
_new_btn.caption_size = Theme90.FS_BODY
|
|
add_child(_new_btn)
|
|
_new_btn.pressed.connect(func() -> void: new_folder_requested.emit())
|
|
|
|
resized.connect(_relayout)
|
|
_relayout()
|
|
|
|
|
|
func _view_rect() -> Rect2:
|
|
return Rect2(2, 2, maxf(0.0, size.x - 18.0), maxf(0.0, size.y - 48.0))
|
|
|
|
|
|
func _footer_rect() -> Rect2:
|
|
return Rect2(2, size.y - 44.0, maxf(0.0, size.x - 4.0), 14.0)
|
|
|
|
|
|
func _relayout() -> void:
|
|
if _view == null:
|
|
return
|
|
var vr := _view_rect()
|
|
_view.position = vr.position
|
|
_view.size = vr.size
|
|
_bar.position = Vector2(size.x - 16.0, vr.position.y + 4.0)
|
|
_bar.size = Vector2(Theme90.SCROLLBAR, maxf(8.0, vr.size.y - 8.0))
|
|
_bar.set_metrics(vr.size.y, maxf(vr.size.y, _content_height()))
|
|
_new_btn.position = Vector2(4, size.y - 27.0)
|
|
_new_btn.size = Vector2(size.x - 8.0, 24.0)
|
|
_apply_layout()
|
|
queue_redraw()
|
|
|
|
|
|
func _content_height() -> float:
|
|
var y := 0.0
|
|
for e in _rows:
|
|
var row: FolderRow = e["row"]
|
|
var vis: bool = e["visible"]
|
|
if vis:
|
|
y += row.custom_minimum_size.y
|
|
return maxf(y + 2.0, 0.0)
|
|
|
|
|
|
## sections: [{id, name, free, folders: [{id, name, count, capacity, locked}]}]
|
|
func set_data(sections: Array) -> void:
|
|
for c in _content.get_children():
|
|
c.queue_free()
|
|
_rows.clear()
|
|
for sec in sections:
|
|
var sid: String = str(sec.get("id", ""))
|
|
var srow := FolderRow.new()
|
|
srow.setup(sid, str(sec.get("name", "")), 0, true)
|
|
srow.free_text = str(sec.get("free", ""))
|
|
srow.custom_minimum_size = Vector2(0, FolderRow.SECTION_H)
|
|
srow.expanded = true
|
|
srow.expansion_toggled.connect(_on_toggle)
|
|
srow.folder_selected.connect(_on_section_clicked)
|
|
srow.context_menu_requested.connect(_on_context)
|
|
_content.add_child(srow)
|
|
_rows.append({"row": srow, "section": sid, "visible": true})
|
|
for fol in sec.get("folders", []):
|
|
var frow := FolderRow.new()
|
|
var fid: String = str(fol.get("id", ""))
|
|
frow.setup(fid, str(fol.get("name", "")), 1, false)
|
|
frow.count = int(fol.get("count", 0))
|
|
frow.capacity = int(fol.get("capacity", 16))
|
|
frow.tooltip_text = "Папка заполнена" if frow.count >= frow.capacity \
|
|
else "%d из %d объектов" % [frow.count, frow.capacity]
|
|
frow.locked = bool(fol.get("locked", false))
|
|
frow.custom_minimum_size = Vector2(0, FolderRow.ROW_H)
|
|
frow.folder_selected.connect(_on_folder_clicked)
|
|
frow.expansion_toggled.connect(_on_toggle)
|
|
frow.file_dropped.connect(_on_drop)
|
|
frow.context_menu_requested.connect(_on_context)
|
|
_content.add_child(frow)
|
|
_rows.append({"row": frow, "section": sid, "visible": true})
|
|
_apply_layout()
|
|
_relayout()
|
|
if not _selected.is_empty():
|
|
select_folder(_selected)
|
|
|
|
|
|
func _apply_layout() -> void:
|
|
var y := 0.0
|
|
var section_open := true
|
|
var w := _view.size.x if _view != null else size.x
|
|
for e in _rows:
|
|
var row: FolderRow = e["row"]
|
|
if row.is_section:
|
|
section_open = row.expanded
|
|
e["visible"] = true
|
|
else:
|
|
e["visible"] = section_open
|
|
row.visible = bool(e["visible"])
|
|
if row.visible:
|
|
row.position = Vector2(0, y)
|
|
row.size = Vector2(w, row.custom_minimum_size.y)
|
|
y += row.custom_minimum_size.y
|
|
if _bar != null and _view != null:
|
|
_bar.set_metrics(_view.size.y, maxf(_view.size.y, y + 2.0))
|
|
if _bar.value > _bar.max_value - _bar.page:
|
|
_content.position.y = -_bar.value
|
|
|
|
|
|
func _on_scroll(v: float) -> void:
|
|
if _content == null:
|
|
return
|
|
_content.position = Vector2(0, -v)
|
|
|
|
|
|
func _on_folder_clicked(folder_id: String) -> void:
|
|
select_folder(folder_id)
|
|
folder_selected.emit(folder_id)
|
|
|
|
|
|
func _on_section_clicked(_section_id: String) -> void:
|
|
pass
|
|
|
|
|
|
func select_folder(folder_id: String) -> void:
|
|
_selected = folder_id
|
|
for e in _rows:
|
|
var row: FolderRow = e["row"]
|
|
if not row.is_section:
|
|
row.selected = row.folder_id == folder_id
|
|
|
|
|
|
func update_count(folder_id: String, count: int) -> void:
|
|
for e in _rows:
|
|
var row: FolderRow = e["row"]
|
|
if not row.is_section and row.folder_id == folder_id:
|
|
row.count = count
|
|
row.queue_redraw()
|
|
|
|
|
|
func _on_toggle(folder_id: String) -> void:
|
|
for e in _rows:
|
|
var row: FolderRow = e["row"]
|
|
if row.folder_id == folder_id and row.is_section:
|
|
row.expanded = not row.expanded
|
|
break
|
|
_apply_layout()
|
|
_relayout()
|
|
|
|
|
|
func _on_drop(folder_id: String, payload: Dictionary) -> void:
|
|
file_dropped.emit(folder_id, payload)
|
|
|
|
|
|
func _on_context(folder_id: String, global_pos: Vector2) -> void:
|
|
context_menu_requested.emit(folder_id, global_pos)
|
|
|
|
|
|
func _gui_input(event: InputEvent) -> void:
|
|
if event is InputEventMouseButton and event.pressed:
|
|
if event.button_index == MOUSE_BUTTON_WHEEL_UP:
|
|
_bar.value -= 32.0
|
|
accept_event()
|
|
elif event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
|
|
_bar.value += 32.0
|
|
accept_event()
|
|
|
|
|
|
func _draw() -> void:
|
|
var vr := _view_rect()
|
|
var fr := _footer_rect()
|
|
var f := Theme90.ui()
|
|
draw_style_box(Theme90.box(Theme90.PAPER_HI, Theme90.BORDER_SOFT, 3, 1), vr)
|
|
|
|
# подвал: одна спокойная строка вместо двух «счётчиков»
|
|
var folders := 0
|
|
var objs := 0
|
|
for e in _rows:
|
|
var row: FolderRow = e["row"]
|
|
if not row.is_section:
|
|
folders += 1
|
|
objs += row.count
|
|
Theme90.text(self, f, Vector2(fr.position.x + 6, fr.position.y + 1),
|
|
"%d папок · %d объектов" % [folders, objs], 10, Theme90.SHADE, fr.size.x - 120)
|
|
var free := "8,5 МБ"
|
|
var fw := f.get_string_size(free, HORIZONTAL_ALIGNMENT_LEFT, -1, 10).x
|
|
Theme90.text(self, f, Vector2(fr.end.x - fw - 6, fr.position.y + 1), free, 10,
|
|
Theme90.SHADE)
|