171 lines
5.3 KiB
GDScript
171 lines
5.3 KiB
GDScript
class_name FolderRow
|
|
extends Control
|
|
## Строка дерева 24 px: мягкий шеврон, папка, имя, счётчик «3/16».
|
|
## Выделение — светлая teal-подложка с тонкой полоской слева, без инверсии.
|
|
## Секции C:/D:/E: — спокойная строка с волосяной линией снизу.
|
|
|
|
signal folder_selected(folder_id: String)
|
|
signal expansion_toggled(folder_id: String)
|
|
signal file_dropped(folder_id: String, payload: Dictionary)
|
|
signal context_menu_requested(folder_id: String, global_pos: Vector2)
|
|
|
|
const ROW_H := 24.0
|
|
const SECTION_H := 22.0
|
|
|
|
var folder_id := ""
|
|
var folder_name := ""
|
|
var count := 0
|
|
var capacity := 16
|
|
var free_text := ""
|
|
var depth := 0
|
|
var is_section := false
|
|
var expandable := true
|
|
var expanded := true:
|
|
set(v):
|
|
expanded = v
|
|
queue_redraw()
|
|
var selected := false:
|
|
set(v):
|
|
selected = v
|
|
queue_redraw()
|
|
var hovered := false:
|
|
set(v):
|
|
hovered = v
|
|
queue_redraw()
|
|
var drop_active := false:
|
|
set(v):
|
|
if drop_active != v:
|
|
drop_active = v
|
|
queue_redraw()
|
|
var locked := false
|
|
|
|
|
|
func _ready() -> void:
|
|
mouse_filter = Control.MOUSE_FILTER_PASS
|
|
mouse_default_cursor_shape = Control.CURSOR_ARROW
|
|
custom_minimum_size = Vector2(0, SECTION_H if is_section else ROW_H)
|
|
if size.y == 0.0:
|
|
size.y = custom_minimum_size.y
|
|
mouse_entered.connect(func() -> void: hovered = true)
|
|
mouse_exited.connect(func() -> void: hovered = false)
|
|
set_process(false)
|
|
|
|
|
|
func setup(p_id: String, p_name: String, p_depth: int, p_section: bool = false) -> void:
|
|
folder_id = p_id
|
|
folder_name = p_name
|
|
depth = p_depth
|
|
is_section = p_section
|
|
custom_minimum_size.y = SECTION_H if p_section else ROW_H
|
|
if size.y < custom_minimum_size.y:
|
|
size.y = custom_minimum_size.y
|
|
queue_redraw()
|
|
|
|
|
|
func _draw() -> void:
|
|
var r := Rect2(Vector2.ZERO, size)
|
|
var f := Theme90.ui()
|
|
if is_section:
|
|
_draw_section(r, f)
|
|
return
|
|
|
|
if selected:
|
|
draw_style_box(Theme90.box(Theme90.ACCENT_PALE, Color(0, 0, 0, 0), 3, 0),
|
|
Rect2(2, 1, size.x - 4, size.y - 2))
|
|
draw_rect(Rect2(2, 5, 2, size.y - 10), Theme90.ACCENT)
|
|
elif drop_active:
|
|
draw_style_box(Theme90.box(Theme90.ACCENT_PALE, Theme90.ACCENT, 3, 1),
|
|
Rect2(2, 1, size.x - 4, size.y - 2))
|
|
elif hovered:
|
|
draw_rect(Rect2(2, 1, size.x - 4, size.y - 2), Color(1, 1, 1, 0.45))
|
|
|
|
var fg := Theme90.NAVY if selected else Theme90.INK
|
|
var sub := Theme90.ACCENT if selected else Theme90.SHADE
|
|
var sx := 5.0 + depth * 12.0
|
|
|
|
# тонкий шеврон вместо объёмного квадрата
|
|
if expandable:
|
|
Theme90.draw_chevron(self, Vector2(sx + 6, size.y * 0.5), 1 if expanded else 2,
|
|
sub, 3.0, 1.3)
|
|
else:
|
|
draw_circle(Vector2(sx + 6, size.y * 0.5), 1.2, sub)
|
|
|
|
# иконка папки
|
|
var icon_x := sx + 15.0
|
|
Theme90.draw_folder_icon(self, Vector2(icon_x, 7),
|
|
Theme90.ACCENT if not locked else Theme90.SHADE, expanded and not is_section)
|
|
|
|
# имя
|
|
var tx := icon_x + 17.0
|
|
var name_w := maxf(20.0, size.x - tx - 56.0)
|
|
var label := Theme90.fit(f, folder_name, 12, name_w)
|
|
Theme90.text_vcenter(self, f, Rect2(tx, 0, name_w, size.y), label, 12, fg)
|
|
|
|
# счётчик — спокойные цифры без «логотипного» шрифта
|
|
if capacity > 0:
|
|
var cnt := "%d/%d" % [count, capacity]
|
|
var cw := f.get_string_size(cnt, HORIZONTAL_ALIGNMENT_LEFT, -1, 10).x
|
|
Theme90.text_vcenter(self, f, Rect2(size.x - cw - 8.0, 0, cw + 2, size.y),
|
|
cnt, 10, sub)
|
|
|
|
if locked:
|
|
Theme90.draw_lock_icon(self, Vector2(size.x - 58, 8), Theme90.SHADE)
|
|
|
|
|
|
func _draw_section(r: Rect2, f: Font) -> void:
|
|
if hovered:
|
|
draw_rect(r, Color(1, 1, 1, 0.35))
|
|
draw_rect(Rect2(2, size.y - 1, maxf(0.0, size.x - 4), 1), Theme90.HAIRLINE)
|
|
var sx := 4.0
|
|
if expandable:
|
|
Theme90.draw_chevron(self, Vector2(sx + 6, size.y * 0.5), 1 if expanded else 2,
|
|
Theme90.NAVY, 3.0, 1.3)
|
|
Theme90.draw_drive_icon(self, Vector2(sx + 16, 6), Theme90.NAVY)
|
|
var fg := Theme90.NAVY if selected else Theme90.INK
|
|
Theme90.text_vcenter(self, f, Rect2(sx + 32, 0, size.x - 80, size.y),
|
|
Theme90.fit(f, folder_name, 11, size.x - 110), 11, fg)
|
|
if not free_text.is_empty():
|
|
var tw := f.get_string_size(free_text, HORIZONTAL_ALIGNMENT_LEFT, -1, 10).x
|
|
Theme90.text(self, f, Vector2(size.x - tw - 8, 6), free_text, 10, Theme90.SHADE)
|
|
|
|
|
|
func _gui_input(event: InputEvent) -> void:
|
|
if event is InputEventMouseButton and event.pressed:
|
|
if event.button_index == MOUSE_BUTTON_LEFT:
|
|
var sx := 5.0 + (0.0 if is_section else depth * 12.0)
|
|
if expandable and Rect2(sx, 5, 12, 12).has_point(event.position):
|
|
expanded = not expanded
|
|
expansion_toggled.emit(folder_id)
|
|
else:
|
|
if not is_section:
|
|
folder_selected.emit(folder_id)
|
|
else:
|
|
expanded = not expanded
|
|
expansion_toggled.emit(folder_id)
|
|
accept_event()
|
|
elif event.button_index == MOUSE_BUTTON_RIGHT:
|
|
context_menu_requested.emit(folder_id, get_global_mouse_position())
|
|
accept_event()
|
|
|
|
|
|
func _can_drop_data(_at_position: Vector2, data: Variant) -> bool:
|
|
if not (data is Dictionary):
|
|
return false
|
|
var k: String = data.get("kind", "")
|
|
if k != "file" and k != "packet":
|
|
return false
|
|
drop_active = true
|
|
return true
|
|
|
|
|
|
func _drop_data(_at_position: Vector2, data: Variant) -> void:
|
|
drop_active = false
|
|
if is_section:
|
|
return
|
|
file_dropped.emit(folder_id, data)
|
|
|
|
|
|
func _notification(what: int) -> void:
|
|
if what == NOTIFICATION_DRAG_END:
|
|
drop_active = false
|