Files

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