Files

503 lines
18 KiB
GDScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
extends Control
## Дев-сцена: предпросмотр всех модальных окон на фейковом игровом экране.
##
## godot --path . --script scripts/ui/dev_capture.gd -- \
## --scene=res://scenes/ui/ModalsPreview.tscn --out=/tmp/modals.png --modal=shop
##
## --modal: shop | pause | settings | hour | run_win | run_lose | menu | confirm | grid
## Клавиши: 1–9 — окна, ← → — переключение, G — сетка.
const SHOP := preload("res://scripts/ui/shop_modal.gd")
const PAUSE := preload("res://scripts/ui/pause_modal.gd")
const SETTINGS := preload("res://scripts/ui/settings_modal.gd")
const HOUR := preload("res://scripts/ui/hour_result_modal.gd")
const RUN := preload("res://scripts/ui/run_result_modal.gd")
const MENU := preload("res://scripts/ui/main_menu.gd")
const CONFIRM := preload("res://scripts/ui/confirm_modal.gd")
const MASCOT_PREVIEW := preload("res://scripts/ui/tutorial_mascot_preview.gd")
const W := 1280.0
const H := 720.0
const TOOLBAR_H := 34.0
const GRID_SCALE := 0.285
const CELL := Vector2(1280.0 * GRID_SCALE, 720.0 * GRID_SCALE)
const GRID_X := 79.0
const GRID_Y := 50.0
const GRID_GAP_X := 14.0
const GRID_GAP_Y := 18.0
const TABS := ["МАГАЗИН", "ПАУЗА", "НАСТРОЙКИ", "ИТОГИ ЧАСА", "ПОБЕДА", "ПОРАЖЕНИЕ",
"МЕНЮ", "ВОПРОС", "МАСКОТ", "СЕТКА"]
const ARG_NAMES := ["shop", "pause", "settings", "hour", "run_win", "run_lose", "menu",
"confirm", "mascot", "grid"]
var _stage: Control
var _scrim: ColorRect
var _overlay: OverlayLayer
var _toolbar: Panel90
var _signal_label: Label
var _modals: Array[Control] = []
var _tab_buttons: Array[Button90] = []
var _active := 0
var _grid_mode := false
var _shop
var _pause
var _settings
var _hour
var _run_win
var _run_lose
var _menu
var _confirm
var _mascot_preview
var _money := 280
var _levels := {"shop_slot3": 1, "shop_buffer": 1, "shop_timer": 2}
func _ready() -> void:
set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
mouse_filter = Control.MOUSE_FILTER_PASS
_build()
var start := _arg_start()
_show_tab(start)
# --------------------------------------------------------------------------
# Сборка
# --------------------------------------------------------------------------
func _build() -> void:
var desk := FakeDesk.new()
desk.name = "FakeDesk"
add_child(desk)
desk.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
desk.mouse_filter = Control.MOUSE_FILTER_IGNORE
_scrim = ColorRect.new()
_scrim.name = "GridScrim"
_scrim.color = Color(0.086, 0.098, 0.129)
_scrim.mouse_filter = Control.MOUSE_FILTER_IGNORE
_scrim.visible = false
add_child(_scrim)
_scrim.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
_stage = Control.new()
_stage.name = "Stage"
add_child(_stage)
_stage.position = Vector2.ZERO
_stage.size = Vector2(W, H)
_build_modals()
_overlay = OverlayLayer.new()
_overlay.name = "Overlay"
_overlay.z_index = 6
_overlay.mouse_filter = Control.MOUSE_FILTER_IGNORE
add_child(_overlay)
_overlay.position = Vector2.ZERO
_overlay.size = Vector2(W, H)
_build_toolbar()
func _build_modals() -> void:
_shop = SHOP.new()
_shop.name = "ShopModal"
_add_modal(_shop)
_pause = PAUSE.new()
_pause.name = "PauseModal"
_add_modal(_pause)
_settings = SETTINGS.new()
_settings.name = "SettingsModal"
_add_modal(_settings)
_hour = HOUR.new()
_hour.name = "HourResultModal"
_add_modal(_hour)
_run_win = RUN.new()
_run_win.name = "RunResultVictory"
_add_modal(_run_win)
_run_lose = RUN.new()
_run_lose.name = "RunResultDefeat"
_add_modal(_run_lose)
_menu = MENU.new()
_menu.name = "MainMenu"
_add_modal(_menu)
_confirm = CONFIRM.new()
_confirm.name = "ConfirmModal"
_add_modal(_confirm)
_mascot_preview = MASCOT_PREVIEW.new()
_mascot_preview.name = "MascotPreview"
_add_modal(_mascot_preview)
# фейковые данные
_shop.open(_money, _levels)
_pause.open()
_settings.open({"sfx": 70, "music": 55, "calm_bg": true, "fullscreen": false})
_hour.open({
"hour": 3, "hours_total": 9, "earned": 1240, "done": 8, "failed": 2,
"reflected": 3, "missed": 1, "sla_delta": -12, "sla": 168, "sla_max": 200,
})
_run_win.open({
"won": true, "reason": "Смена отработана. Все 9 часов закрыты.",
"score": 14820, "best": 15100, "hours": 9, "hours_total": 9,
"money": 2380, "sla": 96, "sla_max": 200,
})
_run_lose.open({
"won": false, "reason": "SLA упал до нуля на шестом часе.",
"score": 6310, "best": 15100, "hours": 5, "hours_total": 9,
"money": 940, "sla": 0, "sla_max": 200,
})
_menu.set_has_save(false)
_confirm.ask("Выйти в главное меню?", "Прогресс текущей смены не сохранится.",
"Да", "Нет", true)
# сигналы
_shop.buy_requested.connect(_on_shop_buy)
_shop.closed.connect(func() -> void: _status("МАГАЗИН ЗАКРЫТ"))
_pause.resumed.connect(func() -> void: _status("ПАУЗА · ПРОДОЛЖИТЬ"))
_pause.settings_requested.connect(func() -> void: _status("ПАУЗА · НАСТРОЙКИ"))
_pause.menu_requested.connect(func() -> void: _status("ПАУЗА · В ГЛАВНОЕ МЕНЮ"))
_pause.closed.connect(func() -> void: _status("ПАУЗА ЗАКРЫТА"))
_settings.settings_changed.connect(_on_settings_changed)
_settings.closed.connect(func() -> void: _status("НАСТРОЙКИ ЗАКРЫТЫ"))
_hour.next_pressed.connect(func() -> void:
_status("ИТОГИ ЧАСА · ДАЛЕЕ")
_hour.open({
"hour": 4, "hours_total": 9, "earned": 1580, "done": 11, "failed": 1,
"reflected": 4, "missed": 0, "sla_delta": 9, "sla": 177, "sla_max": 200,
}))
_run_win.new_run.connect(func() -> void: _status("ЗАБЕГ · НОВЫЙ ЗАБЕГ"))
_run_win.to_menu.connect(func() -> void: _status("ЗАБЕГ · В МЕНЮ"))
_run_lose.new_run.connect(func() -> void: _status("ЗАБЕГ · НОВЫЙ ЗАБЕГ"))
_run_lose.to_menu.connect(func() -> void: _status("ЗАБЕГ · В МЕНЮ"))
_menu.new_run.connect(func() -> void: _status("МЕНЮ · НОВЫЙ ЗАБЕГ"))
_menu.continue_run.connect(func() -> void: _status("МЕНЮ · ПРОДОЛЖИТЬ"))
_menu.settings.connect(func() -> void: _status("МЕНЮ · НАСТРОЙКИ"))
_menu.about.connect(func() -> void: _status("МЕНЮ · ОБ ИГРЕ"))
_confirm.answered.connect(func(yes: bool) -> void:
_status("ОТВЕТ: " + ("ДА" if yes else "НЕТ")))
func _add_modal(m: Control) -> void:
_stage.add_child(m)
# фиксированный размер: в сетке масштабируем сами
m.set_anchors_and_offsets_preset(Control.PRESET_TOP_LEFT)
m.size = Vector2(W, H)
m.position = Vector2.ZERO
_modals.append(m)
func _build_toolbar() -> void:
_toolbar = Panel90.new()
_toolbar.name = "Toolbar"
_toolbar.kind = Panel90.Kind.FLAT
_toolbar.z_index = 10
add_child(_toolbar)
_toolbar.position = Vector2.ZERO
_toolbar.size = Vector2(W, TOOLBAR_H)
var sep := ColorRect.new()
sep.color = Theme90.SHADE
sep.mouse_filter = Control.MOUSE_FILTER_IGNORE
sep.position = Vector2(0, TOOLBAR_H - 1)
sep.size = Vector2(W, 1)
_toolbar.add_child(sep)
var caption := Label.new()
caption.text = "ПРЕДПРОСМОТР"
caption.add_theme_font_override("font", Theme90.ui(700))
caption.add_theme_font_size_override("font_size", Theme90.FS_MICRO)
caption.add_theme_color_override("font_color", Theme90.DARK)
caption.position = Vector2(8, 11)
caption.size = Vector2(80, 12)
caption.mouse_filter = Control.MOUSE_FILTER_IGNORE
_toolbar.add_child(caption)
var f := Theme90.ui()
var sep_x := 92.0
var vline := ColorRect.new()
vline.color = Theme90.SHADE
vline.mouse_filter = Control.MOUSE_FILTER_IGNORE
vline.position = Vector2(sep_x, 5)
vline.size = Vector2(1, 24)
_toolbar.add_child(vline)
var x := 100.0
for i in TABS.size():
var b := Button90.new()
b.flat_style = true
b.caption = TABS[i]
b.caption_size = Theme90.FS_MICRO
b.size = Vector2(f.get_string_size(TABS[i], HORIZONTAL_ALIGNMENT_LEFT, -1,
Theme90.FS_MICRO).x + 18.0, 24)
b.position = Vector2(x, 5)
b.tooltip_text = "Клавиша %d" % (i + 1)
b.pressed.connect(_show_tab.bind(i))
_toolbar.add_child(b)
_tab_buttons.append(b)
x += b.size.x + 4.0
_signal_label = Label.new()
_signal_label.text = "СИГНАЛ: —"
_signal_label.add_theme_font_override("font", Theme90.ui(700))
_signal_label.add_theme_font_size_override("font_size", Theme90.FS_MICRO)
_signal_label.add_theme_color_override("font_color", Theme90.NAVY)
_signal_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
_signal_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
_signal_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
_signal_label.position = Vector2(770, 5)
_signal_label.size = Vector2(330, 24)
_toolbar.add_child(_signal_label)
var hint := Label.new()
hint.text = "1–9 · ← → · G"
hint.add_theme_font_override("font", Theme90.ui(700))
hint.add_theme_font_size_override("font_size", Theme90.FS_MICRO)
hint.add_theme_color_override("font_color", Theme90.DARK)
hint.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
hint.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
hint.mouse_filter = Control.MOUSE_FILTER_IGNORE
hint.position = Vector2(1110, 5)
hint.size = Vector2(160, 24)
_toolbar.add_child(hint)
# --------------------------------------------------------------------------
# Переключение
# --------------------------------------------------------------------------
func _arg_start() -> int:
for a in OS.get_cmdline_user_args():
if a.begins_with("--modal="):
var key := a.substr(8)
var idx := ARG_NAMES.find(key)
if idx >= 0:
return idx
return 0
func _show_tab(index: int) -> void:
_active = clampi(index, 0, TABS.size() - 1)
_grid_mode = _active == TABS.size() - 1
_scrim.visible = _grid_mode
for i in _tab_buttons.size():
_tab_buttons[i].active = i == _active
_tab_buttons[i].queue_redraw()
if _grid_mode:
_overlay.cells.clear()
for i in _modals.size():
var m := _modals[i]
if m == _mascot_preview:
m.visible = false
continue
var cell := _grid_cell(i)
m.visible = true
m.scale = Vector2(GRID_SCALE, GRID_SCALE)
m.position = cell
_overlay.cells.append({"rect": Rect2(cell, CELL), "title": _cell_title(i)})
else:
_overlay.cells.clear()
for i in _modals.size():
var m := _modals[i]
m.scale = Vector2.ONE
m.position = Vector2.ZERO
m.size = Vector2(W, H)
m.visible = i == _active
_overlay.queue_redraw()
_status("—")
func _grid_cell(i: int) -> Vector2:
var col := i % 3
var row := i / 3
return Vector2(GRID_X + col * (CELL.x + GRID_GAP_X),
GRID_Y + row * (CELL.y + GRID_GAP_Y))
func _cell_title(i: int) -> String:
var titles := ["МАГАЗИН", "ПАУЗА", "НАСТРОЙКИ", "ИТОГИ ЧАСА", "ЗАБЕГ: ПОБЕДА",
"ЗАБЕГ: ПОРАЖЕНИЕ", "ГЛАВНОЕ МЕНЮ", "ПОДТВЕРЖДЕНИЕ", "МАСКОТ"]
if i < titles.size():
return titles[i]
return TABS[i]
func _status(text: String) -> void:
if _signal_label != null:
var value := "СИГНАЛ: " + text
_signal_label.text = Theme90.fit(Theme90.ui(700), value, Theme90.FS_MICRO, 320)
func _on_shop_buy(id: String) -> void:
var price: int = _shop.item_price(id)
var level := int(_levels.get(id, 0))
var max_level: int = _shop.item_max_level(id)
if level >= max_level:
_status("МАКСИМУМ: " + _shop.item_name(id))
return
if _money < price:
_status("НЕ ХВАТАЕТ %d $: %s" % [price - _money, _shop.item_name(id)])
return
_money -= price
_levels[id] = level + 1
_shop.refresh(_money, _levels)
_status("КУПЛЕНО: %s · −%d $" % [_shop.item_name(id), price])
func _on_settings_changed(values: Dictionary) -> void:
_status("ЗВУК %d · МУЗЫКА %d · ФОН %s · ЭКРАН %s" % [
int(values.get("sfx", 0)), int(values.get("music", 0)),
"ВКЛ" if bool(values.get("calm_bg", false)) else "ВЫКЛ",
"ВКЛ" if bool(values.get("fullscreen", false)) else "ВЫКЛ"])
func _unhandled_input(event: InputEvent) -> void:
if not (event is InputEventKey) or not event.pressed or event.echo:
return
match event.keycode:
KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9:
_show_tab(event.keycode - KEY_1)
get_viewport().set_input_as_handled()
KEY_G:
_show_tab(TABS.size() - 1)
get_viewport().set_input_as_handled()
KEY_LEFT:
_show_tab((_active - 1 + TABS.size()) % TABS.size())
get_viewport().set_input_as_handled()
KEY_RIGHT:
_show_tab((_active + 1) % TABS.size())
get_viewport().set_input_as_handled()
# --------------------------------------------------------------------------
# Служебные слои
# --------------------------------------------------------------------------
class OverlayLayer:
extends Control
var cells: Array = []
func _draw() -> void:
for cell in cells:
var r: Rect2 = cell["rect"]
draw_rect(r.grow(1), Color(0.55, 0.58, 0.66), false, 1.0)
draw_rect(r, Color(0.20, 0.22, 0.28), false, 1.0)
draw_rect(Rect2(r.position.x, r.position.y - 14, 5, 5), Theme90.ACCENT_LIGHT)
Theme90.text(self, Theme90.ui(), Vector2(r.position.x + 10, r.position.y - 14),
str(cell["title"]), Theme90.FS_MICRO, Color(0.86, 0.89, 0.95))
## Условный «игровой экран» под модалками — чтобы видеть затемнение.
class FakeDesk:
extends Control
func _draw() -> void:
draw_texture_rect(Theme90.dither_texture(), Rect2(Vector2.ZERO, size), true)
var f := Theme90.ui()
var lf := Theme90.logo()
# титлбар приложения — светлая полоса
draw_rect(Rect2(0, 0, size.x, 24), Theme90.PAPER_HI)
draw_rect(Rect2(0, 23, size.x, 1), Theme90.HAIRLINE)
Theme90.text(self, lf, Vector2(10, 7), "PEAK HOUR 97", 9, Theme90.NAVY)
Theme90.text(self, f, Vector2(size.x - 74, 7), "03:42", Theme90.FS_MICRO,
Theme90.SHADE)
# строка меню
draw_rect(Rect2(0, 24, size.x, 20), Theme90.PAPER)
draw_rect(Rect2(0, 43, size.x, 1), Theme90.HAIRLINE)
Theme90.text(self, f, Vector2(10, 27), "ФАЙЛ ПРАВКА ВИД СПРАВКА",
Theme90.FS_MICRO, Theme90.DARK)
Theme90.text(self, f, Vector2(1070, 27), "МАГАЗИН", Theme90.FS_MICRO, Theme90.DARK)
Theme90.text(self, f, Vector2(1140, 27), "НАЧАТЬ ЧАС", Theme90.FS_MICRO, Theme90.NAVY)
# рабочая область
draw_style_box(Theme90.box(Theme90.PAPER, Theme90.BORDER_SOFT, 4, 1), Rect2(8, 48, 840, 548))
_draw_window(Rect2(16, 72, 240, 516), "ДИСКИ")
_draw_window(Rect2(264, 72, 576, 516), "ФАЙЛЫ · ПРИЁМ")
_draw_window(Rect2(856, 48, 416, 264), "ПРИЁМ")
_draw_window(Rect2(856, 320, 416, 276), "ОТПРАВКА")
# дерево
Theme90.draw_folder_icon(self, Vector2(30, 108), Theme90.WARN, true)
Theme90.text(self, f, Vector2(50, 104), "C: ЛОКАЛЬНЫЙ", Theme90.FS_MICRO, Theme90.INK)
for i in 5:
Theme90.draw_folder_icon(self, Vector2(42, 132 + i * 20), Theme90.WARN, false)
Theme90.text(self, f, Vector2(62, 130 + i * 20),
["ПРИЁМ", "ОТПРАВКА", "АРХИВ", "ПРОЕКТЫ", "СКАНЫ"][i],
Theme90.FS_MICRO, Theme90.DARK)
# сетка файлов
for i in 18:
var col := i % 6
var row := i / 6
var r := Rect2(292 + col * 88, 112 + row * 72, 76, 60)
draw_style_box(Theme90.box(Theme90.WHITE, Theme90.BORDER_SOFT, 3, 1), r)
Theme90.draw_doc_icon(self, Rect2(r.position + Vector2(26, 6), Vector2(24, 24)),
["text", "sound", "photo", "video"][i % 4],
[Theme90.ACCENT, Theme90.WARN, Theme90.SUCCESS, Theme90.ERROR][i % 4])
Theme90.text(self, f, Vector2(r.position.x + 7, r.position.y + 38),
Theme90.fit(f, ["отчёт_смены", "реклама", "скан_пасп", "кадр_4", "договор",
"прайс"][i % 6] + ".xls", Theme90.FS_MICRO, 62),
Theme90.FS_MICRO, Theme90.DARK)
# карточки приёма и запросов
for i in 4:
var y := 80.0 + i * 52.0
var card := Rect2(866, y, 396, 44)
draw_style_box(Theme90.box(Theme90.WHITE, Theme90.BORDER_SOFT, 3, 1), card)
Theme90.draw_packet_icon(self, card.position + Vector2(10, 14), Theme90.ACCENT)
Theme90.text(self, f, card.position + Vector2(32, 9), "пакет_%02d.wav" % (i + 1),
Theme90.FS_MICRO, Theme90.INK)
Theme90.text(self, f, card.position + Vector2(32, 24), "1,8 МБ · 00:12",
Theme90.FS_MICRO, Theme90.SHADE)
for i in 3:
var y := 328.0 + i * 64.0
var card := Rect2(866, y, 396, 56)
draw_style_box(Theme90.box(Theme90.PAPER_HI, Theme90.BORDER_SOFT, 3, 1), card)
Theme90.text(self, f, card.position + Vector2(10, 7), "ООО «ВЕКТОР»",
Theme90.FS_MICRO, Theme90.INK)
Theme90.text(self, f, card.position + Vector2(10, 22), "Требуется 2 файла",
Theme90.FS_MICRO, Theme90.SHADE)
for j in 2:
draw_style_box(Theme90.well_box(Theme90.WHITE),
Rect2(card.position + Vector2(270 + j * 58, 12), Vector2(50, 32)))
# слоты
draw_style_box(Theme90.box(Theme90.PAPER_HI, Theme90.BORDER_SOFT, 4, 1), Rect2(8, 604, 1264, 52))
Theme90.text(self, f, Vector2(20, 620), "TCP", Theme90.FS_MICRO, Theme90.NAVY)
for i in 4:
draw_style_box(Theme90.well_box(Theme90.WHITE), Rect2(58 + i * 240, 610, 236, 40))
Theme90.text(self, f, Vector2(70 + i * 240, 622), "СЛОТ %d" % (i + 1),
Theme90.FS_MICRO, Theme90.SHADE)
# статус-бар
draw_rect(Rect2(0, 660, size.x, 60), Theme90.PAPER_HI)
draw_rect(Rect2(0, 660, size.x, 1), Theme90.HAIRLINE)
Theme90.text(self, f, Vector2(18, 672), "УЗЕЛ 7 НА СВЯЗИ · БУФЕР 3/4 · КАНАЛ 45%",
Theme90.FS_MICRO, Theme90.SHADE)
Theme90.text(self, f, Vector2(18, 693), "СМЕНА 3 · 03:42 / 08:00",
Theme90.FS_MICRO, Theme90.SHADE)
Theme90.text(self, lf, Vector2(1010, 680), "$ 1240", 12, Theme90.SUCCESS)
func _draw_window(r: Rect2, title: String) -> void:
draw_style_box(Theme90.box(Theme90.WHITE, Theme90.BORDER, 3, 1, Theme90.SHADOW), r)
var tr := Rect2(r.position.x + 1, r.position.y + 1, r.size.x - 2, 18)
draw_style_box(Theme90.top_box(Theme90.PAPER_HI, 3), tr)
draw_rect(Rect2(tr.position.x, tr.end.y, tr.size.x, 1), Theme90.HAIRLINE)
Theme90.text_vcenter(self, Theme90.ui(), Rect2(tr.position.x + 8, tr.position.y,
tr.size.x - 16, 18), title, Theme90.FS_MICRO, Theme90.NAVY)