Files

248 lines
7.5 KiB
GDScript

class_name StatusBar
extends Control
## Строка состояния: одна светлая полоса, тонкие разделители и цветные шкалы.
## Без утопленных коробок и лишних подписей. Ключи сегментов прежние:
## tcp / channel / sla / buffer / money / hour.
##
## Над сегментом приёма лежит прозрачная зона подсказки: наведение показывает,
## что такое буфер и сколько места занято сейчас.
var segments: Array[Dictionary] = []
var _floats: Array[Control] = []
var _buffer_hint: Control = null
const PAD := 14.0
func _ready() -> void:
mouse_filter = Control.MOUSE_FILTER_STOP
segments = [
{"key": "tcp", "label": "TCP", "value": "2 / 3", "w": 132.0, "fill": 0.667},
{"key": "channel", "label": "КАНАЛ", "value": "45%", "w": 222.0, "fill": 0.45},
{"key": "sla", "label": "SLA", "value": "128 / 200", "w": 188.0, "fill": 0.64},
{"key": "buffer", "label": "МЕСТО В ПРИЁМЕ", "value": "3 / 4", "w": 158.0, "fill": 0.75},
{"key": "money", "label": "СЧЁТ", "value": "$ 1 240", "w": 176.0},
{"key": "hour", "label": "СМЕНА", "value": "ЧАС 3 03:42", "w": 356.0, "fill": 0.463},
]
_buffer_hint = Control.new()
_buffer_hint.name = "BufferHint"
_buffer_hint.mouse_filter = Control.MOUSE_FILTER_PASS
_buffer_hint.mouse_default_cursor_shape = Control.CURSOR_HELP
_buffer_hint.focus_mode = Control.FOCUS_NONE
add_child(_buffer_hint)
resized.connect(_layout_hint)
call_deferred("_layout_hint")
## Подсказка «что такое буфер» с текущим значением сегмента.
func _buffer_tooltip() -> String:
var raw := ""
for s in segments:
if str(s["key"]) == "buffer":
raw = str(s["value"])
break
var parts := raw.split("/")
var used := 0
var cap := 0
if parts.size() == 2:
used = parts[0].strip_edges().to_int()
cap = parts[1].strip_edges().to_int()
var out := "Буфер (место в приёме) — сколько пакетов можно держать одновременно\n"
out += "Сейчас: %d / %d" % [used, cap]
if cap > 0:
out += " · свободно %d" % maxi(0, cap - used)
if used >= cap:
out += "\nБуфер заполнен: новые пакеты не примутся, пока место не освободится."
return out
## Прозрачная зона над сегментом буфера: только подсказка, без отрисовки.
func _layout_hint() -> void:
if _buffer_hint == null:
return
var idx := -1
for i in segments.size():
if str(segments[i]["key"]) == "buffer":
idx = i
break
if idx < 0:
return
var rects := _segment_rects()
if idx >= rects.size():
return
var r: Rect2 = rects[idx]
_buffer_hint.position = Vector2(r.position.x, 0.0)
_buffer_hint.size = Vector2(r.size.x, size.y)
_buffer_hint.tooltip_text = _buffer_tooltip()
func _segment_rects() -> Array[Rect2]:
var out: Array[Rect2] = []
var total := 0.0
for s in segments:
total += float(s["w"])
var avail := maxf(0.0, size.x - 16.0)
var x := 8.0
for s in segments:
var w := floorf(avail * (float(s["w"]) / maxf(1.0, total)))
out.append(Rect2(x, 0, w, size.y))
x += w
return out
func _draw() -> void:
var full := Rect2(Vector2.ZERO, size)
draw_rect(full, Theme90.PAPER_HI)
draw_rect(Rect2(0, 0, size.x, 1), Theme90.BORDER_SOFT)
draw_rect(Rect2(0, size.y - 1, size.x, 1), Color(0, 0, 0, 0.05))
var f := Theme90.ui()
var rects := _segment_rects()
for i in segments.size():
var s := segments[i]
var r: Rect2 = rects[i]
if i > 0:
draw_rect(Rect2(r.position.x, 9, 1, size.y - 20), Theme90.HAIRLINE)
Theme90.text(self, f, Vector2(r.position.x + PAD, 9), str(s["label"]),
Theme90.FS_MICRO, Theme90.SHADE)
if str(s["key"]) == "buffer":
_draw_help_badge(f, r, str(s["label"]))
var col := _value_color(s)
var val := str(s["value"])
var vf := f
if _is_numeric(val):
Theme90.num_line(self, Vector2(r.position.x + PAD, 23), val, 14, col)
else:
Theme90.text(self, vf, Vector2(r.position.x + PAD, 23), val, 14, col)
if s.has("blocks") or s.has("bar") or s.has("fill"):
var ratio := float(s.get("fill", 0.0))
var bar := Rect2(r.position.x + PAD, size.y - 16.0, maxf(4.0, r.size.x - PAD * 2.0), 4.0)
Theme90.draw_meter(self, bar, ratio, _gauge_color(s), Theme90.PAPER_LO)
func _draw_help_badge(f: Font, r: Rect2, label: String) -> void:
var lw := f.get_string_size(label, HORIZONTAL_ALIGNMENT_LEFT, -1, Theme90.FS_MICRO).x
var b := Rect2(r.position.x + PAD + lw + 6.0, 10.0, 9.0, 9.0)
draw_style_box(Theme90.fill_box(Color(Theme90.SHADE.r, Theme90.SHADE.g,
Theme90.SHADE.b, 0.13), 4), b)
Theme90.text_vcenter(self, f, b, "?", 9, Theme90.SHADE, -1, HORIZONTAL_ALIGNMENT_CENTER)
func _is_numeric(s: String) -> bool:
for ch in s:
if ch.is_valid_int() or " /%$.,:".contains(ch):
continue
return false
return true
func _gauge_color(s: Dictionary) -> Color:
match str(s["key"]):
"channel":
var fill := float(s.get("fill", 0.0))
if fill > 0.8:
return Theme90.ERROR
if fill > 0.6:
return Theme90.WARN
return Theme90.ACCENT
"sla":
var fill2 := float(s.get("fill", 1.0))
if fill2 < 0.4:
return Theme90.ERROR
if fill2 < 0.6:
return Theme90.WARN
return Theme90.SUCCESS
"money":
return Theme90.SUCCESS
"hour":
return Theme90.ACCENT_LIGHT
_:
return Theme90.ACCENT
func _value_color(s: Dictionary) -> Color:
match str(s["key"]):
"channel":
var fill := float(s.get("fill", 0.0))
if fill > 0.8:
return Theme90.ERROR
if fill > 0.6:
return Theme90.WARN
return Theme90.INK
"sla":
var fill2 := float(s.get("fill", 1.0))
if fill2 < 0.4:
return Theme90.ERROR
if fill2 < 0.6:
return Theme90.WARN
return Theme90.INK
"money":
return Theme90.SUCCESS
"hour":
return Theme90.NAVY
_:
return Theme90.INK
func set_metric(key: String, value: String, fill: float = -1.0) -> void:
for s in segments:
if str(s["key"]) == key:
s["value"] = value
if fill >= 0.0:
s["fill"] = fill
if key == "buffer" and _buffer_hint != null:
_buffer_hint.tooltip_text = _buffer_tooltip()
queue_redraw()
return
func get_metric(key: String) -> Dictionary:
for s in segments:
if str(s["key"]) == key:
return s
return {}
## Всплывающая надпись над сегментом (+50, −10 и т.п.).
func flash(key: String, text: String, color: Color = Theme90.SUCCESS) -> void:
var rects := _segment_rects()
var idx := -1
for i in segments.size():
if str(segments[i]["key"]) == key:
idx = i
break
if idx < 0:
return
var r: Rect2 = rects[idx]
var ft := FloatNote.new(text, color)
add_child(ft)
ft.position = Vector2(r.position.x + PAD, 4)
_floats.append(ft)
var tw := create_tween()
tw.set_parallel(true)
tw.tween_property(ft, "position:y", ft.position.y - 22.0, 1.1)
tw.tween_property(ft, "modulate:a", 0.0, 0.5).set_delay(0.55)
tw.chain().tween_callback(func() -> void:
_floats.erase(ft)
ft.queue_free())
class FloatNote:
extends Control
var txt := ""
var col := Color.WHITE
func _init(p_text: String, p_color: Color) -> void:
txt = p_text
col = p_color
mouse_filter = Control.MOUSE_FILTER_IGNORE
func _draw() -> void:
var f := Theme90.ui()
var w := f.get_string_size(txt, HORIZONTAL_ALIGNMENT_LEFT, -1, 12).x
size = Vector2(w + 10, 18)
draw_style_box(Theme90.box(Color(1, 1, 1, 0.92), Theme90.BORDER_SOFT, 3, 1,
Theme90.SHADOW), Rect2(Vector2.ZERO, size))
Theme90.text(self, f, Vector2(5, 3), txt, 11, col)