178 lines
5.7 KiB
GDScript
178 lines
5.7 KiB
GDScript
class_name WindowFrame
|
|
extends Control
|
|
## Светлое окно: бумажное тело, тонкий 1 px контур, скругление 4.
|
|
## Заголовок — светлая полоса с тёмным текстом, цветной иконкой и волосяной
|
|
## линией снизу. Никаких синих титлбаров и объёмных квадратов.
|
|
|
|
signal close_requested
|
|
|
|
const TITLE_H := 20.0
|
|
|
|
@export var title := "ОКНО":
|
|
set(v):
|
|
title = v
|
|
queue_redraw()
|
|
@export var title_hint := "":
|
|
set(v):
|
|
title_hint = v
|
|
queue_redraw()
|
|
@export var icon_kind := "generic":
|
|
set(v):
|
|
icon_kind = v
|
|
queue_redraw()
|
|
@export var title_only := false:
|
|
set(v):
|
|
title_only = v
|
|
queue_redraw()
|
|
@export var title_logo := false:
|
|
set(v):
|
|
title_logo = v
|
|
queue_redraw()
|
|
@export var title_size := Theme90.FS_BODY
|
|
@export var close_enabled := false:
|
|
set(v):
|
|
close_enabled = v
|
|
queue_redraw()
|
|
@export var accent_line := false:
|
|
set(v):
|
|
accent_line = v
|
|
queue_redraw()
|
|
|
|
var content: Control
|
|
var _close_rect := Rect2()
|
|
|
|
|
|
func _ready() -> void:
|
|
mouse_filter = Control.MOUSE_FILTER_STOP
|
|
custom_minimum_size = Vector2(maxf(custom_minimum_size.x, 80.0), TITLE_H + 24.0)
|
|
if content == null:
|
|
content = Control.new()
|
|
content.name = "Content"
|
|
content.mouse_filter = Control.MOUSE_FILTER_PASS
|
|
content.clip_contents = true
|
|
add_child(content)
|
|
_layout()
|
|
resized.connect(_layout)
|
|
|
|
|
|
func _layout() -> void:
|
|
if content == null:
|
|
return
|
|
content.position = Vector2(2.0, TITLE_H + 2.0)
|
|
content.size = Vector2(maxf(0.0, size.x - 4.0), maxf(0.0, size.y - TITLE_H - 4.0))
|
|
content.custom_minimum_size = Vector2.ZERO
|
|
|
|
|
|
func _icon_color() -> Color:
|
|
match icon_kind:
|
|
"outbox":
|
|
return Theme90.WARN
|
|
"files":
|
|
return Theme90.ACCENT
|
|
"tree":
|
|
return Theme90.SUCCESS
|
|
_:
|
|
return Theme90.NAVY
|
|
|
|
|
|
func _draw() -> void:
|
|
var r := Rect2(Vector2.ZERO, size)
|
|
# тело окна: белая бумага + тонкий контур + мягкая тень
|
|
draw_style_box(Theme90.box(Theme90.WHITE, Theme90.BORDER, 4, 1, Theme90.SHADOW), r)
|
|
_draw_title()
|
|
if title_only:
|
|
return
|
|
if accent_line:
|
|
draw_rect(Rect2(1.0, TITLE_H + 1.0, size.x - 2.0, 1.0),
|
|
Color(Theme90.ACCENT.r, Theme90.ACCENT.g, Theme90.ACCENT.b, 0.24))
|
|
|
|
|
|
func _draw_title() -> void:
|
|
var tr := Rect2(1.0, 1.0, maxf(0.0, size.x - 2.0), TITLE_H)
|
|
draw_style_box(Theme90.top_box(Theme90.PAPER_HI, 4), tr)
|
|
draw_rect(Rect2(tr.position.x + 1.0, tr.end.y, tr.size.x - 2.0, 1.0), Theme90.HAIRLINE)
|
|
|
|
var icon_color := _icon_color()
|
|
_draw_icon(Rect2(tr.position.x + 7.0, tr.position.y + 3.0, 14.0, 14.0), icon_color)
|
|
|
|
# закрытие — одна аккуратная точка-крестик, только если разрешено
|
|
_close_rect = Rect2()
|
|
if close_enabled:
|
|
_close_rect = Rect2(tr.end.x - 22.0, tr.position.y + 3.0, 14.0, 14.0)
|
|
draw_circle(_close_rect.get_center(), 6.0, Color(Theme90.ERROR.r, Theme90.ERROR.g,
|
|
Theme90.ERROR.b, 0.14))
|
|
Theme90.draw_x(self, _close_rect, Theme90.ERROR, 1.2)
|
|
|
|
var f := Theme90.ui()
|
|
var tx := tr.position.x + 26.0
|
|
var right_limit := tr.end.x - (24.0 if close_enabled else 8.0)
|
|
if not title.is_empty():
|
|
var tf: Font = Theme90.logo() if title_logo else f
|
|
var tsz := 10 if title_logo else title_size
|
|
var th := tf.get_height(tsz)
|
|
Theme90.text(self, tf, Vector2(tx, tr.position.y + roundf((TITLE_H - th) * 0.5)),
|
|
title, tsz, Theme90.NAVY_DEEP)
|
|
tx += tf.get_string_size(title, HORIZONTAL_ALIGNMENT_LEFT, -1, tsz).x + 12.0
|
|
|
|
if not title_hint.is_empty():
|
|
var hint := title_hint
|
|
var hw := f.get_string_size(hint, HORIZONTAL_ALIGNMENT_LEFT, -1,
|
|
Theme90.FS_MICRO).x
|
|
var limit := right_limit - 8.0 - tx
|
|
if hw > limit:
|
|
hint = Theme90.fit(f, hint, Theme90.FS_MICRO, limit)
|
|
hw = f.get_string_size(hint, HORIZONTAL_ALIGNMENT_LEFT, -1,
|
|
Theme90.FS_MICRO).x
|
|
if limit > 8.0:
|
|
Theme90.text(self, f, Vector2(right_limit - hw, tr.position.y + 5.0), hint,
|
|
Theme90.FS_MICRO, Theme90.SHADE)
|
|
|
|
|
|
func _draw_icon(r: Rect2, color: Color) -> void:
|
|
var c := roundf(r.position.x)
|
|
var y := roundf(r.position.y)
|
|
match icon_kind:
|
|
"inbox":
|
|
draw_style_box(Theme90.box(Color(color.r, color.g, color.b, 0.12), Color(0, 0, 0, 0), 3, 0),
|
|
r)
|
|
Theme90.draw_packet_icon(self, Vector2(c + 1, y + 3), color)
|
|
"outbox":
|
|
draw_style_box(Theme90.box(Color(color.r, color.g, color.b, 0.12), Color(0, 0, 0, 0), 3, 0),
|
|
r)
|
|
Theme90.draw_packet_icon(self, Vector2(c + 1, y + 3), color)
|
|
draw_line(Vector2(c + 1, y + 1), Vector2(c + 13, y + 1), color, 1.0)
|
|
"files":
|
|
draw_style_box(Theme90.box(Color(color.r, color.g, color.b, 0.12), Color(0, 0, 0, 0), 3, 0),
|
|
r)
|
|
draw_style_box(Theme90.box(Theme90.WHITE, Color(color.r, color.g, color.b, 0.55), 1, 1),
|
|
Rect2(c + 2, y + 2, 9, 11))
|
|
draw_style_box(Theme90.box(Theme90.WHITE, Color(color.r, color.g, color.b, 0.55), 1, 1),
|
|
Rect2(c + 4, y + 4, 9, 11))
|
|
for i in 3:
|
|
draw_rect(Rect2(c + 6, y + 7 + i * 2.2, 5, 0.8), color)
|
|
"tree":
|
|
Theme90.draw_folder_icon(self, Vector2(c + 1, y + 2), color, true)
|
|
"app":
|
|
# дискета: светлая карточка с цветной наклейкой
|
|
draw_style_box(Theme90.box(Theme90.PAPER_HI, Color(color.r, color.g, color.b, 0.5), 2, 1),
|
|
Rect2(c + 1, y + 1, 12, 12))
|
|
draw_rect(Rect2(c + 4, y + 2, 6, 4), color)
|
|
draw_rect(Rect2(c + 3, y + 8, 8, 3), Color(color.r, color.g, color.b, 0.6))
|
|
draw_rect(Rect2(c + 5, y + 9, 4, 1), Theme90.PAPER_HI)
|
|
_:
|
|
draw_circle(r.get_center(), 3.0, color)
|
|
|
|
|
|
func _gui_input(event: InputEvent) -> void:
|
|
if event is InputEventMouseButton and event.pressed \
|
|
and event.button_index == MOUSE_BUTTON_LEFT:
|
|
if close_enabled and _close_rect.has_point(event.position):
|
|
close_requested.emit()
|
|
accept_event()
|
|
|
|
|
|
func set_title(t: String, hint: String = "") -> void:
|
|
title = t
|
|
title_hint = hint
|
|
queue_redraw()
|