Добавлен , опубликован

Основы Интерфейса

Содержание:
Этот большая подстатья будет посвящена кнопкам.
существуют несколько типов кнопок:
  • BUTTON (кнопка с изображением)
  • TEXTBUTTON (кнопка с текстом)
  • GLUEBUTTON (кнопка с изображением)
  • GLUETEXTBUTTON (кнопка с текстом)
  • SIMPLEBUTTON (кнопка, но о ней поговорим попозже)
есть и др типы frames, которые похожи это CHECKBOX и др.
Я заметил, что все фреймы - это почти 70-90% имеют реакцию на клики, даже фрейм типа TEXT, что самое удивительное, имеет реакцию на клик. все фреймы это по сути кнопки встроенными различными реакциями на разные события. Какие то типы фреймы обладают обширными функциями, а какие-то типы ограничены - имеют одно событие, а какие-то совсем не имеют никаких событии.

SimpleButton

SIMPLEBUTTON - эта кнопка интерфейса. Я начал именно с нее. Оказывается, что весь игровой интерфейс заполнен этими SimpleFrames, SimpleFrames - простые фреймы, состоящие из простых элементов таких как Texture, String, Layer, SimpleButton. SimpleFrames лежат выше, чем все элементы
Texture, String, Layer редко когда функционировали как отдельные элементы, часто являлись дочерними элементами других фреймов. SimpleButton может быть так и кнопкой с изображением, так и кнопкой с текстом. Все зависит от того, какие у него в импорте fdf-file прописаны дочерние фреймы. Если есть Textures может стать и кнопкой с изображением, а если есть еще и String, отвечающие за шрифт, то наша кнопка может стать еще и текстовой. Часто все шаблоны прописываются в fdf-files.
В этих SimpleFrames очень много Textures, String, все кнопки в игре созданы как SimpleButton. Я смотрел fdf-files. Командная панель (CommandBarFrame) состоит из таких кнопок, итемы (InventoryBar), кнопки мини-карты (MinimapButtonBar) тоже состоят из таких кнопок, UpperButtonBar тоже состоит из таких кнопок. Почти все в игровом интерфейсе состоит из этих SimpleFrames. (UpperButtonBar - наглядный пример с изображением, пока что c SimpleButton проблема с примерами. fdf-file нашел один пример с изображением. У остальных как-то не понятно как создается).
Особенности SimpleButton:
  • контролирует пространство на экране, поэтому в этом месте нельзя выбирать объекты на карте (юниты, итемы. декор). Т.е. SimpleButton как SimpleFrame лежит выше чем остальные типы фреймов, и поэтому прочно занимает место. Но так не всегда бывает, однако, если два SimpleButton с одинаковым уровнем создать, то выше будет та кнопка, которая создана последней. Если же уровень фрейма выше, то кнопка будет выше. Это значит, что реакция событии мыши или подсказки не будут работать в этой области. К примеру, я не могу использовать функциональные фреймы, тк они всегда находятся ниже, чем SimpleFrames. Функциональные фреймы зависят от уровня родителя, чаще мы используем в качестве родителя gameUI, который ниже simpleframes.
  • Каждая SIMPLEBUTTON может иметь только одно событие FRAMEEVENT_CONTROL_CLICK, нельзя регистрировать несколько событии, всегда записывает последнее. когда повешено на фрейм одно событие, и тут же регистрирует другое для того же SimpleButton, то предыдущее событие больше не работает. Это значит, что нужно писать все в одном триггере
Плюсы SimpleButton:
  • нет ограничении за пределах 4:3 экрана. Могу создавать по бокам кнопку. по ней можно щелкнуть, запустив FRAMEEVENT_CONTROL_CLICK.
  • Отсутствие залипание клавиатуры. В функциональных фреймах: при нажатии на кнопку-фрейм (button, gluebutton) фокус клавиатуры отключается, возможна игра при нажатии на фрейм думает, что эта кнопка не отпущена, и поэтому клавиатуру не переключают. И нам приходилось вручную отпускать кнопку. Однако, решение не очень хорошое, можно дико много кликать по кнопке, то экран игры и клава перестает работать. У SimpleButton не наблюдается такие залипания.
Недостаток SimpleButton - ограничение.
  • отсутствие динамичности кнопки. функциональные дочерние элементы статичны и недоступны для BlzGetFrameByName, как мы привыкли в обычных кнопках. что делает их плохими для кнопок с иконками. что хочу я сказать этим? а вот что: Динамически изменять изображения для кнопок не получится. Короче, вы не сможете изменять тексты и текстуры, зарегистрированные событиями в fdf-files. Это так, и поэтому печально. Вот с обычными кнопками button, gluebutton такое не будет происходить. Это только SimpleButton так. Об этом можете прочесть ниже про SimpleButton. Короче, это значит, что каждый фрейм нужно записывать в fdf-file, каждой кнопке пути текстур нужно прописывать, и вызывать оттуда. Если хотите сделать уникальную кнопку. Можно вызвать иконку кнопки, но саму текстуру нужно записать в тело фрейма кнопки (т.е. эту текстуру не нужно регистрировать события). Можно схитрить этим способом, и поверх вешать текстуру-слоя.
  • малое число событии - ограничение. У SimpleButton можно зарегистрировать одно событие - событие клика FRAMEEVENT_CONTROL_CLICK. Обычные рамочные кнопки button, gluebutton имеют гораздо больше функционала: и имеют больше событии. Однако, я не вижу недостатка: обычная кнопка имеет события нажатия / отпускания кнопки, события клика, и событие наведения мыши (вход/выход). события нажатия кнопки FRAMEEVENT_MOUSE_DOWN не работает у обычных фреймов. события клика/отпускания кнопки - работает при отпускании, просто клик раньше срабатывает. А события наведения мыши вход/выход можно сделать самим с помощью подсказки tooltip через специальное действие BlzFrameSetTooltip(frame, tooltip). На кнопку вешаем simpleframe и у вас будет работать.
  • нет наследования. simpleframes не наследует параметры родителя. Таким фреймам как SimpleFrames нельзя задать родителя или ребенка. Аналогично, и другие фреймы не могут быть детьми/родителями для SimpleFrames. Кто-то теперь может сказать: «но я создал КНОПКУ для SIMPLEFRAME в формате fdf или я создал КНОПКУ для КНОПКИ во время игры». Да, вы это сделали, но при более точной проверке, сравнив HandleIds родителей нового фрейма и выбранного, можно увидеть, что они не совпадают, в обоих случаях вы видите handleIds разных фреймов, я называю это замещающий родитель. Когда это происходит, сначала можно задаться вопросом, почему не применяются видимость и другое поведение родителей. Но причина: желаемый родитель не является настоящим родителем, поэтому потомок не копирует видимость / альфа … Это очень важно, возможно вам не понять почему так зациклился на этом? приведем обычный пример: например вы создаете контейнер с кнопками (группа кнопок), при создании фреймов завязываете все кнопки на родителя. Вы создали контейнер кнопок. Теперь нам не нужно взаимодействовать со всеми кнопками, достаточно прятать родителя, и с ним одновременно прячут и потомков. но с simpleframes этого может не произойти, тк они не наследуют параметры.
Это не значит, что simpleframe - плохое решение. Я вот нашел в нем очень хорошее применение. Она решает проблему залипания функциональных кнопок. Нашел способ заменять статичные изображения. Ну об этом поговорим попозже в следующих уроках. Щас мы рассматриваем функциональные кнопки. А simple frames решено рассмотреть позже.

BUTTON и TEXTBUTTON /GLUEBUTTON и GLUETEXTBUTTON

Функциональные кнопки в отличии от SimpleButtons используется в главном меню интерфейса. Практически большая часть по записям fdf-files.
BUTTON и TEXTBUTTON - это просто обычные кнопки, все зависит от настроек fdf-files. Я смотрел в fdf-files, оказывается BUTTON используется в качестве
  • полосы/ползунка для scrollbar/slider. В качестве ползунка или крутящей полосы
  • кнопок прокрутки ползунка/полосы для scrollbar/slider (больше>, меньше <)
  • кнопок для контекстного меню (PopupMenu)
кнопка button чаще всего используется для таких вот мелких работ
TEXTBUTTON чаще используется как кнопка с текстом, в менюшках часто используется в панелях меню как шаблоны. А также в названиях PopupMenu
Есть еще GlueButton - этот тип кнопки ничем не отличается от Button. Они могут использовать одни и те же функции. Можно даже для GLUEBUTTON использовать шаблоны BUTTON, или наоборот. По источнику все типы фреймов с приставкой “GLUE” при создании BlzCreateFrame имеют встроенные звуки откликов (при нажатии кнопки у вас проигрывает звук клика). Также у кнопок GLUEBUTTON и GLUETEXTBUTTON в отличии от BUTTON и TEXTBUTTON задают горячие клавиши, присутствует всплывающий текст/описание (TOOLTIP).
BUTTON применяется в типах POPUMENU, SCROLLBAR для создании маленьких кнопок-стрелок, контекстного меню и пр. Смотрю fdf-files и вижу, что создаются именно там кнопка BUTTON. А TEXTBUTTON создаются в главном меню. Видимо, там где не нужны кнопки со звуком клика и горячие клавиши/подсказки, эти легко нашли свое применение. Так они во всем похожи. Возможно у них отличаются события, но я пока не знаю точно.
Пока что BUTTON и TEXTBUTTON можно придать точно такие данные, как и у GlUE. Пробовал в fdf-file Button придать точно такие же настройки, как и у GlueButton. TextButton точно также взял настройки от GlueTextButton. По правде говоря, можно использовать в качестве шаблона TextButton или GlueTextButton на все, просто в текстовых кнопках вместо изображения фон. Если хотите сделать кнопку с изображением без текста, достаточно создать фрейм с шаблоном TextButton/GlueTextButton, у вас создается абсолютно пустой фрейм с фоном шаблона без текста, фон можно заменить => задать изображение, нужен текст => задаете текст. Короче, текстовые шаблоны очень удобны для кнопок.
Помните, что настройки бывают разными для разных типов фреймов, и берутся в fdf-files. Еще нам нужна ползунок-кнопка, мы задаем настройки fdf-file. Если нужны кнопки прокрутки ползунка (> или <) для Slider, тоже нужно создавать шаблоны.

Давайте научимся создавать триггерно BUTTON и TEXTBUTTON

кратко: мы разберем с триггерными нативками фреймов, и попрактикуемся создавать кнопку триггерно. Это очень важно знать. Даже имея fdf-files, мы все равно вынуждены прибегать триггерам. Просто fdf-files помогают нам, облегчая код. Но некоторые люди предпочитают чисто использовать код: jass или lua-код. Не все любят возиться с импортом. Даже я не люблю импорт, постоянно вынужден открывать, сохранять, переносить. И даже можете ошибиться, или что-то забыть. Тем более функционал fdf-файлов довольно не привычен, и что-то может не работать, потому что не знаете из-за чего. Однако, есть люди, которые сидят в старых патчах, и там используют мемхаки, и вынуждены использовать fdf-импорт для работы с frames из-за ограниченного функционала.

Функции создания фреймов

Warcraft 3 V1.31 предоставил 3 natives для создания фреймов.
  • BlzCreateSimpleFrame, создающая только и только SimpleFrames из fdf-шаблона по имени. SimpleFrames всегда работают импортом. Трудно им без импорта, нет нужных готовых шаблонов в архивах или нужного функционала в триггерах, чтобы раз и настроил в коде.
  • BlzCreateFrame создает функциональный фрейм (не simpleframe) из fdf-шаблона по имени. И полностью копирует параметры, заданные в fdf-file. Можно использовать эту функцию для создания обычных фреймов.
  • BlzCreateFrameByType - это уникальная функция создания фрейма. Она может создать так и simpleframes, так и функциональных. можно указать подробные параметры, в ней можно указать тип. Именно тип определяет фрейм. Задать фрейму собственное имя, которое может помочь при дебагах или можно обратиться по имени. Так еще и указать fdf-шаблон. Обычно можно и не указывать fdf-шаблон, тогда вы создадите фрейм-пустышку, используя станд шаблон. Эта функция поэтому и уникальна тем, что всегда помогает создавать пустые фреймы чисто кодом, в отличии от двух других. В первых двух функциях часто бывает не всегда работают параметры, указанные в fdf-file. BlzCreateFrameByType очень удобна, тк ей удобнее создавать пустые новые фреймы с нужным типом, но и обратное, шаблоны тоже могут не работать. Если хочется скопировать какие то параметры из шаблона, то это можно не всегда работать. Поэтому проще использовать BlzCreateFrame или BlzCreateSimpleFrame
native BlzCreateFrame takes string name, framehandle owner, integer priority, integer createContext returns framehandle
native BlzCreateSimpleFrame takes string name, framehandle owner, integer createContext returns framehandle
native BlzCreateFrameByType takes string typeName, string name, framehandle owner, string inherits, integer createContext returns framehandle

BlzCreateFrame

BlzCreateFrame не может создавать frame как SimpleFrames, которые определены в fdf. Большинству этих фреймов можете прикреплять события Frameevents, и они могут делать множество вещей. В большинстве случаев вам следует использовать это. Давайте поясним аргументы:
string name
этот аргумент берет строковое название фреймов из fdf-files, которые подключены в игре
это имя фрейма, который вы хотите создать, вы должны взять его из fdf. Это 2-е слово в "name" после типа фрейма
Пример:
в fdf-files вы создали frame
//GLUEBUTTON - тип фрейма, MyButton - название фрейма

Frame "GLUEBUTTON" "MyButton" {
   //ваша настройка фрейма 
}
В триггерах в качестве name вы должны обратиться как “MyButton”
framehandle owner
родитель фрейма. при создании фрейма всегда указывают родителя фрейма. Фрейм наследует состояние родителя. Иначе, он у вас может не создаться. Нужно найти такого родителя, обычно мы берем дефолтные фреймы. Однако, нужна найти среди дефолтных правильных, у неправильных он может не создаться или не существует
integer priority
No Idea, значение не должно быть отрицательным.
Предположение: возможно, приоритет влияет на FrameLevel когда два фрейма находятся в одном уровне. Но не тестировал.
createContext
какой индекс в хранилище фреймов займет новый фрейм, и его дочерний элемент. Доступ к хранилищу кадров осуществляется с помощью BlzGetFrameByName. Может быть любым целым числом.
Короче, вы указываете родителя, а вместе с ним и Context. Этот index можно использовать для обращения. Как потомку, так и имени BlzGetFrameByName. Но когда создается новый с таким же именем, то он занимает место старого. И тогда сложно будет к нему обратиться.

Blz​Create​Simple​Frame


BlzCreateSimpleFrame используется для создания SimpleFrames, определенных в fdf. SimpleFrames имеет в названии типа «SIMPLE». Большинство SimpleFrames не могут иметь зарегистрированных фрейм-событий. Их создание мало чем отличается от создания обычных фреймов.
Аргументы как в BlzCreateFrame, но упускается из виду приоритет

Blz​Create​Frame​By​Type

BlzCreateFrameByType создает фрейм с новым желаемым именем string name. Нужно определить, какой тип string typeName и из какого фрейма string inherits вы берете данные.
Наследование работает для фреймов, которые можно создать с помощью BlzCreateFrame / BlzCreateFrame, но там приходилось использовать готовые fdf-files. Для каждого фрейма приходилось прописывать новый frame в fdf-file. Там в fdf-files приходилось имя писать, шаблоны применять (если нужно было).
BlzCreateFrameByType от этого свободен, он способен создать фрейм с нужным названием, используя готовый шаблон из fdf-files или иметь фрейм из fdf-file с другим названием.
лучше всего подходит, когда вы хотите создать пустой фрейм типа x или хотите иметь фрейм y (из fdf) с другим именем. Пример:
Создает кнопку, которая ведет себя как «ScriptDialogButton», но будет называться «MyButton».
local gameUI = BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI, 0)
BlzCreateFrameByType("GLUETEXTBUTTON", "MyButton", gameUI, "ScriptDialogButton", 0)

Разбор fdf-скриптов с наследованием TextButton

Можно создавать только MainFrames. Я называю фреймы MainFrames, если у них есть FrameHead (заголовок фрейма) за пределами любого другого FrameBody (тело фрейма). (Простые) фреймы должны быть загружены с помощью tocs (fdf), чтобы их можно было создавать.
MainFrame - обычно, это главные фреймы, которые создаются снаружи. Внутри блока (тела фрейма) можно создать еще фреймы (потомки).
fdf код
//MainFrame MyBackdrop
Frame "BACKDROP" "MyBackdrop" { //FrameHead заголовок
//тело главного фрейма

   Frame "GLUEBUTTON" "MyButton" {
    //тело фрейма MyButton
   }

} 
Заметка: Если я захочу создать “MyButton”, то я не могу создавать почему-то потомков. Только MainFrames создаются нормально.
Обычно все фреймы используют готовые шаблоны (templates), наследуя их характеристики. К примеру, выше мы прописали триггерный код, наследуя параметры кнопки ScriptDialogButton. Нужно заглянуть в fdf-file, чтобы понять о чем. Этот фрейм лежит в файле "UI\FrameDef\UI\EscMenuTemplates.fdf"
fdf-код
IncludeFile "UI\FrameDef\UI\EscMenuTemplates.fdf", //подключаем файл

Frame "GLUETEXTBUTTON" "ScriptDialogButton" INHERITS WITHCHILDREN "EscMenuButtonTemplate" { //наследуем данные шаблона GlueTextButton EscMenuButtonTemplate
    UseActiveContext,
    ButtonText "ScriptDialogButtonText",
    Frame "TEXT" "ScriptDialogButtonText" INHERITS "EscMenuButtonTextTemplate" { //наследуем данные шаблона Text EscMenuButtonTextTemplate
        Text "",
    }
} 
Примечание: Довольно забавно получается, мы в триггере создали фрейм “MyButton”, который полностью копирует/наследует данные фрейма “ScriptDialogButton”. А оказывается фрейм “ScriptDialogButton” наследует/копирует данные GlueTextButton EscMenuButtonTemplate (в этом фрейме лежит информация картинки) и Text EscMenuButtonTextTemplate (в этом фрейме лежит инфа о тексте: шрифт, цвет, выравнивание и прочее)
В скрипте выше мы привязываем фрейм TEXT к нашей кнопке GLUEBUTTON с помощью fdf-функции ButtonText. ButtomText позволит кнопке изменять текст с помощью BlzFrameSetText(frame, "text")
fdf-код
ButtonText "ScriptDialogButtonText",
Frame "TEXT" "ScriptDialogButtonText" INHERITS "EscMenuButtonTextTemplate" {
   Text "",
}
Кто хочет посмотреть, что написано в этих фреймах. То сюда скинул:
шаблон GlueTextButton EscMenuButtonTemplate
fdf код
//включенный backdrop (активный фон)
Frame "BACKDROP" "EscMenuButtonBackdropTemplate" {
    DecorateFileNames,
    BackdropTileBackground,
    BackdropBackground  "EscMenuButtonBackground",
    BackdropCornerFlags "UL|UR|BL|BR|T|L|B|R",
    BackdropCornerSize  0.0125,
    BackdropBackgroundSize  0.256,
    BackdropBackgroundInsets 0.005 0.005 0.005 0.005,
    BackdropEdgeFile  "EscMenuButtonBorder",
}


//backdrop при клике мыши по кнопке
Frame "BACKDROP" "EscMenuButtonPushedBackdropTemplate" {
    DecorateFileNames,
    BackdropTileBackground,
    BackdropBackground  "EscMenuButtonPushedBackground",
    BackdropCornerFlags "UL|UR|BL|BR|T|L|B|R",
    BackdropCornerSize  0.0125,
    BackdropBackgroundSize  0.256,
    BackdropBackgroundInsets 0.005 0.005 0.005 0.005,
    BackdropEdgeFile  "EscMenuButtonPushedBorder",
}


//выключенный backdrop пример при паузе
Frame "BACKDROP" "EscMenuButtonDisabledBackdropTemplate" {
    DecorateFileNames,
    BackdropTileBackground,
    BackdropBackground  "EscMenuButtonDisabledBackground",
    BackdropCornerFlags "UL|UR|BL|BR|T|L|B|R",
    BackdropCornerSize  0.0125,
    BackdropBackgroundSize  0.256,
    BackdropBackgroundInsets 0.005 0.005 0.005 0.005,
    BackdropEdgeFile  "EscMenuButtonDisabledBorder",
}


//backdrop 
Frame "BACKDROP" "EscMenuButtonDisabledPushedBackdropTemplate" {
    DecorateFileNames,
    BackdropTileBackground,
    BackdropBackground  "EscMenuButtonDisabledPushedBackground",
    BackdropCornerFlags "UL|UR|BL|BR|T|L|B|R",
    BackdropCornerSize  0.0125,
    BackdropBackgroundSize  0.256,
    BackdropBackgroundInsets 0.005 0.005 0.005 0.005,
    BackdropEdgeFile  "EscMenuButtonDisabledPushedBorder",
}

Frame "BACKDROP" "EscMenuControlBackdropTemplate" {
    DecorateFileNames,
    BackdropTileBackground,
    BackdropBackground  "EscMenuEditBoxBackground",
    BackdropCornerFlags "UL|UR|BL|BR|T|L|B|R",
    BackdropCornerSize  0.0125,
    BackdropBackgroundSize  0.256,
    BackdropBackgroundInsets 0.005 0.005 0.005 0.005,
    BackdropEdgeFile  "EscMenuEditBoxBorder",
    BackdropBlendAll,
}

Frame "HIGHLIGHT" "EscMenuButtonMouseOverHighlightTemplate" {
    DecorateFileNames,
    HighlightType "FILETEXTURE",
    HighlightAlphaFile "EscMenuButtonMouseOverHighlight",
    HighlightAlphaMode "ADD",
}

Frame "GLUETEXTBUTTON" "EscMenuButtonTemplate" {
    Width 0.228,
    Height 0.035,
    ControlStyle "AUTOTRACK|HIGHLIGHTONMOUSEOVER",
    ButtonPushedTextOffset 0.002f -0.002f,
    
      //включенный backdrop
    ControlBackdrop "ButtonBackdropTemplate",
    Frame "BACKDROP" "ButtonBackdropTemplate" INHERITS "EscMenuButtonBackdropTemplate" {
    }

      //backdrop при нажатии/клике кнопки
    ControlPushedBackdrop "ButtonPushedBackdropTemplate",
    Frame "BACKDROP" "ButtonPushedBackdropTemplate" INHERITS "EscMenuButtonPushedBackdropTemplate" {
    }

      //выключенный backdrop такое происходит во время паузы
    ControlDisabledBackdrop "ButtonDisabledBackdropTemplate",
    Frame "BACKDROP" "ButtonDisabledBackdropTemplate" INHERITS "EscMenuButtonDisabledBackdropTemplate" {
    }

      //backdrop при отпускании кнопки
    ControlDisabledPushedBackdrop "ButtonDisabledPushedBackdropTemplate",
    Frame "BACKDROP" "ButtonDisabledPushedBackdropTemplate" INHERITS "EscMenuButtonDisabledPushedBackdropTemplate" {
    }

      //когда наводят мышью на кнопку, она подсвечивается
    ControlMouseOverHighlight "ButtonMouseOverHighlightTemplate",
    Frame "HIGHLIGHT" "ButtonMouseOverHighlightTemplate" INHERITS "EscMenuButtonMouseOverHighlightTemplate" {
    }
}
Обратится к этим потомкам можно как:
lua код - первый способ BlzFrameGetChild
обратиться как к потомкам. Тут используют номер index. это зависит от того в каком порядке они были указаны в fdf-file в шаблоне. В данном случае они просто создаются вместе с родителем. Еще можно создать триггерно триггером пример BlzCreateFrame и др. Главное указать родителя в функции создания. Число потомков у родителя увеличивается. Проверить можно BlzFrameGetChildrenCount(parent)
local  button = BlzCreateFrame("EscMenuButtonTemplate", GameUI, 0,0)
local texture1 =BlzFrameGetChild(button, 0) -- текстура ButtonBackdropTemplate
local texture2 =BlzFrameGetChild(button, 1) -- текстура ButtonPushedBackdropTemplate
local texture3 =BlzFrameGetChild(button, 2) -- текстура ButtonDisabledBackdropTemplate
local texture4 =BlzFrameGetChild(button, 3) -- текстура ButtonDisabledPushedBackdropTemplate
local highlight5 =BlzFrameGetChild(button, 4) -- эффект ButtonMouseOverHighlightTemplate
lua код - первый способ BlzGetFrameByName
Обычно в функции fdf-file у потомков может быть указано UseActiveContext , что означает, что они наследуют Contex родителя. Пример у родителя contex равен 2, то и у потомков будет равен 2.
в триггерах при создании можно указать CreateContex.
В данном случае, у нас внизу 0. Тк я не сильно заморачивался с этим для примера. ДА и сам не люблю с этим возиться. Не было такой практики.
так и к именным частям (при условии если родитель button будет создан):
local  button = BlzCreateFrame("EscMenuButtonTemplate", GameUI, 0,0)
local  texture1 = BlzGetFrameByName("ButtonBackdropTemplate",0)
local  texture2 = BlzGetFrameByName("ButtonPushedBackdropTemplate",0)
local  texture3 = BlzGetFrameByName("ButtonDisabledBackdropTemplate",0)
local  texture4 = BlzGetFrameByName("ButtonDisabledPushedBackdropTemplate",0)
local  highlight5 = BlzGetFrameByName("ButtonMouseOverHighlightTemplate",0)
данные шаблона Text
fdf-file
Frame "TEXT" "EscMenuButtonTextTemplate" {
    DecorateFileNames,
    FrameFont "EscMenuTextFont", 0.013, "",
    FontJustificationH JUSTIFYCENTER,
    FontJustificationV JUSTIFYMIDDLE,    
    FontJustificationOffset 0.0 -0.002,
    FontFlags "FIXEDSIZE",
    FontColor 0.99 0.827 0.0705 1.0,
    FontHighlightColor 1.0 1.0 1.0 1.0,
    FontDisabledColor 0.5 0.5 0.5 1.0,
    FontShadowColor 0.0 0.0 0.0 0.9,    
    FontShadowOffset 0.002 -0.002,    
}
На этом разъяснения закончились. Пошли к практике

GLUETEXTBUTTON (кнопка с текстом) с fdf

источник - тут можно скачать карту и посмотреть
В этом примере мы создаем интерактивную кнопку с текстом. Мы используем для кнопки это определение фрейм. Фрейм, который мы хотим создать, загружается по умолчанию. Его имя - «ScriptDialogButton», он принимает данные из «EscMenuButtonTemplate».
код
Frame "GLUETEXTBUTTON" "ScriptDialogButton" INHERITS WITHCHILDREN "EscMenuButtonTemplate" {
    UseActiveContext,
    ButtonText "ScriptDialogButtonText",
   Frame "TEXT" "ScriptDialogButtonText" INHERITS "EscMenuButtonTextTemplate" {
       Text "",
   }
}
Давайте начнем создание. Мы создаем фрейм на основе имени "ScriptDialogButton", скажем, ORIGIN_FRAME_GAME_UI является его родительским элементом и использует createcontext 0. Также мы устанавливаем размер и положение этого нового созданного фрейма, используя кнопку
jass код
function CreateButton1 takes nothing returns nothing
   local framehandle mainButton  = BlzCreateFrame("ScriptDialogButton", BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI, 0), 0,0)
   call BlzFrameSetSize(mainButton, 0.12, 0.05)
   call BlzFrameSetAbsPoint(mainButton, FRAMEPOINT_CENTER, 0.3,0.3)
endfunction
Вот у нас получилась кнопка. На нее можно навести мышь, текстура подсвечивается (эта кнопка унаследовала данные из кнопки «EscMenuButtonTemplate»). Саму текстуру кнопки можно перемещать и нажимать. Разве что нет текста, щас это исправим. Давайте дадим этой кнопке текст. Мы получаем доступ к дочернему фрейму, управляющему ButtonText «ScriptDialogButtonText».
Пример задания текста кнопки
jass код
function CreateButton2 takes nothing returns nothing
   local framehandle mainButton  = BlzCreateFrame("ScriptDialogButton", BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI,0), 0,0)
   local framehandle buttonText = BlzGetFrameByName("ScriptDialogButtonText",0)  //Get The buttons textChild
   call BlzFrameSetSize(mainButton, 0.12, 0.05)
   call BlzFrameSetAbsPoint(mainButton, FRAMEPOINT_CENTER, 0.3,0.3)
   call BlzFrameSetText(buttonText, "My ButtonText")
endfunction
Теперь кнопка стала выглядеть лучше с текстом. Но мы хотим, чтобы какой-то код запускался при нажатии кнопки. Чтобы мы получали какую то реакцию. Для этого на фрейм вешают событие клика.
jass код
function ButtonCallBack takes nothing returns nothing
   call BJDebugMsg(GetPlayerName(GetTriggerPlayer()) + " pressed: "+ BlzFrameGetText(BlzGetTriggerFrame()))
endfunction

function CreateButton3 takes nothing returns nothing
   local trigger trig = CreateTrigger() //The Trigger Handling the Frameevent
   local framehandle mainButton  = BlzCreateFrame("ScriptDialogButton", BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI,0), 0,0)
   call BlzFrameSetSize(mainButton, 0.12, 0.05)
   call BlzFrameSetAbsPoint(mainButton, FRAMEPOINT_CENTER, 0.3,0.3)
  call BlzFrameSetText(mainButton, "My ButtonText") //Cause of a line in scriptDialogButton 'ButtonText "ScriptDialogButtonText",' one can directly read/write text of the button.

   call BlzTriggerRegisterFrameEvent(trig, mainButton, FRAMEEVENT_CONTROL_CLICK)
   call TriggerAddAction(trig, function ButtonCallBack) //Function ButtonCallBack will run when mainButton is clicked
endfunction
автор хайва: Как видно из этого фрагмента кода CreateButton3, фрейм Textchild никак не создается, но он все же существует и работает. Причина этого - прописанная строка в fdf-file шаблона 'ButtonText "ScriptDialogButtonText",'.

GLUEBUTTON (кнопка с изображением) с fdf

Как создать простую кнопку? надо задавать различные настройки для кнопки при переключении. Короче, нужно задать разные текстуры backdrops при разных реакциях игры. Также нужна подсветка Highlight. Возможна пригодиться ли подсказка ToolTip при наводке мыши на кнопку? А также возможна и задать горячие клавиши для кнопки.
Кнопки - это мощные и полезные UI-фреймы. Кнопка в Warcraft 3 - это всего лишь место на экране, которое можно нажимать. Нажатие может отправить звуковую обратную связь (GLUE, созданную с помощью BlzCreateFrame), и можно поймать нажатие с помощью TriggerEvent, который позволяет выполнять код. Но если кнопка - это только пространство, доступное для нажатия, откуда берутся отображаемое изображение backdrop, текст TEXT и выделение HIGHLIGHT?
Каждый их этих фреймов (backdrop,Text,HighLight) являются потомками BUTTON / GLUEBUTTON. Такие потомки имитируют размер и положение родителя.
Есть Backdrop_1 и цвет Text, который отображается, когда Button включен. Другой Backdrop_2, когда BUTTON отключен (пример игра находится в режиме паузы). Также цвет Text тоже меняется. И так далее каждый из них является потомком Button. Об этом можно посмотреть тут мы разбирали fdf-file и должно быть понятно как все работает.
fdf-код
IncludeFile "UI\FrameDef\UI\EscMenuTemplates.fdf",

Frame "TEXT" "MyButtonTextTemplate" {
    DecorateFileNames,
    FrameFont "EscMenuTextFont", 0.013, "",
    FontJustificationH JUSTIFYCENTER,
    FontJustificationV JUSTIFYMIDDLE,    
    FontJustificationOffset 0.0 -0.002,
    FontFlags "FIXEDSIZE",
    FontColor 0.99 0.827 0.0705 1.0,
    FontHighlightColor 1.0 1.0 1.0 1.0,
    FontDisabledColor 0.5 0.5 0.5 1.0,
    FontShadowColor 0.0 0.0 0.0 0.9,    
    FontShadowOffset 0.002 -0.002,    
}


Frame "BACKDROP" "MyControlBackdropTemplate" {
    DecorateFileNames,
    BackdropTileBackground,
    BackdropBackground  "EscMenuEditBoxBackground",
    BackdropCornerFlags "UL|UR|BL|BR|T|L|B|R",
    BackdropCornerSize  0.0125,
    BackdropBackgroundSize  0.256,
    BackdropBackgroundInsets 0.005 0.005 0.005 0.005,
    BackdropEdgeFile  "EscMenuEditBoxBorder",
    BackdropBlendAll,
}


Frame "GLUETEXTBUTTON" "MyButtonTemplate" {
    Width 0.228,
    Height 0.035,
    ControlStyle "AUTOTRACK|HIGHLIGHTONFOCUS|HIGHLIGHTONMOUSEOVER",
    ButtonPushedTextOffset 0.002f -0.002f,
    
    //включенный backdrop (активный фон)
    ControlBackdrop "MyButtonBackdropTemplate",
    Frame "BACKDROP" "MyButtonBackdropTemplate" {
        DecorateFileNames,
        BackdropTileBackground,
        BackdropBackground  "EscMenuButtonBackground",
        BackdropCornerFlags "UL|UR|BL|BR|T|L|B|R",
        BackdropCornerSize  0.0125,
        BackdropBackgroundSize  0.256,
        BackdropBackgroundInsets 0.005 0.005 0.005 0.005,
        BackdropEdgeFile  "EscMenuButtonBorder",
    }

      //backdrop при нажатии/клике кнопки
    ControlPushedBackdrop "MyButtonPushedBackdropTemplate",
    Frame "BACKDROP" "MyButtonPushedBackdropTemplate" {
        DecorateFileNames,
        BackdropTileBackground,
        BackdropBackground  "EscMenuButtonPushedBackground",
        BackdropCornerFlags "UL|UR|BL|BR|T|L|B|R",
        BackdropCornerSize  0.0125,
        BackdropBackgroundSize  0.256,
        BackdropBackgroundInsets 0.005 0.005 0.005 0.005,
        BackdropEdgeFile  "EscMenuButtonPushedBorder",
    }

      //выключенный backdrop такое происходит во время паузы
    ControlDisabledBackdrop "MyButtonDisabledBackdropTemplate",
    Frame "BACKDROP" "MyButtonDisabledBackdropTemplate" {
        DecorateFileNames,
        BackdropTileBackground,
        BackdropBackground  "EscMenuButtonDisabledBackground",
        BackdropCornerFlags "UL|UR|BL|BR|T|L|B|R",
        BackdropCornerSize  0.0125,
        BackdropBackgroundSize  0.256,
        BackdropBackgroundInsets 0.005 0.005 0.005 0.005,
        BackdropEdgeFile  "EscMenuButtonDisabledBorder",
    }

      //выключенный backdrop при нажатии кнопки
    ControlDisabledPushedBackdrop "MyButtonDisabledPushedBackdropTemplate",
    Frame "BACKDROP" "MyButtonDisabledPushedBackdropTemplate" {
        DecorateFileNames,
        BackdropTileBackground,
        BackdropBackground  "EscMenuButtonDisabledPushedBackground",
        BackdropCornerFlags "UL|UR|BL|BR|T|L|B|R",
        BackdropCornerSize  0.0125,
        BackdropBackgroundSize  0.256,
        BackdropBackgroundInsets 0.005 0.005 0.005 0.005,
        BackdropEdgeFile  "EscMenuButtonDisabledPushedBorder",
    }

      //когда наводят мышью на кнопку, она подсвечивается
    ControlMouseOverHighlight "MyButtonMouseOverHighlightTemplate",
    Frame "HIGHLIGHT" "MyButtonMouseOverHighlightTemplate" {
        DecorateFileNames,
        HighlightType "FILETEXTURE",
        HighlightAlphaFile "EscMenuButtonMouseOverHighlight",
        HighlightAlphaMode "ADD",
    }
    
    //эффект фокусируется на фрейме, при повторном нажатии не по нашему фрейму фокусировка снимается
    ControlFocusHighlight "MyButtonFocusHighlightTemplate",
    Frame "HIGHLIGHT" "MyButtonFocusHighlightTemplate" {
        HighlightType "FILETEXTURE",
        HighlightAlphaFile "UI\Widgets\Glues\GlueScreen-CampaignButton-KeyboardHighlight.blp",    
        HighlightAlphaMode "ADD",
    }
}
Вот изображение текста внутри fdf-файла GLUETEXTBUTTON, содержащего больше всего, когда не все сильно связанные дочерние фреймы. В Image: текст, связанный с Childframes, находится в цветном поле. Также есть комментарии прямо к рамкам разным цветом.
Об этом мы разбирались с Control. Короче, что хочу сказать, можно взять шаблон GLUETEXTBUTTON но без текста, он тоже будет работать с кнопками с изображением.

Не используем не надобные ChildFrames

кратко: автор хайва предлагает укоротить в fdf-file код фрейма-кнопки. Так как кнопка содержит много лишнего. Некоторые параметры кнопки можно убрать (пример свечение при наведении, выделение). К тому же, некоторые из них не работают. Пример, ControlDisabledBackdrop работают во время паузы только у дефолтных фреймов. А вот у самодельных это не работает, заработает в том случает, если вручную кодом отключить BlzFrameSetEnable(button, false). А так это бесполезная штука, не бойтесь. Если не прописывать ControlDisabledBackdrop, то ва зеленые иконки не будут беспокоить.
автор хайва: Дочерние кадры, которые вам не нужны, часто можно пропустить. Таким образом, это все еще действительный GLUEBUTTON: даже с упоминанием только ControlBackdrop, ControlDisabledBackdrop и ControlMouseOverHighlight. Означает текстуру во включенном состоянии, текстуру для отключенного состояния и свечение при наведении курсора мыши.
fdf-код
Frame "GLUEBUTTON" "HeroSelectorButton" {
    Width 0.035,
    Height 0.035,
    ControlStyle "AUTOTRACK|HIGHLIGHTONMOUSEOVER",

    ControlBackdrop "HeroSelectorButtonIcon",
    Frame "BACKDROP" "HeroSelectorButtonIcon" {
    }

    ControlDisabledBackdrop "HeroSelectorButtonIconDisabled",
    Frame "BACKDROP" "HeroSelectorButtonIconDisabled" {
    }

    ControlMouseOverHighlight "HeroSelectorButtonHighLight",
    Frame "HIGHLIGHT" "HeroSelectorButtonHighLight" {
        HighlightType "FILETEXTURE",
        HighlightAlphaFile "UI\Glues\ScoreScreen\scorescreen-tab-hilight.blp",
        HighlightAlphaMode "ADD",
    }
}
Пример создания этой «HeroSelectorButton».
jass код
//Create "HeroSelectorButton", for game UI
    local framehandle buttonFrame = BlzCreateFrame("HeroSelectorButton", BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI, 0), 0, 0)
//By Having created "HeroSelectorButton" also its children are created, this children are accessed using BlzGetFrameByName right after the creation or later one if the slots were not taken by other frames.
    local framehandle buttonIconFrame = BlzGetFrameByName("HeroSelectorButtonIcon", 0)
//Set a Texture
    call BlzFrameSetTexture(buttonIconFrame, "ReplaceableTextures\\CommandButtons\\BTNSelectHeroOn", 0, true)
// Place the buttonFrame to the center of the screen
    call BlzFrameSetAbsPoint(buttonFrame, FRAMEPOINT_CENTER, 0.4, 0.3)
// Give that buttonFrame a size
    call BlzFrameSetSize(buttonFrame, 0.05, 0.05)

Hotkeys in fdf

GLUEBUTTON и GLUETEXTBUTTON могут иметь горячие клавиши, настраивается это прямо в fdf-file. Существует fdf-функция TabFocusPush, которая предоставляет право вводить горячую клавишу. TabFocusPush создает список строк StringList с желаемыми горячими клавишами.
вы можете прописать в списке
fdf код
// The Hotkeys for the Buttons
StringList {
 KeyMyButton1 "1",
 KeyMyButton2 "2",
 KeyMyButton3 "3",
}
Затем в фрейме кнопки прописываем горячую клавишу ControlShortcutKey "StringName". Пример: ControlShortcutKey "KeyMyButton1". fdf-функция ControlShortcutKey не работает без TabFocusPush.
Пример fdf:
fdf код
IncludeFile "UI\FrameDef\UI\EscMenuTemplates.fdf",

// The Hotkeys for the Buttons
StringList {
 KeyMyButton1 "1",
 KeyMyButton2 "2",
 KeyMyButton3 "3",
}

Frame "FRAME" "MyButtonF" {
    Width 0.1,
    Height 0.1,
    TabFocusPush, // Enables ControlShortcutKey for children
    LayerStyle "IGNORETRACKEVENTS", // this Frame itself will not control/Block the Mouse
    Frame "BUTTON" "MyButton1" INHERITS WITHCHILDREN "EscMenuButtonTemplate" {
        ControlShortcutKey "KeyMyButton1", // read "KeyMyButton1" and use it's value as Hotkey
        SetPoint TOP, "MyButtonF", TOP, 0, 0,
    }
    Frame "BUTTON" "MyButton2" INHERITS WITHCHILDREN "EscMenuButtonTemplate" {
        ControlShortcutKey "KeyMyButton2",
        SetPoint TOP, "MyButton1", BOTTOM, 0, 0,
    }
    Frame "BUTTON" "MyButton3" INHERITS WITHCHILDREN "EscMenuButtonTemplate" {
        ControlShortcutKey "KeyMyButton3",
        SetPoint TOP, "MyButton2", BOTTOM, 0, 0,
    }
 
}
Результат файла fdf. Он создает 3 строки (KeyMyButton1, KeyMyButton2, KeyMyButton3) и определяет ФРЕЙМ «MyButtonF», а также 3 дочерних фрейма кнопок «MyButton1», «MyButton2» и «MyButton3». Код Lua для загрузки fdf / toc создает кадры, запускает и события для прослушивания нажатий кнопок.

Keyboard Focus (фокусы с клавиатурой)

кратко: ваш переводчик понял так, есть фокус клавиатуры в игре. В обычной игре фокус клавиатуры подключен к горячим клавишам кнопок, но бывают ситуации, когда горячие клавиши не работают. Пример во время ввода текста в чат, фокус клавиатуры переключен на ввод текста. Это же очевидно же. Но автор упоминает про баги. Это баг blizzard с обычными button и gluebutton, когда нажатая кнопка считается не отпущенной. И нам приходится вручную отпускать кнопку
Примечание: Короче, так понял в игре при нажатии на нашу самодельную кнопку-фрейм происходит потеря фокуса клавиатуры. Видимо внутри игры алгоритм считает, что кнопка-фрейм еще удерживается, и не была отпущена. Тут должно быть две реакции: нажатие и отпускание. Самое странное, что в рефордже ввели зачем-то событие FRAMEEVENT_MOUSE_DOWN , но оно не работает (на хайве посмотрев весь список типов фреймов, там показаны какие события работают у этих типов. я не у одного не видел такого события. А это значит, либо хотели ввести или убрали). А FRAMEEVENT_CONTROL_CLICK и FRAMEEVENT_MOUSE_UP - срабатывают в момент отпускания. Это были так рассуждения. Так происходит залипание кнопок. Об этом обо всем по порядку

Решения

Если создать button в игре, а затем нажать на нее, фокус смещается на нашу кнопку-фрейм. И далее перестает работать нормально клавиатура. Я не могу вызвать горячие клавиши, игра практически не реагирует на клавиатуру, работает только мышка. И только после нажатия ЛКМ по карте/цели сбрасывается фокус. Сбрасывается также нажатием на дефолтные кнопки интерфейса (не самодельные фреймы!!! Видимо в игре прикручено настройка). Это самый стандартный баг в игре, в игре просто сломана настройка, и игра считает, что фрейм еще удерживают.
Нашел три решения того, чтобы фокус стал работать адекватно:
1. сброс через отключение/включение
Сбросить можно только, если что-то сделав с фреймом, а именно отключить его:
CanFight нашел хороший и простой способ решить проблему с фокусом клавиатуры: внутри нажатых кнопок обратный вызов отключает и включает кнопку, затем она считается нажатой, и кнопка теряет фокус.
call BlzFrameSetEnable(BlzGetTriggerFrame(), false) //disable the clicked button
call BlzFrameSetEnable(BlzGetTriggerFrame(), true) //enable it again.
переводчик: Однако, такое решение не очень хорошое. Это работает, если игрок нажимает на фрейм один или несколько раз. Если дико много кликать по фрейму, то у вас собьется фокус и работа триггера с отключением. И поэтому для динамичной игры использовать такое не рекомендую. Я как-то сделал игру на таких фреймах, а потом сильно пожалел.
Это хорошо работает, если вызывать "самодельное меню с кнопками". У вас от этого не сильно что-то теряется. Если родителя самодельного меню выключить и убрать, то все работает. Кстати, если родителя отключить и спрятать, то тоже самое будут отключены и выключены потомки. Фокус норм отображается
2. заменить gluebutton,button, gluetextbutton,textbutton и др подобные фреймы на simplebutton
У SimpleButton нет никаких залипании, в отличии от Button, GlueButton. Стоит подумать, а может нам лучше изучить SimpleButton? Но имеет ряд неудобств.
В отличии от обычных "менюшных" кнопок симпфреймы это являются игровыми фреймами, и залипании не наблюдается, сколько бы не кликал. Однако, имеет кучу проблем, но об этом я расскажу в след уроках в разделах симплфрейм и симплбуттон
заменить gluebutton,button, gluetextbutton,textbutton и др подобные фреймы на TEXT и похожие. У таких фреймов нет регистра на клавиши клавы
Еще можно заменить BUTTON или GLUEBUTTON на TEXT (и др похожие, если есть такие). фрейм TEXT хоть и является текстовым, он еще является и кликабельным. Можно задать невидимую рамку с размерами width, height. Имеет события клика и наведения, и прокрутки колесиком. Не вызывает залипания при нажимании на TEXT. Возможна, Button, GlueButton резервируют клавиши клавиатуры, даже, если не указаны в fdf-file. в TEXT подобного регистра на клавиши клавиатуры нет.
TEXTAREA, TIMERTEXT, SLIDER, SCROLLBAR
Это все фреймы, которые не вызывают залипание после нажатия, тк это не кнопки, то конечно, они не вызовут ниче подобного. У них это проблема отсутствует.
возможна есть еще и другие, но пока не проверены лично
Но есть и другие проблемы. Для таких кнопок нужно много доп фреймов делать. Иконку в качестве backdrop, наведение делать, чтобы просвечивать кнопку.
Хочу еще отметить другую проблему: у таких фреймов событие клика вроде не работает по норм, только событие отпускание фиксируется FRAMEEVENT_MOUSE_UP
Вторая проблема: это событие ловит нажатие и левой, так и правой кнопкой мыши. Короче, пришлось, событием наведения фиксировать, что мышь внутри. И событиями мыши фиксировать нажатие лкм. Такой вот велосипед. В 1.33 патче мне сломали близзард событие наведения

Важный баг ЛКМ, которая сбрасывает кнопку клавы

источник
кратко: Этот баг никак не связан с фреймами. Это отдельный краткий курс. Но вы тоже должны понять работу фокуса клавиатуры. Поэтому я решил это тоже включить. Вдруг вы захотите сделать управление на кнопках клавиатуры W,A,S,D и тому подобное. Вначале я читал одну статью и включил ее сюда, тоже думал, что ниже урок связан с фреймами. Но моя невнимательность сильно запутала. Будьте внимательны
Есть еще один уникальный похожий случай, связанный с левой кнопки мыши и клавиатурой (никак не связан с кнопками-фреймами, просто первый случай с кнопкой-фреймом - там кнопка-фрейм не была отжата, и поэтому клавиатура не работает. Здесь же с ЛКМ и клавой ситуация другая, кнопку клавиатуры удерживали. Но клик ЛКМ по карте просто сбрасывает фокус клавиатуры. И алгоритм не знает, что сбросили). Вот описание с xgm: Если во время нажатия любой кнопки с клавиатуры, нажать левую кнопку мыши (в любом месте экрана), а потом отпустить нажатую клавишу, то события кнопки клавиатуры, что кнопка отжата не произойдет, потребуется нажать кнопку ещё раз (эффект залипания). Об этом рассказано здесь ссылка пример
пример как отслеживать клавиши клавиатуры расписан по ссылке - ссылкапример
function BlzTriggerRegisterPlayerKeyEvent(whichTrigger, whichPlayer, key, metaKey, keyDown)
решение: использование simpleframe

Горячие клавиши

кратко: если вы создали кнопку-фрейм через fdf-шаблон, в которой указана горячая кнопка. Но она может не работать. Тут описано автором, что делать
По какой-то странной причине горячие клавиши не работают (когда они созданы на GAMEUI), начала работы нужно скрыть и показать фрейм-родителя. Тогда горячие клавиши будут реагировать на нажатие кнопки клавиатуры.
Горячие клавиши запускают событие с нажатой клавишей Ctrl, которое не сохраняет фокус клавиатуры, в отличие от случая, когда событие запускается щелчком мыши. Кнопки, добавленные кодом (к родительскому элементу с TabFocusPush,) во время выполнения, не будут использовать ControlShortcutKey, что делает это довольно статичным ( как и большинство в fdf).
Можно было бы использовать OsKeyEvents для чего-то более динамичного.

Содержание
`
ОЖИДАНИЕ РЕКЛАМЫ...