/
Mr.Stalin
/
FOnline-Engine
Обзор
Документация
Войти
/
Mr.Stalin
/
FOnline-Engine
Код
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
Source/Scripting/AngelScript/CoreScripts/Gui.fos
4 851 строка
119 KB
cvet
fix(Grid): adjust cell positioning to prevent scroll offset issues during refresh
3 часа назад
3 часа назад
f60d355
Код
Авторство
О чём код?
namespace Gui { #if CLIENT ///@ Enum GuiScreen None = 0 ///@ Enum CursorType Default = 0 ///@ Enum MessageBoxType None = 0 ///@ Enum MessageBoxType Default ///@ Enum MessageBoxType All ///@ Setting Client bool Gui.MsgboxInvert CursorType Cursor = CursorType::Default; CursorType DraggableCursor = CursorType::Default; ident CursorData; // Anchor styles ///@ Enum AnchorStyle None = 0 ///@ Enum AnchorStyle Left = 1 ///@ Enum AnchorStyle Right = 2 ///@ Enum AnchorStyle Top = 4 ///@ Enum AnchorStyle Bottom = 8 // Dock styles ///@ Enum DockStyle None = 0 ///@ Enum DockStyle Left = 1 ///@ Enum DockStyle Right = 2 ///@ Enum DockStyle Top = 3 ///@ Enum DockStyle Bottom = 4 ///@ Enum DockStyle Fill = 5 ///@ Event Client Game OnScreenChange(bool show, GuiScreen screenNum, string=>any params) ///@ Event Client Game OnConsoleMessage(string message) interface IDropMenu { void Draw(); void MouseDown(MouseButton button); void MouseUp(MouseButton button); bool PreMouseMove(ipos offset); void PostMouseMove(ipos offset); void InputLost(); void Loop(); }; funcdef Screen CreateScreenFunc(); funcdef bool DragAndDropHandler(Object obj, Object? target); // Inheritance // Object // Panel // Screen // Grid // ItemView // Button // CheckBox // RadioButton // Text // TextInput // Console // MessageBox // Callbacks // Object // void OnConstruct() // void OnInit() // void OnShow() // void OnShow(dict<string, any> params) // void OnHide() // void OnAppear() // void OnDisappear() // void OnDraw() // void OnPostDraw() // void OnMove(ipos deltaPos) // void OnMouseDown(MouseButton button) // void OnMouseUp(MouseButton button, bool lost) // void OnMousePressed(MouseButton button) // void OnLMousePressed() // void OnRMousePressed() // void OnMouseClick(MouseButton button) // void OnLMouseClick() // void OnRMouseClick() // void OnMouseMove() // void OnGlobalMouseDown(MouseButton button) // void OnGlobalMouseUp(MouseButton button) // void OnGlobalMousePressed(MouseButton button) // void OnGlobalMouseClick(MouseButton button) // void OnGlobalMouseMove() // void OnInput() // void OnInput(KeyCode key) // void OnInput(string text) // void OnInput(KeyCode key, string text) // void OnGlobalInput(KeyCode key, string text) // void OnActiveChanged() // void OnFocusChanged() // void OnHoverChanged() // void OnDragChanged() // void OnResizeGrid(Object cell, int cellIndex) // void OnDrawItem(Item item, Object cell, int cellIndex) // void OnRefreshText() // CheckBox // void OnCheckedChanged() // ItemView // Item[] OnGetItems() - return all items for display // int OnCheckItem(Item item) - return slot index if UseSorting == false; sorting value if UseSorting == true; < 0 to discard item // API // Object // bool Active // bool ActiveSelf // string Name // ipos Pos // ipos AbsolutePos // isize Size // AnchorStyle Anchor // DockStyle Dock // bool IsDraggable // bool IsNotHittable // bool IsNotCatchable // bool CheckTransparentOnHit // bool FocusGroup // bool IsFocused // bool IsPressed // bool IsHovered // bool IsDragged // int ChildCount // Object Parent // Screen Screen // Grid Grid // int CellIndex // void Init(Object parent) // void Remove() // void SetActive(bool active) // void SetPosition(ipos pos) // void SetPosition(int x, int y) // void SetPosition(string iniKey) // void SetSize(isize size) // void SetSize(int width, int height) // void SetAnchor(AnchorStyle anchorStyles) // void SetDock(DockStyle dockStyle) // void SetColor(ucolor color) // void SetDraggable(bool enabled) // void SetNotHittable(bool enabled) // void SetNotCatchable(bool enabled) // void SetCheckTransparentOnHit(bool enabled) // void SetFocusGroup(bool enabled) // Object? FindMouseHit() // Object? FindHit(ipos pos) // bool IsMouseHit() // bool IsHit(ipos pos) // void GetWholeSize(ipos& centerPos, isize& wholeSize, bool onlyChidren = false) // void Draw(ipos pos) // void Move(ipos deltaPos) // void StartDragging() // void MouseClick(MouseButton button) // void Input(KeyCode key, string text) // Panel FindPanel(string name) // Text FindText(string name) // TextInput FindTextInput(string name) // Button FindButton(string name) // Object Find(string name, bool deepFind = true, int skip = 0) // int Count(string name, bool deepCount = true) // Object GetChild(int index) // Panel : Object // Sprite::Sprite BackgroundImage // SpriteLayout BackgroundImageLayout // void SetBackgroundImage(string imageName, SpriteLayout imageLayout = SpriteLayout::None) // void SetBackgroundImage(hstring imageName, SpriteLayout imageLayout = SpriteLayout::None) // void SetFrameImage(string imageName) // void SetFrameImage(hstring imageName) // void SetCapInsets(int left, int top, int right, int bottom) // void SetCropContent(bool enabled) // void SetAutoScroll(bool ver, bool hor) // int VerticalScrollValue // int HorizontalScrollValue // int VerticalScrollRange // int HorizontalScrollRange // void ModifyScroll(int ver, int hor) // bool CanModifyScroll(int ver, int hor) // void SetScrollValue(int ver, int hor) // Text : Object // string Text (overridable) // int TextFont // ucolor TextColor // ucolor TextColorFocused // int TextFlags // void SetText(string text, int font, int flags) // void SetText(string text) // void SetTextWithResize(string text) // void SetTextFont(int font) // void SetTextFlags(int flags) // void SetTextColor(ucolor color) // void SetTextFocusedColor(ucolor color) // TextInput : Text : Object // int InputLength // bool IsTextPassword // string PasswordChar // void SetInputLength(int length) // void SetInputPassword(string passwordChar) // void SetCarriage(bool enable) // Button : Panel : Object // Sprite::Sprite PressedImage // SpriteLayout PressedImageLayout // Sprite::Sprite HoverImage // SpriteLayout HoverImageLayout // bool IsSwitched // bool IsDisabled // void SetPressedImage(string imageName, SpriteLayout imageLayout = SpriteLayout::None) // void SetPressedImage(hstring imageName, SpriteLayout imageLayout = SpriteLayout::None) // void SetHoverImage(string imageName, SpriteLayout imageLayout = SpriteLayout::None) // void SetHoverImage(hstring imageName, SpriteLayout imageLayout = SpriteLayout::None) // void SetDisabledImage(string imageName, SpriteLayout imageLayout = SpriteLayout::None) // void SetDisabledImage(hstring imageName, SpriteLayout imageLayout = SpriteLayout::None) // void SetSwitch(bool enabled) // void SetCondition(bool enabled) // CheckBox : Button : Panel : Object // bool IsChecked // void SetChecked(bool checked) // RadioButton : CheckBox : Button : Panel : Object // Screen : Panel : Object // GuiScreen Num // bool IsModal // bool IsMultiinstance // bool IsCloseOnMiss // bool IsCanMove // bool IsMoveIgnoreBorders // CursorType[] AvailableCursors // CursorType Cursor // bool IsOnTop // void SetModal(bool enabled) // void SetMultiinstance(bool enabled) // void SetCloseOnMiss(bool enabled) // void SetCanMove(bool enabled, bool ignoreBorders) // void SetAvailableCursors(CursorType[] cursors) // Grid : Panel : Object // string CellPrototype // int GridSize // int Columns // Object[] Cells // void ResizeGrid(int size) // void RefreshContentPositions() // void SetCellPrototype(string name) // void SetGridSize(int size) // void SetColumns(int length) // void SetPadding(ipos pos) // void SetPadding(int x, int y) // MessageBox : Text : Object // string[] MessageTexts // int[] MessageTypes // string[] MessageTimes // MessageBoxType[] DisplayedMessages // bool InvertMessages // void AddMessage(string text, MessageBoxType type = MessageBoxType::Default) // void SetDisplayedMessages(MessageBoxType[] messageTypes) // void ChangeDisplayedMessage(MessageBoxType messageType, bool enable) // void SetInvertMessages(bool invert) // void ClearMessages() // Console : TextInput : Text : Object // string HistoryStorageName // string[] History // int HistoryMaxLength // void Activate() // void Deactivate() // void SendText() // void Toggle() // Automatically manage calls of Activate / Deactivate / SendText // void SetHistoryStorage(string storageName) // void SetHistoryMaxLength(int length) // ItemView : Grid : Panel : Object // int UserData // int UserDataExt // bool UseSorting // Item[] Items // Item? GetItem(int cellIndex) // void Resort() // void SetUserData(int data) // void SetUserDataExt(int data) // void SetUseSorting(bool enable) class Object { bool get_Active() final { return _Active; } bool get_ActiveSelf() final { return _ActiveSelf; } string get_Name() final { return _Name; } ipos get_Pos() final { Object? parent = _Parent; return parent != null ? _AbsolutePos - parent._AbsolutePos : _AbsolutePos; } ipos get_AbsolutePos() final { return _AbsolutePos; } isize get_Size() final { return _Size; } AnchorStyle get_Anchor() final { return _Anchor; } DockStyle get_Dock() final { return _Dock; } ucolor get_Color() final { return _Color; } bool get_IsDraggable() final { return _IsDraggable; } bool get_IsNotHittable() final { return _IsNotHittable; } bool get_IsNotCatchable() final { return _IsNotCatchable; } bool get_CheckTransparentOnHit() final { return _CheckTransparentOnHit; } bool get_FocusGroup() final { return _FocusGroup; } bool get_IsFocused() final { return _IsFocused; } bool get_IsPressed() final { return _IsPressed; } bool get_IsHovered() final { return _IsHovered; } bool get_IsDragged() final { Object? parent = _Parent; return _IsDragged || (parent != null && parent.IsDragged); } int get_ChildCount() final { return _Children.length(); } Object? get_Parent() final { return _Parent; } Screen? get_Screen() final { Object? parent = _Parent; return parent != null ? parent.Screen : cast<Screen>(this); } Grid? get_Grid() final { return _Grid; } int get_CellIndex() final { return _CellIndex; } bool _ActiveSelf; bool _Active; bool _HasOnDraw; bool _NeedDraw; string _Name; ipos _BasePos; isize _BaseSize; ipos _AbsolutePos; isize _Size; AnchorStyle _Anchor; DockStyle _Dock; ucolor _Color; bool _IsDragged; bool _IsDraggable; bool _IsNotHittable; bool _IsNotCatchable; bool _CheckTransparentOnHit; bool _FocusGroup; bool _DeferredMousePressed; bool _IsFocused; bool _IsPressed; bool _IsHovered; MouseButton _PressedButton; ipos _PressedPos; Object? _Parent; Object[] _Children; Grid? _Grid; int _CellIndex; bool _Highlighted; // Callbacks void OnConstruct() { } void OnInit() { } void OnShow() { } void OnShow(dict<string, any> params) { } void OnHide() { } void OnAppear() { } void OnDisappear() { } void OnRemove() { } void OnDraw() { } void OnPostDraw() { } void OnMove(ipos deltaPos) { } void OnMouseDown(MouseButton button) { } void OnMouseUp(MouseButton button, bool lost) { } void OnMousePressed(MouseButton button) { } void OnLMousePressed() { } void OnRMousePressed() { } void OnMouseClick(MouseButton button) { } void OnLMouseClick() { } void OnRMouseClick() { } void OnMouseMove() { } void OnGlobalMouseDown(MouseButton button) { } void OnGlobalMouseUp(MouseButton button) { } void OnGlobalMousePressed(MouseButton button) { } void OnGlobalMouseClick(MouseButton button) { } void OnGlobalMouseMove() { } void OnInput() { } void OnInput(KeyCode key) { } void OnInput(string text) { } void OnInput(KeyCode key, string text) { } void OnGlobalInput(KeyCode key, string text) { } void OnActiveChanged() { } void OnFocusChanged() { } void OnHoverChanged() { } void OnDragChanged() { } void OnResizeGrid(Object cell, int cellIndex) { } void OnDrawItem(Item item, Object cell, int cellIndex) { } void OnRefreshText() { } Object() { _Children = array<Object>(); } void Init(Object? parent) { _ActiveSelf = true; _Name = reflection::typeof<Object>(this).nameWithoutNamespace; _Highlighted = false; Object? oldParent = _Parent; if (oldParent != null) { oldParent._Children.removeAt(oldParent._Children.findByRef(this)); } _Parent = parent; if (parent != null) { parent._Children.insertLast(this); } _RefreshActive(); _Construct(); OnConstruct(); _RefreshNeedDraw(); if (parent != null) { parent._RefreshNeedDraw(); } Screen? screen = Screen; if (screen != null && screen._IsRegistered) { _Init(); } } void _Construct() { // Virtual } void _FixClone() { _Parent = null; _Children = array<Object>(); _Highlighted = false; } void _Init() { Object? parent = _Parent; _Active = _ActiveSelf && (parent == null || parent._Active); for (int i = 0; i < _Children.length(); i++) { _Children[i]._Init(); } OnInit(); _RefreshPosition(); } void _Show(dict<string, any> params) { OnShow(); OnShow(params); for (int i = 0; i < _Children.length(); i++) { _Children[i]._Show(params); } } void _Hide() { OnHide(); for (int i = 0; i < _Children.length(); i++) { _Children[i]._Hide(); } } void _Appear() { OnAppear(); for (int i = 0; i < _Children.length(); i++) { _Children[i]._Appear(); } } void _Disappear() { OnDisappear(); for (int i = 0; i < _Children.length(); i++) { _Children[i]._Disappear(); } } void _DragChanged() { OnDragChanged(); for (int i = 0; i < _Children.length(); i++) { _Children[i]._DragChanged(); } } void Remove() { Object? oldParent = _Parent; if (oldParent != null) { oldParent._Children.removeAt(oldParent._Children.findByRef(this)); _Parent = null; } _Remove(); _ActiveSelf = false; _RefreshActive(); if (oldParent != null) { oldParent._RefreshNeedDraw(); } } void _Remove() { OnRemove(); for (int i = 0; i < _Children.length(); i++) { _Children[i]._Remove(); } } void _RefreshPositionRecursive() { _RefreshPosition(); for (int i = 0; i < _Children.length(); i++) { _Children[i]._RefreshPositionRecursive(); } } void _RefreshTextRecursive() { for (int i = 0; i < _Children.length(); i++) { _Children[i]._RefreshTextRecursive(); } OnRefreshText(); } void _DrawCallback() { // Draw item callback if (_Grid != null) { ItemView? itemView = cast<ItemView>(_Grid); if (itemView != null) { Item? cellItem = itemView.GetItem(_CellIndex); if (cellItem != null) { OnDrawItem(cellItem, this, _CellIndex); } } } // Common draw callback if (_HasOnDraw) { OnDraw(); } } void _Draw(bool callCallback) { if (!_ActiveSelf || !_NeedDraw) { return; } if (callCallback) { _DrawCallback(); } // _Children int childCount = _Children.length(); for (int i = 0; i < childCount; i++) { Object child = _Children[i]; if (child._ActiveSelf && child._NeedDraw && !child._IsDragged) { child._Draw(true); } } if (callCallback) { OnPostDraw(); } } bool _HasSelfDraw() { return _HasOnDraw || _Grid != null; } void _RefreshNeedDraw() { bool need = _HasSelfDraw(); if (!need) { for (int i = 0; i < _Children.length(); i++) { if (_Children[i]._NeedDraw) { need = true; break; } } } if (_NeedDraw == need) { return; } _NeedDraw = need; Object? parent = _Parent; if (parent != null) { parent._RefreshNeedDraw(); } } void Draw(ipos pos) { ipos deltaPos = pos - _AbsolutePos; _Move(deltaPos, false, false); _Draw(true); _Move(-deltaPos, false, false); } void Move(ipos deltaPos) { _Move(deltaPos, true, true); } void _Move(ipos deltaPos, bool callCallback, bool moveBasePos) { _AbsolutePos += deltaPos; if (moveBasePos) { _BasePos += deltaPos; } for (int i = 0; i < _Children.length(); i++) { _Children[i]._Move(deltaPos, false, false); } if (callCallback) { _MoveCallback(deltaPos); } } void _MoveCallback(ipos deltaPos) { OnMove(deltaPos); for (int i = 0; i < _Children.length(); i++) { _Children[i]._MoveCallback(deltaPos); } } bool _IsCatchable() { bool result = !_IsNotCatchable && !_IsDraggable; Object? parent = Parent; return result && (parent != null ? parent._IsCatchable() : true); } Object? FindMouseHit() { return FindHit(Game.MousePos); } Object? FindHit(ipos pos) { // Inactive subtrees can never produce a hit (IsHit walks ancestors and bails on inactive) if (!_ActiveSelf) { return null; } // Check children for (int i = _Children.length() - 1; i >= 0; i--) { Object? obj = _Children[i].FindHit(pos); if (obj != null) { return obj; } } if (_IsHitSelf(pos)) { return this; } // No collision found return null; } bool IsMouseHit() { return IsHit(Game.MousePos); } bool IsHit(ipos pos) { if (!_IsHitSelf(pos)) { return false; } Object? ancestor = _Parent; while (ancestor != null) { Object? obj = ancestor; if (obj == null) { break; } if (!obj._ActiveSelf) { return false; } Panel? panel = cast<Panel>(obj); if (panel != null && panel._CropContent && (pos.x < obj._AbsolutePos.x || pos.x >= obj._AbsolutePos.x + obj._Size.width || pos.y < obj._AbsolutePos.y || pos.y >= obj._AbsolutePos.y + obj._Size.height)) { return false; } ancestor = obj._Parent; } return true; } bool _IsHitSelf(ipos pos) { if (!_ActiveSelf || _IsNotHittable || _Size.width <= 0 || _Size.height <= 0) { return false; } if (pos.x < _AbsolutePos.x || pos.x >= _AbsolutePos.x + _Size.width || pos.y < _AbsolutePos.y || pos.y >= _AbsolutePos.y + _Size.height) { return false; } return true; } void _GetWholeSizeRect(int[] rect) { int l = _AbsolutePos.x; int t = _AbsolutePos.y; int r = l + Size.width; int b = t + Size.height; if (l < rect[0]) { rect[0] = l; } if (t < rect[1]) { rect[1] = t; } if (r > rect[2]) { rect[2] = r; } if (b > rect[3]) { rect[3] = b; } for (int i = 0; i < _Children.length(); i++) { _Children[i]._GetWholeSizeRect(rect); } } void GetWholeSize(ipos& centerPos, isize& wholeSize, bool onlyChidren = false) { int[] rect = {1000000000, 1000000000, -1000000000, -1000000000}; if (!onlyChidren) { _GetWholeSizeRect(rect); } else if (_Children.length() > 0) { for (int i = 0; i < _Children.length(); i++) { _Children[i]._GetWholeSizeRect(rect); } } else { rect[0] = rect[1] = rect[2] = rect[3] = 0; } wholeSize.width = rect[2] - rect[0]; wholeSize.height = rect[3] - rect[1]; centerPos.x = rect[0] + wholeSize.width / 2; centerPos.y = rect[1] + wholeSize.height / 2; } void _MouseDown(MouseButton button) { _IsPressed = true; _PressedButton = button; _PressedPos = Game.MousePos; OnMouseDown(button); if (_IsDraggable && _PressedButton == MouseButton::Left) { bool draggableCursor = (DraggableCursor != CursorType::Default && Cursor == DraggableCursor); if (draggableCursor) { _IsDragged = true; _DragChanged(); } } if (!_DeferredMousePressed) { Screen? screen = Screen; verify(screen != null, "GUI object must be on a screen for mouse press"); screen._GlobalMousePressed(button); _MousePressed(button); } } void StartDragging() { if (_IsDraggable && !_IsDragged && _IsPressed && _PressedButton == MouseButton::Left && !IsDragged) { _IsDragged = true; _DragChanged(); } } void _MousePressed(MouseButton button) { OnMousePressed(button); if (button == MouseButton::Left) { OnLMousePressed(); } else if (button == MouseButton::Right) { OnRMousePressed(); } _MousePressedUnder(button); } void _MousePressedUnder(MouseButton button) { Object? parent = Parent; if (parent != null) { parent._MousePressedUnder(button); } } void _MouseUp(bool lost) { _IsPressed = false; if (_DeferredMousePressed) { Screen? screen = Screen; verify(screen != null, "GUI object must be on a screen for mouse release"); screen._GlobalMousePressed(_PressedButton); _MousePressed(_PressedButton); } OnMouseUp(_PressedButton, lost); if (_IsDragged) { _IsDragged = false; _DragChanged(); } } void MouseClick(MouseButton button) { OnMouseClick(button); if (button == MouseButton::Left) { OnLMouseClick(); } else if (button == MouseButton::Right) { OnRMouseClick(); } } void _MouseMove() { OnMouseMove(); } void _GlobalMouseDown(MouseButton button) { OnGlobalMouseDown(button); for (int i = 0; i < _Children.length(); i++) { _Children[i]._GlobalMouseDown(button); } } void _GlobalMouseUp(MouseButton button) { OnGlobalMouseUp(button); for (int i = 0; i < _Children.length(); i++) { _Children[i]._GlobalMouseUp(button); } } void _GlobalMousePressed(MouseButton button) { OnGlobalMousePressed(button); for (int i = 0; i < _Children.length(); i++) { _Children[i]._GlobalMousePressed(button); } } void _GlobalMouseClick(MouseButton button) { OnGlobalMouseClick(button); for (int i = 0; i < _Children.length(); i++) { _Children[i]._GlobalMouseClick(button); } } void _GlobalMouseMove() { OnGlobalMouseMove(); for (int i = 0; i < _Children.length(); i++) { _Children[i]._GlobalMouseMove(); } } void Input(KeyCode key, string text) { OnInput(); if (text.isEmpty()) { OnInput(key); } else { OnInput(text); } OnInput(key, text); } void _GlobalInput(KeyCode key, string text) { OnGlobalInput(key, text); for (int i = 0; i < _Children.length(); i++) { _Children[i]._GlobalInput(key, text); } } void _Focus() { if (!_IsFocused) { _IsFocused = true; OnFocusChanged(); } } void _Unfocus() { if (_IsFocused) { _IsFocused = false; OnFocusChanged(); } } void _Hover() { if (!_IsHovered) { _IsHovered = true; OnHoverChanged(); } } void _Unhover() { if (_IsHovered) { _IsHovered = false; OnHoverChanged(); } } Object _Clone(Object parent) { Object newObject; reflection::typeof<Object>(this).instantiate(this, newObject); newObject._FixClone(); newObject._Parent = parent; parent._Children.insertLast(newObject); newObject.OnConstruct(); for (int i = 0; i < _Children.length(); i++) { _Children[i]._Clone(newObject); } return newObject; } Panel? FindPanel(string name) { return cast<Panel>(Find(name)); } Text? FindText(string name) { return cast<Text>(Find(name)); } TextInput? FindTextInput(string name) { return cast<TextInput>(Find(name)); } Button? FindButton(string name) { return cast<Button>(Find(name)); } Object? Find(string name, bool deepFind = true, int skip = 0) { return _Find(name, deepFind, skip); } Object? _Find(const string& name, bool deepFind, int& skip) { int len = _Children.length(); for (int i = 0; i < len; i++) { Object child = _Children[i]; if (child._Name == name) { if (skip <= 0) { return child; } else { skip--; } } if (deepFind) { Object? obj = child._Find(name, deepFind, skip); if (obj != null) { return obj; } } } return null; } int Count(string name, bool deepCount = true) { int result = 0; _Count(name, deepCount, result); return result; } void _Count(const string& name, bool deepCount, int& result) { int len = _Children.length(); for (int i = 0; i < len; i++) { Object child = _Children[i]; if (child._Name == name) { result++; } if (deepCount) { child._Count(name, deepCount, result); } } } Object GetChild(int index) { return _Children[index]; } void _RefreshPosition() { // Base data _Size = _BaseSize; Object? parent = _Parent; ipos parentAbsolutePos = parent != null ? parent._AbsolutePos : ZERO_IPOS; isize parentSize = parent != null ? parent._Size : isize(Settings.View.ScreenWidth, Settings.View.ScreenHeight); isize parentBaseSize = parent != null ? parent._BaseSize : _BaseSize; // Dock ipos newPos; if (_Dock != DockStyle::None) { if (_Dock == DockStyle::Left) { newPos = parentAbsolutePos; _Size.height = parentSize.height; } else if (_Dock == DockStyle::Right) { newPos.x = parentAbsolutePos.x + parentSize.width - _Size.width; newPos.y = parentAbsolutePos.y; _Size.height = parentSize.height; } else if (_Dock == DockStyle::Top) { newPos = parentAbsolutePos; _Size.width = parentSize.width; } else if (_Dock == DockStyle::Bottom) { newPos.x = parentAbsolutePos.x; newPos.y = parentAbsolutePos.y + parentSize.height - _Size.height; _Size.width = parentSize.width; } else if (_Dock == DockStyle::Fill) { newPos.x = parentAbsolutePos.x; newPos.y = parentAbsolutePos.y; _Size.width = parentSize.width; _Size.height = parentSize.height; } else { newPos = _BasePos + parentAbsolutePos; } } // Anchor else { if ((_Anchor & AnchorStyle::Left) != 0) { newPos.x = parentAbsolutePos.x + _BasePos.x; } else if ((_Anchor & AnchorStyle::Right) != 0) { newPos.x = parentAbsolutePos.x + _BasePos.x + (parentSize.width - parentBaseSize.width); } else { newPos.x = parentAbsolutePos.x + _BasePos.x + (parentSize.width - parentBaseSize.width) / 2; } if ((_Anchor & AnchorStyle::Top) != 0) { newPos.y = parentAbsolutePos.y + _BasePos.y; } else if ((_Anchor & AnchorStyle::Bottom) != 0) { newPos.y = parentAbsolutePos.y + _BasePos.y + (parentSize.height - parentBaseSize.height); } else { newPos.y = parentAbsolutePos.y + _BasePos.y + (parentSize.height - parentBaseSize.height) / 2; } } // Move control if (newPos != _AbsolutePos) { _Move(newPos - _AbsolutePos, false, false); } } void _SizeChanged() { // Internal callback } // Options void SetName(string name) { _Name = name; } void SetActive(bool active) { if (_ActiveSelf != active) { _ActiveSelf = active; Object? parent = _Parent; _Active = _ActiveSelf && (parent == null || parent._Active); _ActiveChanged(); } } void _ActiveChanged() { OnActiveChanged(); for (int i = 0; i < _Children.length(); i++) { _Children[i]._Active = _Children[i]._ActiveSelf && _Active; _Children[i]._ActiveChanged(); } } void _RefreshActive() { Object? parent = _Parent; bool newActive = _ActiveSelf && (parent == null || parent._Active); if (_Active == newActive) { return; } _Active = newActive; for (int i = 0; i < _Children.length(); i++) { _Children[i]._RefreshActive(); } } void SetPosition(ipos pos) { if (_BasePos == pos) { return; } _BasePos = pos; _RefreshPosition(); } void SetPosition(int x, int y) { SetPosition(ipos(x, y)); } void SetSize(isize size) { if (_BaseSize == size) { return; } _BaseSize = size; _RefreshPosition(); _SizeChanged(); } void SetSize(int width, int height) { SetSize(isize(width, height)); } void SetAnchor(AnchorStyle anchorStyles) { if (_Anchor == anchorStyles) { return; } _Anchor = anchorStyles; _RefreshPosition(); } void SetDock(DockStyle dockStyle) { if (_Dock == dockStyle) { return; } _Dock = dockStyle; _RefreshPosition(); } void SetColor(ucolor color) { _Color = color; } void SetDraggable(bool enabled) { _IsDraggable = enabled; } void SetNotHittable(bool enabled) { _IsNotHittable = enabled; } void SetNotCatchable(bool enabled) { _IsNotCatchable = enabled; } void SetCheckTransparentOnHit(bool enabled) { _CheckTransparentOnHit = enabled; } void SetFocusGroup(bool enabled) { _FocusGroup = enabled; } void SetDeferredMousePressed(bool enabled) { _DeferredMousePressed = enabled; } void SetHasOnDraw(bool enabled) { if (_HasOnDraw == enabled) { return; } _HasOnDraw = enabled; _RefreshNeedDraw(); } }; const int PanelScrollAnimationDurationMs = 120; class Panel : Object { Sprite::Sprite? get_BackgroundImage() final { return _BackgroundImage; } SpriteLayout get_BackgroundImageLayout() final { Sprite::Sprite? backgroundImage = _BackgroundImage; return backgroundImage != null ? backgroundImage.Layout : SpriteLayout::None; } bool get_IsVerticalScroll() final { return _IsVerticalScroll; } bool get_IsHorizontalScroll() final { return _IsHorizontalScroll; } int get_VerticalScrollPercent() final { return _VerticalScrollPercent; } int get_HorizontalScrollPercent() final { return _HorizontalScrollPercent; } int get_VerticalScrollValue() final { return _VerticalScroll; } int get_HorizontalScrollValue() final { return _HorizontalScroll; } int get_VerticalScrollRange() final { int maxVerScroll = 0; int maxHorScroll = 0; _GetScrollableSize(maxVerScroll, maxHorScroll); return maxVerScroll; } int get_HorizontalScrollRange() final { int maxVerScroll = 0; int maxHorScroll = 0; _GetScrollableSize(maxVerScroll, maxHorScroll); return maxHorScroll; } Sprite::Sprite? _BackgroundImage; int _CapLeft; int _CapTop; int _CapRight; int _CapBottom; bool _CropContent; bool _IsVerticalScroll; bool _IsHorizontalScroll; int _VerticalScroll; int _HorizontalScroll; int _VerticalScrollPercent; int _HorizontalScrollPercent; Tween::ValueTween _VerticalScrollTween; Tween::ValueTween _HorizontalScrollTween; void _Construct() override { Object::_Construct(); _ResetScrollTweens(); } void _FixClone() override { Object::_FixClone(); _ResetScrollTweens(); } void _ResetScrollTweens() { _VerticalScrollTween = Tween::ValueTween(); _HorizontalScrollTween = Tween::ValueTween(); _VerticalScrollTween.SnapTo(_VerticalScroll); _HorizontalScrollTween.SnapTo(_HorizontalScroll); } bool _IsHitSelf(ipos pos) override { if (!Object::_IsHitSelf(pos)) { return false; } Sprite::Sprite? backgroundImage = _BackgroundImage; if (_CheckTransparentOnHit && backgroundImage != null) { return Game.IsSpriteHit(backgroundImage.Id, pos - _AbsolutePos); } return true; } Object? FindHit(ipos pos) override { if (!_ActiveSelf) { return null; } // Cropping panel outside its rect cannot contain any hittable descendant if (_CropContent && (pos.x < _AbsolutePos.x || pos.x >= _AbsolutePos.x + _Size.width || pos.y < _AbsolutePos.y || pos.y >= _AbsolutePos.y + _Size.height)) { return null; } for (int i = _Children.length() - 1; i >= 0; i--) { Object? obj = _Children[i].FindHit(pos); if (obj != null) { return obj; } } if (_IsHitSelf(pos)) { return this; } return null; } void _Draw(bool callCallback) override { if (!_ActiveSelf || !_NeedDraw) { return; } _RefreshSmoothScroll(); bool crop = _CropContent; if (crop) { Game.PushDrawScissor(AbsolutePos, Size); } if (callCallback) { _DrawCallback(); } // Normal sprite if (_BackgroundImage != null) { _DrawImage(_BackgroundImage); } Object::_Draw(false); if (callCallback) { OnPostDraw(); } if (crop) { Game.PopDrawScissor(); } } bool _HasSelfDraw() override { return _HasOnDraw || _BackgroundImage != null || _Grid != null; } void _DrawImage(Sprite::Sprite image) { if (_Size == isize()) { return; } // Normal background (no 9-slice cap insets authored): draw with the sprite's stored layout. if (_CapLeft <= 0 && _CapTop <= 0 && _CapRight <= 0 && _CapBottom <= 0) { image.Draw(_AbsolutePos, _Size, -1, -1, SpriteLayout::None, _Color); return; } // 9-slice frame: fixed corners, stretched edges and center, into the current size. Cap insets // are measured in the frame sprite's own source pixels. if (image.Id == 0) { return; } isize src = image.Size; if (src.width <= 0 || src.height <= 0) { image.Draw(_AbsolutePos, _Size, -1, -1, SpriteLayout::Stretch, _Color); return; } int dw = _Size.width; int dh = _Size.height; // Source and destination cap sizes, clamped so malformed authored values cannot produce // invalid UV ranges and opposite caps never overlap on a small frame. int sl = Math::Clamp(_CapLeft, 0, src.width); int sr = Math::Clamp(_CapRight, 0, src.width - sl); int st = Math::Clamp(_CapTop, 0, src.height); int sb = Math::Clamp(_CapBottom, 0, src.height - st); int dl = Math::Clamp(sl, 0, dw); int dr = Math::Clamp(sr, 0, dw - dl); int dt = Math::Clamp(st, 0, dh); int db = Math::Clamp(sb, 0, dh - dt); // Source cap UV fractions. float uL = float(sl) / float(src.width); float uR = float(src.width - sr) / float(src.width); float vT = float(st) / float(src.height); float vB = float(src.height - sb) / float(src.height); int x0 = _AbsolutePos.x; int y0 = _AbsolutePos.y; int xR = x0 + dw - dr; int yB = y0 + dh - db; int midW = dw - dl - dr; int midH = dh - dt - db; uint id = image.Id; ucolor color = _Color; // Corners (drawn 1:1, kept crisp). if (!_DrawSlice(id, 0.0f, 0.0f, uL, vT, ipos(x0, y0), isize(dl, dt), color)) { _DrawFallbackImage(image); return; } if (!_DrawSlice(id, uR, 0.0f, 1.0f, vT, ipos(xR, y0), isize(dr, dt), color)) { _DrawFallbackImage(image); return; } if (!_DrawSlice(id, 0.0f, vB, uL, 1.0f, ipos(x0, yB), isize(dl, db), color)) { _DrawFallbackImage(image); return; } if (!_DrawSlice(id, uR, vB, 1.0f, 1.0f, ipos(xR, yB), isize(dr, db), color)) { _DrawFallbackImage(image); return; } // Horizontal edges (stretched along the width). if (!_DrawSlice(id, uL, 0.0f, uR, vT, ipos(x0 + dl, y0), isize(midW, dt), color)) { _DrawFallbackImage(image); return; } if (!_DrawSlice(id, uL, vB, uR, 1.0f, ipos(x0 + dl, yB), isize(midW, db), color)) { _DrawFallbackImage(image); return; } // Vertical edges (stretched along the height). if (!_DrawSlice(id, 0.0f, vT, uL, vB, ipos(x0, y0 + dt), isize(dl, midH), color)) { _DrawFallbackImage(image); return; } if (!_DrawSlice(id, uR, vT, 1.0f, vB, ipos(xR, y0 + dt), isize(dr, midH), color)) { _DrawFallbackImage(image); return; } // Center fill (stretched; transparent in hollow frames). if (!_DrawSlice(id, uL, vT, uR, vB, ipos(x0 + dl, y0 + dt), isize(midW, midH), color)) { _DrawFallbackImage(image); } } private bool _DrawSlice(uint id, float u0, float v0, float u1, float v1, ipos pos, isize size, ucolor color) { if (size.width > 0 && size.height > 0) { return Game.DrawSpriteRegion(id, fpos(u0, v0), fpos(u1, v1), pos, size, color); } return true; } void _DrawFallbackImage(Sprite::Sprite image) { image.Draw(_AbsolutePos, _Size, -1, -1, SpriteLayout::Stretch, _Color); } // Generic UI: enabling 9-slice for this panel/screen background. Cap insets in source pixels. void SetCapInsets(int left, int top, int right, int bottom) { _CapLeft = left; _CapTop = top; _CapRight = right; _CapBottom = bottom; _RefreshNeedDraw(); } void SetFrameImage(string imageName) { SetFrameImage(imageName.hstr()); } // Loads the (9-slice) frame sprite without auto-resizing to its native size, so the panel/screen // keeps its authored/docked size and reslices the sprite to fit. void SetFrameImage(hstring imageName) { Sprite::Sprite? curImage = _BackgroundImage; if (curImage != null && curImage.LoadedName == imageName) { return; } _BackgroundImage = null; if (imageName != EMPTY_HSTRING) { Sprite::Sprite spr = Sprite::Sprite(); if (spr.Load(imageName)) { _BackgroundImage = spr; } } _RefreshNeedDraw(); } void SetBackgroundImage(string imageName, SpriteLayout imageLayout = SpriteLayout::None) { _ClearCapInsets(); _SetImage(_BackgroundImage, imageName, imageLayout); } void SetBackgroundImage(hstring imageName, SpriteLayout imageLayout = SpriteLayout::None) { _ClearCapInsets(); _SetImage(_BackgroundImage, imageName, imageLayout); } void _ClearCapInsets() { bool hadCapInsets = _CapLeft != 0 || _CapTop != 0 || _CapRight != 0 || _CapBottom != 0; _CapLeft = 0; _CapTop = 0; _CapRight = 0; _CapBottom = 0; if (hadCapInsets) { _RefreshNeedDraw(); } } void _SetImage(Sprite::Sprite?& curImage, string imageName, SpriteLayout imageLayout) { _SetImage(curImage, imageName.hstr(), imageLayout); } void _SetImage(Sprite::Sprite?& curImage, hstring imageName, SpriteLayout imageLayout) { if (curImage != null && curImage.LoadedName == imageName) { return; } curImage = null; if (imageName != EMPTY_HSTRING) { Sprite::Sprite spr = Sprite::Sprite(); if (spr.Load(imageName)) { curImage = spr; } } if (curImage != null) { curImage.Layout = imageLayout; } if (curImage != null && (imageLayout == SpriteLayout::None || _BaseSize == isize())) { SetSize(curImage.Size); } _RefreshNeedDraw(); } void _MousePressedUnder(MouseButton button) override { if (_IsVerticalScroll && (button == MouseButton::WheelUp || button == MouseButton::WheelDown)) { int dy = 0; if (button == MouseButton::WheelUp) { dy = -1; } else if (button == MouseButton::WheelDown) { dy = 1; } if (dy != 0) { _ModifyScroll(dy, 0); } // Stop processing return; } Object::_MousePressedUnder(button); } void ModifyScroll(int ver, int hor) { _ModifyScroll(ver, hor); } bool CanModifyScroll(int ver, int hor) { return _ModifyScroll(ver, hor, true); } void SetScrollValue(int ver, int hor) { if (!_IsVerticalScroll && !_IsHorizontalScroll) { return; } int newVer = (_IsVerticalScroll ? ver : 0); int newHor = (_IsHorizontalScroll ? hor : 0); if (newVer != _VerticalScroll || newHor != _HorizontalScroll || _VerticalScrollTween.Active || _HorizontalScrollTween.Active) { _SetScrollValue(newVer, newHor, false, true); } } void SetScrollPercent(int verPercent, int horPercent) { if (!_IsVerticalScroll && !_IsHorizontalScroll) { return; } int maxVerScroll = 0, maxHorScroll = 0; _GetScrollableSize(maxVerScroll, maxHorScroll); int newVer = (maxVerScroll > 0 ? maxVerScroll * verPercent / 100 : 0); int newHor = (maxHorScroll > 0 ? maxHorScroll * horPercent / 100 : 0); if (newVer != _VerticalScroll || newHor != _HorizontalScroll || _VerticalScrollTween.Active || _HorizontalScrollTween.Active) { _SetScrollValue(newVer, newHor, false, true); } } int _GetAdaptiveScrollStep(int scrollRange) { int adaptiveStep = scrollRange / 20; return Math::Clamp(adaptiveStep, 10, 120); } void _ClampScrollValue(int& ver, int& hor) { int maxVerScroll = 0, maxHorScroll = 0; _GetScrollableSize(maxVerScroll, maxHorScroll); if (_IsVerticalScroll) { if (ver > maxVerScroll) { ver = maxVerScroll; } if (ver < 0) { ver = 0; } } else { ver = 0; } if (_IsHorizontalScroll) { if (hor > maxHorScroll) { hor = maxHorScroll; } if (hor < 0) { hor = 0; } } else { hor = 0; } } void _SetScrollTarget(int ver, int hor) { if (!_IsVerticalScroll && !_IsHorizontalScroll) { return; } int targetVer = (_IsVerticalScroll ? ver : 0); int targetHor = (_IsHorizontalScroll ? hor : 0); _ClampScrollValue(targetVer, targetHor); if (_IsVerticalScroll) { if (targetVer != _VerticalScrollTween.Target || !_VerticalScrollTween.Active) { _VerticalScrollTween.MoveTo(_VerticalScroll, targetVer, PanelScrollAnimationDurationMs); } if (!_VerticalScrollTween.Active) { _VerticalScrollTween.SnapTo(_VerticalScroll); } } if (_IsHorizontalScroll) { if (targetHor != _HorizontalScrollTween.Target || !_HorizontalScrollTween.Active) { _HorizontalScrollTween.MoveTo(_HorizontalScroll, targetHor, PanelScrollAnimationDurationMs); } if (!_HorizontalScrollTween.Active) { _HorizontalScrollTween.SnapTo(_HorizontalScroll); } } } void _RefreshSmoothScroll() { if (!_IsVerticalScroll && !_IsHorizontalScroll) { return; } if (!_VerticalScrollTween.Active && !_HorizontalScrollTween.Active) { _SetScrollValue(_VerticalScroll, _HorizontalScroll, false, true); return; } int curVer = _VerticalScroll; int curHor = _HorizontalScroll; int targetVer = (_VerticalScrollTween.Active ? _VerticalScrollTween.Target : _VerticalScroll); int targetHor = (_HorizontalScrollTween.Active ? _HorizontalScrollTween.Target : _HorizontalScroll); _ClampScrollValue(targetVer, targetHor); if (_VerticalScrollTween.Active && targetVer != _VerticalScrollTween.Target) { _VerticalScrollTween.MoveTo(_VerticalScroll, targetVer, PanelScrollAnimationDurationMs); } if (_HorizontalScrollTween.Active && targetHor != _HorizontalScrollTween.Target) { _HorizontalScrollTween.MoveTo(_HorizontalScroll, targetHor, PanelScrollAnimationDurationMs); } if (_VerticalScrollTween.Active) { curVer = _VerticalScrollTween.Update(); } if (_HorizontalScrollTween.Active) { curHor = _HorizontalScrollTween.Update(); } _SetScrollValue(curVer, curHor, false, false); if (!_VerticalScrollTween.Active) { _VerticalScrollTween.SnapTo(_VerticalScroll); } if (!_HorizontalScrollTween.Active) { _HorizontalScrollTween.SnapTo(_HorizontalScroll); } } bool _SetScrollValue(int ver, int hor, bool onlyCheck = false, bool syncTarget = true) { if (!_IsVerticalScroll && !_IsHorizontalScroll) { return false; } int oldVer = _VerticalScroll; int oldHor = _HorizontalScroll; int curVer = (_IsVerticalScroll ? ver : 0); int curHor = (_IsHorizontalScroll ? hor : 0); _ClampScrollValue(curVer, curHor); int maxVerScroll = 0, maxHorScroll = 0; _GetScrollableSize(maxVerScroll, maxHorScroll); if (!onlyCheck || (ver == _VerticalScroll && hor == _HorizontalScroll)) { _VerticalScrollPercent = (maxVerScroll > 0 ? curVer * 100 / maxVerScroll : 0); _HorizontalScrollPercent = (maxHorScroll > 0 ? curHor * 100 / maxHorScroll : 0); } if (!onlyCheck && syncTarget) { _VerticalScrollTween.SnapTo(curVer); _HorizontalScrollTween.SnapTo(curHor); } if (curVer != oldVer || curHor != oldHor) { if (!onlyCheck) { _VerticalScroll = curVer; _HorizontalScroll = curHor; for (int i = 0; i < _Children.length(); i++) { _Children[i]._Move(ipos(-(curHor - oldHor), -(curVer - oldVer)), true, false); } } return true; } return false; } bool _ModifyScroll(int ver, int hor, bool onlyCheck = false) { if (!_IsVerticalScroll && !_IsHorizontalScroll) { return false; } int maxVerScroll = 0, maxHorScroll = 0; _GetScrollableSize(maxVerScroll, maxHorScroll); int verStep = 0; int horStep = 0; if (_IsVerticalScroll && ver != 0) { verStep = (ver < 0 ? -1 : 1) * _GetAdaptiveScrollStep(maxVerScroll); } if (_IsHorizontalScroll && hor != 0) { horStep = (hor < 0 ? -1 : 1) * _GetAdaptiveScrollStep(maxHorScroll); } int baseVer = (_VerticalScrollTween.Active ? _VerticalScrollTween.Target : _VerticalScroll); int baseHor = (_HorizontalScrollTween.Active ? _HorizontalScrollTween.Target : _HorizontalScroll); int targetVer = baseVer + verStep; int targetHor = baseHor + horStep; _ClampScrollValue(targetVer, targetHor); if (onlyCheck) { return targetVer != baseVer || targetHor != baseHor; } if (targetVer != baseVer || targetHor != baseHor) { _SetScrollTarget(targetVer, targetHor); return true; } return false; } void _GetScrollableSize(int& maxVerScroll, int& maxHorScroll) { if (!_IsVerticalScroll && !_IsHorizontalScroll) { return; } ipos centerPos; isize wholeSize; GetWholeSize(centerPos, wholeSize, true); if (_IsVerticalScroll) { maxVerScroll = wholeSize.height - Size.height + ((centerPos.y + _VerticalScroll) - wholeSize.height / 2 - AbsolutePos.y); } if (_IsHorizontalScroll) { maxHorScroll = wholeSize.width - Size.width + ((centerPos.x + _HorizontalScroll) - wholeSize.width / 2 - AbsolutePos.x); } } void SetCropContent(bool enabled) { _CropContent = enabled; } void SetAutoScroll(bool ver, bool hor) { _IsVerticalScroll = ver; _IsHorizontalScroll = hor; } }; class Text : Object { string get_Text() { return _Text; } void set_Text(string value) { _Text = value; } FontType get_TextFont() final { return _TextFont; } ucolor get_TextColor() final { return _TextColor; } ucolor get_TextColorFocused() final { return _TextColorFocused; } FontFlag get_TextFlags() final { return _TextFlags; } string _Text; FontType _TextFont; ucolor _TextColor; ucolor _TextColorFocused; FontFlag _TextFlags; int _TextSkipLines; void _Construct() override { Object::_Construct(); _TextFont = FontType::Default; _TextColor = Color::Text; _TextFlags = FontFlag::None; } void _Draw(bool callCallback) override { if (!_ActiveSelf || !_NeedDraw) { return; } if (callCallback) { _DrawCallback(); } // Text string text = Text; if (text.length() > 0) { ucolor color = (_IsFocused && _TextColorFocused != ZERO_UCOLOR ? _TextColorFocused : _TextColor); Game.DrawText(text, _AbsolutePos, _Size, color, TextFormat(_TextFont, _TextFlags, _TextSkipLines)); } Object::_Draw(false); if (callCallback) { OnPostDraw(); } } bool _HasSelfDraw() override { return true; } void SetText(string text, FontType font, FontFlag flags) { if (!text.isEmpty()) { _Text = text; } else { _Text = ""; } _TextFont = font; _TextFlags = flags; } void SetText(string text) { if (!text.isEmpty()) { _Text = text; } else { _Text = ""; } } void SetTextWithResize(string text) { isize resultSize; int lines = 0; Game.GetTextInfo(text, isize(Size.width, 1000), TextFormat(_TextFont, _TextFlags, _TextSkipLines), resultSize, lines); SetSize(isize(_Size.width, resultSize.height + 5)); SetText(text); } void SetTextFont(FontType font) { _TextFont = font; } void SetTextFlags(FontFlag flags) { _TextFlags = flags; } void SetTextColor(ucolor color) { _TextColor = color; } void SetTextFocusedColor(ucolor color) { _TextColorFocused = color; } }; const timespan PasswordShowTime = Time::Milliseconds(1000); class TextInput : Text { int get_InputLength() final { return _InputLength; } bool get_IsTextPassword() final { return _IsTextPassword; } string get_PasswordChar() final { return _PasswordChar; } int _InputLength; bool _IsTextPassword; string _PasswordChar; nanotime _PasswordTime; int _CarriagePos; void _Construct() override { Text::_Construct(); SetCarriage(false); _TextColorFocused = Color::TextFocused; _IsNotCatchable = true; } void _Focus() override { Game.SetScreenKeyboard(true); Text::_Focus(); } void _Unfocus() override { Console? curConsole = CurConsole; if (curConsole == null || !curConsole.Active) { Game.SetScreenKeyboard(false); } Text::_Unfocus(); } void _Draw(bool callCallback) override { if (!_ActiveSelf || !_NeedDraw) { return; } if (callCallback) { _DrawCallback(); } // Text string text = _Text; if (_IsTextPassword && text.length() > 0) { string rawText = text; text = ""; for (int i = 0; i < rawText.length(); i++) { text += _PasswordChar; } if (Game.FrameTime - _PasswordTime <= PasswordShowTime) { text[text.length() - 1] = rawText[text.length() - 1]; } } if (_CarriagePos != -1 && _IsFocused) { if (_CarriagePos < 0) { _CarriagePos = 0; } if (_CarriagePos > text.length()) { _CarriagePos = text.length(); } text = text.substr(0, _CarriagePos) + (Game.FrameTime.milliseconds % 800 < 400 ? "!" : ".") + text.substr(_CarriagePos); } if (text.length() > 0) { ucolor color = (_IsFocused && _TextColorFocused != ZERO_UCOLOR ? _TextColorFocused : _TextColor); Game.DrawText(text, _AbsolutePos, _Size, color, TextFormat(_TextFont, _TextFlags, _TextSkipLines)); } Object::_Draw(false); if (callCallback) { OnPostDraw(); } } void Input(KeyCode key, string text) override { int oldLen = _Text.length(); _ProcessKey(key, text); while (_InputLength != 0 && _Text.length() > _InputLength) { _Text = _Text.substr(0, _Text.length() - 1); } if (_IsTextPassword) { _PasswordTime = (_Text.length() > oldLen ? Game.FrameTime : ZERO_NANOTIME); } Text::Input(key, text); } void _ProcessKey(KeyCode key, string text) { if (Input::IsCtrlDown() && !Input::IsShiftDown() && !Input::IsAltDown()) { if (key == KeyCode::C || key == KeyCode::X) { Game.SetClipboardText(_Text); if (key == KeyCode::X) { _Text.clear(); } } } if (_CarriagePos != -1) { if (_CarriagePos < 0) { _CarriagePos = 0; } else if (_CarriagePos > _Text.length()) { _CarriagePos = _Text.length(); } if (key == KeyCode::Back) { if (_CarriagePos > 0) { _Text[_CarriagePos - 1] = ""; _CarriagePos--; } } else if (key == KeyCode::Delete) { if (_CarriagePos < _Text.length()) { _Text[_CarriagePos] = ""; } } else if (key == KeyCode::Right) { if (_CarriagePos < _Text.length()) { _CarriagePos++; } } else if (key == KeyCode::Left) { if (_CarriagePos > 0) { _CarriagePos--; } } else if (key == KeyCode::Home) { _CarriagePos = 0; } else if (key == KeyCode::End) { _CarriagePos = _Text.length(); } else if (!text.isEmpty()) { _Text = _Text.substr(0, _CarriagePos) + text + _Text.substr(_CarriagePos); _CarriagePos += text.length(); } } else { if (key == KeyCode::Back) { if (_Text.length() > 0) { _Text[_Text.length() - 1] = ""; } } else if (!text.isEmpty()) { _Text += text; } } } void SetInputLength(int length) { _InputLength = length; } void SetInputPassword(string passwordChar) { _IsTextPassword = (passwordChar.length() == 1); _PasswordChar = passwordChar; _PasswordTime = ZERO_NANOTIME; } void SetCarriage(bool enable) { _CarriagePos = (enable ? _Text.length() : -1); } }; class Button : Panel { bool get_IsDisabled() final { return _IsDisabled; } bool get_IsSwitched() final { return _IsSwitched; } Sprite::Sprite? get_PressedImage() final { return _PressedImage; } SpriteLayout get_PressedImageLayout() final { Sprite::Sprite? image = _PressedImage; return image != null ? image.Layout : SpriteLayout::None; } Sprite::Sprite? get_HoverImage() final { return _HoverImage; } SpriteLayout get_HoverImageLayout() final { Sprite::Sprite? image = _HoverImage; return image != null ? image.Layout : SpriteLayout::None; } Sprite::Sprite? get_DisabledImage() final { return _DisabledImage; } SpriteLayout get_DisabledImageLayout() final { Sprite::Sprite? image = _DisabledImage; return image != null ? image.Layout : SpriteLayout::None; } bool _IsDisabled; bool _IsSwitched; Sprite::Sprite? _PressedImage; Sprite::Sprite? _HoverImage; Sprite::Sprite? _DisabledImage; void _Construct() override { Panel::_Construct(); _DeferredMousePressed = true; _IsNotCatchable = true; } void _Draw(bool callCallback) override { if (!_ActiveSelf || !_NeedDraw) { return; } if (callCallback) { _DrawCallback(); } if (!_IsDisabled) { // Pressed image bool isPressed = ((_IsPressed && _PressedButton == MouseButton::Left) || _IsSwitched); if (isPressed && _PressedImage != null) { _DrawImage(_PressedImage); } // Hover image else if (_IsHovered && _HoverImage != null) { _DrawImage(_HoverImage); } // Normal image else if (_BackgroundImage != null) { _DrawImage(_BackgroundImage); } } else { // Disabled image if (_DisabledImage != null) { _DrawImage(_DisabledImage); } // Normal image else if (_BackgroundImage != null) { _DrawImage(_BackgroundImage); } } Object::_Draw(false); if (callCallback) { OnPostDraw(); } } bool _HasSelfDraw() override { return _HasOnDraw || _BackgroundImage != null || _PressedImage != null || _HoverImage != null || _DisabledImage != null || _Grid != null; } void MouseClick(MouseButton button) { if (_IsDisabled) { return; } Panel::MouseClick(button); } void _MousePressed(MouseButton button) { if (_IsDisabled) { return; } Panel::_MousePressed(button); } void SetPressedImage(string imageName, SpriteLayout imageLayout = SpriteLayout::None) { _SetImage(_PressedImage, imageName, imageLayout); } void SetPressedImage(hstring imageName, SpriteLayout imageLayout = SpriteLayout::None) { _SetImage(_PressedImage, imageName, imageLayout); } void SetHoverImage(string imageName, SpriteLayout imageLayout = SpriteLayout::None) { _SetImage(_HoverImage, imageName, imageLayout); } void SetHoverImage(hstring imageName, SpriteLayout imageLayout = SpriteLayout::None) { _SetImage(_HoverImage, imageName, imageLayout); } void SetDisabledImage(string imageName, SpriteLayout imageLayout = SpriteLayout::None) { _SetImage(_DisabledImage, imageName, imageLayout); } void SetDisabledImage(hstring imageName, SpriteLayout imageLayout = SpriteLayout::None) { _SetImage(_DisabledImage, imageName, imageLayout); } void SetSwitch(bool enabled) { _IsSwitched = enabled; } void SetCondition(bool enabled) { _IsDisabled = !enabled; } }; class CheckBox : Button { bool get_IsChecked() final { return _IsSwitched; } // Callbacks void OnCheckedChanged() { } void MouseClick(MouseButton button) { if (_IsDisabled) { return; } if (button == MouseButton::Left) { SetChecked(!_IsSwitched); } Panel::MouseClick(button); } void SetChecked(bool checked) { if (_IsSwitched != checked) { _IsSwitched = checked; OnCheckedChanged(); } } }; class RadioButton : CheckBox { void MouseClick(MouseButton button) { if (_IsDisabled || _IsSwitched) { return; } if (button == MouseButton::Left) { SetChecked(true); } Panel::MouseClick(button); } void SetChecked(bool checked) override { Object? parent = _Parent; if (checked && parent != null) { for (int i = 0; i < parent._Children.length(); i++) { RadioButton? button = cast<RadioButton>(parent._Children[i]); if (button != null && button._IsSwitched) { button._IsSwitched = false; button.OnCheckedChanged(); } } } if (_IsSwitched != checked) { _IsSwitched = checked; OnCheckedChanged(); } } }; class Screen : Panel { GuiScreen get_Num() final { return _Num; } bool get_IsModal() final { return _IsModal; } bool get_IsMultiinstance() final { return _IsMultiinstance; } bool get_IsCloseOnMiss() final { return _IsCloseOnMiss; } CursorType[] get_AvailableCursors() final { return _AvailableCursors; } CursorType get_Cursor() final { return _Cursor; } bool get_IsCanMove() final { return _IsCanMove; } bool get_IsMoveIgnoreBorders() final { return _IsMoveIgnoreBorders; } bool get_IsOnTop() final { return _IsOnTop; } GuiScreen _Num; bool _IsRegistered; bool _IsModal; bool _IsMultiinstance; bool _IsCloseOnMiss; CursorType[] _AvailableCursors; CursorType _Cursor; bool _IsCanMove; bool _IsMoveIgnoreBorders; bool _IsOnTop; // Workaround for input state accessing from shared code bool[] _InputKeyPressed; bool[] _InputMousePressed; void _Construct() override { _AvailableCursors = array<CursorType>(); _InputKeyPressed = array<bool>(); _InputMousePressed = array<bool>(); Panel::_Construct(); } void _FixClone() override { _AvailableCursors = _AvailableCursors.clone(); Panel::_FixClone(); } void _Show(dict<string, any> params) override { // Make screen active _ActiveSelf = true; _RefreshActive(); // Set default cursor _Cursor = _AvailableCursors.length() > 0 ? AvailableCursors[0] : CursorType::Default; // Base behaviour Panel::_Show(params); } void _Hide() override { // Make screen active _ActiveSelf = false; _RefreshActive(); // Base behaviour Panel::_Hide(); } void _Appear() override { // On top _IsOnTop = true; // Set screen cursor Gui::Cursor = _Cursor; // Base behaviour Panel::_Appear(); } void _Disappear() override { // Not on top more _IsOnTop = false; // Store cursor _Cursor = Gui::Cursor; if (_AvailableCursors.find(_Cursor) == -1) { _Cursor = _AvailableCursors.length() > 0 ? AvailableCursors[0] : CursorType::Default; } // Base behaviour Panel::_Disappear(); } void _GlobalMouseClick(MouseButton button) override { if (button == MouseButton::Right) { if (_AvailableCursors.length() > 0) { CursorType curCursor = Gui::Cursor; int curCursorIndex = _AvailableCursors.find(curCursor); if (curCursorIndex != -1) { curCursorIndex++; if (curCursorIndex >= _AvailableCursors.length()) { curCursorIndex = 0; } Gui::Cursor = _AvailableCursors[curCursorIndex]; } } } else { Panel::_GlobalMouseClick(button); } } void _GlobalMouseMove() override { // Process moving if (_IsCanMove) { Object? pressedObj = _FindPressed(this); if (pressedObj != null && pressedObj._PressedButton == MouseButton::Left && pressedObj._IsCatchable()) { ipos lastPos = _AbsolutePos; ipos newPos = _AbsolutePos + (Game.MousePos - pressedObj._PressedPos); pressedObj._PressedPos = Game.MousePos; // Check screen borders if (!_IsMoveIgnoreBorders) { Object? parent = _Parent; ipos parentAbsolutePos = parent != null ? parent._AbsolutePos : ZERO_IPOS; isize parentSize = parent != null ? parent._Size : isize(Settings.View.ScreenWidth, Settings.View.ScreenHeight); ipos prevPos = newPos; if (newPos.x < parentAbsolutePos.x) { newPos.x = parentAbsolutePos.x; } if (newPos.y < parentAbsolutePos.y) { newPos.y = parentAbsolutePos.y; } if (newPos.x + _Size.width > parentSize.width) { newPos.x = parentSize.width - _Size.width; } if (newPos.y + _Size.height > parentSize.height) { newPos.y = parentSize.height - _Size.height; } pressedObj._PressedPos += newPos - prevPos; } // Callback if (lastPos != newPos) { ipos deltaPos = newPos - lastPos; _Move(deltaPos, true, true); } } } Panel::_GlobalMouseMove(); } Object? _FindPressed(Object obj) { if (obj._IsPressed) { return obj; } for (int i = 0; i < obj._Children.length(); i++) { Object? pressedObj = _FindPressed(obj._Children[i]); if (pressedObj != null) { return pressedObj; } } return null; } // Options void SetModal(bool enabled) { _IsModal = enabled; } void SetMultiinstance(bool enabled) { _IsMultiinstance = enabled; } void SetCloseOnMiss(bool enabled) { _IsCloseOnMiss = enabled; } void SetAvailableCursors(CursorType[] cursors) { _AvailableCursors = cursors; } void SetCanMove(bool enabled, bool ignoreBorders) { _IsCanMove = enabled; _IsMoveIgnoreBorders = ignoreBorders; } }; class Grid : Panel { string get_CellPrototype() final { return _CellPrototype; } int get_GridSize() final { return _GridSize; } int get_Columns() final { return _Columns; } ipos get_Padding() final { return _Padding; } Object[] get_Cells() final { return _Cells; } string _CellPrototype; int _GridSize; int _Columns; ipos _Padding; Object[] _Cells; void _Construct() override { Panel::_Construct(); _Cells = array<Object>(); } void _FixClone() override { _Cells = _Cells.clone(); Panel::_FixClone(); } void _Init() override { Panel::_Init(); if (!_CellPrototype.isEmpty()) { SetCellPrototype(_CellPrototype); } if (_GridSize > 0) { ResizeGrid(_GridSize); } } void ResizeGrid(int size) { // Refresh grid size _GridSize = size; // Find cell prototype if (_CellPrototype.isEmpty()) { return; } Object? parent = Parent; Object? cellPrototype; if (_CellPrototype[0] != ".") { cellPrototype = Find(_CellPrototype); } else if (parent != null) { cellPrototype = parent.Find(_CellPrototype.substr(1), false); } if (cellPrototype == null) { return; } // Get cell index int childIndex = -1; Object? cellParent = cellPrototype._Parent; if (cellParent != null && cellParent == this) { childIndex = cellParent._Children.findByRef(cellPrototype); } // Remove current instances for (int i = 0; i < _Children.length();) { if (_Cells.findByRef(_Children[i]) != -1) { _Children[i].SetActive(false); _SetCellIndex(_Children[i], null, -1); _Children.removeAt(i); } else { i++; } } _Cells.clear(); // Create new intsances cellPrototype._ActiveSelf = true; cellPrototype._RefreshActive(); Object[] cellInstances = {}; for (int i = 0; i < _GridSize; i++) { Object cellInstance = cellPrototype._Clone(this); _Children.removeLast(); _Children.insertAt(++childIndex, cellInstance); _Cells.insertLast(cellInstance); _SetCellIndex(cellInstance, this, cellInstances.length()); cellInstances.insertLast(cellInstance); } cellPrototype._ActiveSelf = false; cellPrototype._RefreshActive(); // Callbacks for (int i = 0; i < cellInstances.length(); i++) { OnResizeGrid(cellInstances[i], i); _ResizeGrid(cellInstances[i], cellInstances[i], i); } // Init for (int i = 0; i < cellInstances.length(); i++) { cellInstances[i]._Init(); } // Move int col = 0; int row = 0; int rowHeight = 0; int shiftX = -_HorizontalScroll; int shiftY = -_VerticalScroll; for (int i = 0; i < cellInstances.length(); i++) { cellInstances[i]._Move(ipos(shiftX, shiftY), false, true); shiftX += cellInstances[i]._Size.width + _Padding.x; if (rowHeight < cellInstances[i]._Size.height + _Padding.y) { rowHeight = cellInstances[i]._Size.height + _Padding.y; } if (++col >= _Columns) { col = 0; row++; shiftX = 0; shiftY += rowHeight; rowHeight = 0; } } _ModifyScroll(0, 0); } void RefreshContentPositions() { if (_Cells.length() == 0) { return; } // Re-flow the rows relative to the first cell, which stays where it is. The scroll offset must NOT appear // here: scrolling has already moved every cell, so subtracting it again would drag the whole block one more // page up on each refresh. That stayed invisible while grids were only ever refreshed at scroll 0. int col = 0; int row = 0; int rowHeight = 0; int shiftX = 0; int shiftY = 0; int baseX = _Cells[0]._BasePos.x; int baseY = _Cells[0]._BasePos.y; for (int i = 0; i < _Cells.length(); i++) { int ox = shiftX - _Cells[i]._BasePos.x + baseX; int oy = shiftY - _Cells[i]._BasePos.y + baseY; if (ox != 0 || oy != 0) { _Cells[i]._Move(ipos(ox, oy), false, true); } shiftX += _Cells[i]._Size.width + _Padding.x; if (rowHeight < _Cells[i]._Size.height + _Padding.y) { rowHeight = _Cells[i]._Size.height + _Padding.y; } if (++col >= _Columns) { col = 0; row++; shiftX = 0; shiftY += rowHeight; rowHeight = 0; } } _ModifyScroll(0, 0); } void _SetCellIndex(Object obj, Grid grid, int cellIndex) { obj._Grid = grid; obj._CellIndex = cellIndex; for (int i = 0; i < obj._Children.length(); i++) { _SetCellIndex(obj._Children[i], grid, cellIndex); } obj._RefreshNeedDraw(); } void _ResizeGrid(Object obj, Object cell, int cellIndex) { obj.OnResizeGrid(cell, cellIndex); for (int i = 0; i < obj._Children.length(); i++) { _ResizeGrid(obj._Children[i], cell, cellIndex); } } void SetCellPrototype(string name) { _CellPrototype = name; if (!_CellPrototype.isEmpty()) { Object? parent = Parent; Object? cellPrototype; if (_CellPrototype[0] != ".") { cellPrototype = Find(_CellPrototype); } else if (parent != null) { cellPrototype = parent.Find(_CellPrototype.substr(1), false); } if (cellPrototype != null) { cellPrototype._ActiveSelf = false; cellPrototype._RefreshActive(); } } } void SetGridSize(int size) { _GridSize = size; } void SetColumns(int length) { _Columns = length; } void SetPadding(ipos pos) { _Padding = pos; } void SetPadding(int x, int y) { SetPadding(ipos(x, y)); } }; class MessageBox : Text { string[] get_MessageTexts() final { return _MessageTexts; } MessageBoxType[] get_MessageTypes() final { return _MessageTypes; } string[] get_MessageTimes() final { return _MessageTimes; } MessageBoxType[] get_DisplayedMessages() final { return _DisplayedMessages; } bool get_InvertMessages() final { return _InvertMessages; } string[] _MessageTexts; MessageBoxType[] _MessageTypes; string[] _MessageTimes; MessageBoxType[] _DisplayedMessages; bool _InvertMessages; int _Scroll; int _MaxScroll; int _ScrollLines; Sprite::Sprite _ScrollUp; Sprite::Sprite _ScrollDown; bool _CursorHidden; void _Construct() override { Text::_Construct(); _MessageTexts = array<string>(); _MessageTypes = array<MessageBoxType>(); _MessageTimes = array<string>(); _DisplayedMessages = {MessageBoxType::All}; _IsNotCatchable = true; _ScrollUp = Sprite::Sprite("MiniArrowUp.png".hstr()); _ScrollDown = Sprite::Sprite("MiniArrowDown.png".hstr()); } void _FixClone() override { _MessageTexts = _MessageTexts.clone(); _MessageTypes = _MessageTypes.clone(); _MessageTimes = _MessageTimes.clone(); _DisplayedMessages = _DisplayedMessages.clone(); Text::_FixClone(); } void _Show(dict<string, any> params) override { Text::_Show(params); _InvertMessages = Settings.Gui.MsgboxInvert; _GenerateText(); } void _Draw(bool callCallback) override { if (!_ActiveSelf || !_NeedDraw) { return; } if (!_InvertMessages) { _TextFlags = FontFlag(FontFlag::KeepTail | FontFlag::AlignBottom); _TextSkipLines = _ScrollLines; } else { _TextFlags = FontFlag::None; _TextSkipLines = _ScrollLines; } Text::_Draw(callCallback); if (_IsHovered) { Sprite::Sprite spr = Game.MousePos.y < _AbsolutePos.y + _Size.height / 2 ? _ScrollUp : _ScrollDown; spr.Draw(ipos(Game.MousePos.x - spr.Size.width / 2, Game.MousePos.y - spr.Size.height / 2)); } } void _Remove() override { if (_CursorHidden) { _CursorHidden = false; Settings.Gui.HideCursor = false; } Text::_Remove(); } void _Hover() override { Text::_Hover(); if (!Settings.Gui.HideCursor) { _CursorHidden = true; Settings.Gui.HideCursor = true; } } void _Unhover() override { Text::_Unhover(); if (_CursorHidden) { _CursorHidden = false; Settings.Gui.HideCursor = false; } } void _SizeChanged() override { _GenerateText(); Text::_SizeChanged(); } void _ActiveChanged() override { if (Active) { _GenerateText(); } Text::_ActiveChanged(); } void _MousePressed(MouseButton button) override { if (button == MouseButton::Left || button == MouseButton::WheelUp || button == MouseButton::WheelDown) { if (button == MouseButton::WheelUp || (button == MouseButton::Left && Game.MousePos.y < _AbsolutePos.y + _Size.height / 2)) { if (_InvertMessages && _Scroll > 0) { _Scroll--; } if (!_InvertMessages && _Scroll < _MaxScroll) { _Scroll++; } } else if (button == MouseButton::WheelDown || (button == MouseButton::Left && Game.MousePos.y >= _AbsolutePos.y + _Size.height / 2)) { if (_InvertMessages && _Scroll < _MaxScroll) { _Scroll++; } if (!_InvertMessages && _Scroll > 0) { _Scroll--; } } _GenerateText(); } Text::_MousePressed(button); } void AddMessage(string text, MessageBoxType type = MessageBoxType::Default) { // Set text _MessageTexts.insertLast(text); _MessageTypes.insertLast(type); // Set time int year = 0; int month = 0; int day = 0; int hour = 0; int minute = 0; int second = 0; int millisecond = 0; int microsecond = 0; int nanosecond = 0; nanotime time = Game.GetPrecisionTime(); Game.UnpackTime(time, year, month, day, hour, minute, second, millisecond, microsecond, nanosecond); string messageTime = (hour <= 9 ? "0" : "") + hour + ":" + (minute <= 9 ? "0" : "") + minute + ":" + (second <= 9 ? "0" : "") + second + " "; _MessageTimes.insertLast(messageTime); // Generate mess box if (_DisplayedMessages.find(type) != -1 || _DisplayedMessages.find(MessageBoxType::All) != -1) { if (_Scroll > 0 && _IsHovered) { _Scroll++; } else { _Scroll = 0; } } _GenerateText(); } void _GenerateText() { if (!Active) { return; } _Text = ""; if (_MessageTexts.length() == 0) { return; } int maxLines = Game.GetTextLines(_Size, _TextFont); if (maxLines <= 0) { _MaxScroll = 0; _ScrollLines = 0; return; } _ScrollLines = -1; int lines = 0; for (int i = _MessageTexts.length() - 1; i >= 0; i--) { string messageText = _MessageTexts[i]; MessageBoxType messageType = _MessageTypes[i]; string messageTime = _MessageTimes[i]; // Skip if not need to display if (_DisplayedMessages.find(messageType) == -1 && _DisplayedMessages.find(MessageBoxType::All) == -1) { continue; } // Skip scrolled lines int curLines = lines; isize resultSize; int skipLines = 0; Game.GetTextInfo(messageText, isize(_Size.width, 1000), TextFormat(_TextFont, FontFlag::None, 0), resultSize, skipLines); lines += skipLines; if (_ScrollLines < 0) { if (lines <= _Scroll) { continue; } _ScrollLines = _Scroll - curLines; } if (curLines - _Scroll < maxLines) { // Add to message box if (_InvertMessages) { _Text += messageText + "\n"; } else { _Text = messageText + "\n" + _Text; } } else { break; } } _MaxScroll = lines - maxLines; if (_ScrollLines < 0) { _ScrollLines = 0; } } void SetDisplayedMessages(MessageBoxType[] messageTypes) { _DisplayedMessages = messageTypes.clone(); _GenerateText(); } void ChangeDisplayedMessage(MessageBoxType messageType, bool enable) { bool generateText = false; int curIndex = _DisplayedMessages.find(messageType); if (enable && curIndex == -1) { _DisplayedMessages.insertLast(messageType); generateText = true; } else if (!enable && curIndex != -1) { _DisplayedMessages.removeAt(curIndex); generateText = true; } if (generateText) { _GenerateText(); } } void SetInvertMessages(bool invert) { _InvertMessages = invert; _GenerateText(); } void ClearMessages() { _MessageTexts.clear(); _MessageTypes.clear(); _MessageTimes.clear(); _GenerateText(); } }; const string ConsoleDataPrefix = "Console"; class Console : TextInput { bool get_DisableDeactivation() final { return _DisableDeactivation; } string get_HistoryStorageName() final { return _HistoryStorageName; } string[] get_History() final { return _History; } int get_HistoryMaxLength() final { return _HistoryMaxLength; } bool _DisableDeactivation; string _HistoryStorageName; string _HistoryActualStorageName; string[] _History; int _HistoryMaxLength; int _HistoryCur; void _Construct() override { TextInput::_Construct(); _History = array<string>(); SetCarriage(true); _HistoryStorageName = "Main"; } void _FixClone() override { _History = _History.clone(); TextInput::_FixClone(); } void _Show(dict<string, any> params) override { CurConsole = this; TextInput::_Show(params); } void _ActiveChanged() override { Game.SetScreenKeyboard(Active); TextInput::_ActiveChanged(); } void Toggle() { if (!Active) { // Activate console Activate(); } else if (_Text.length() == 0) { // Deactivate console if (!_DisableDeactivation) { Deactivate(); } } else { // Send text SendText(); } } void Activate() { // Activate console Object? parent = Parent; if (Active || parent == null || !parent.Active) { return; } // Load history string actualStorageName = ""; if (!_HistoryStorageName.isEmpty()) { actualStorageName = ConsoleDataPrefix + "_" + _HistoryStorageName; } if (_HistoryActualStorageName != actualStorageName) { _History.clear(); _HistoryActualStorageName = actualStorageName; if (_HistoryActualStorageName != "") { Serializer::Serializer data = Serializer::Serializer(); if (data.LoadFromCache(_HistoryActualStorageName) > 0) { data.Get(_History); } } } _HistoryCur = _History.length(); // Raise callbacks SetActive(true); } void Deactivate() { // Deactivate console if (_ActiveSelf) { SetActive(false); } } void SendText() { if (!Active) { return; } // Modify history _History.insertLast(_Text); for (int i = 0; i < _History.length() - 1;) { if (_History[i] == _History.last()) { _History.removeAt(i); } else { i++; } } // Trim history length int historyMaxLength = (_HistoryMaxLength != 0 ? _HistoryMaxLength : Settings.Input.ConsoleHistorySize); while (_History.length() > historyMaxLength) { _History.removeAt(0); } _HistoryCur = _History.length(); // Save history if (_HistoryActualStorageName != "") { // Filter commands string[] filteredHistory = array<string>(); for (int i = 0; i < _History.length(); i++) { if (_History[i].startsWith("~")) { filteredHistory.insertLast(_History[i]); } } Serializer::Serializer data = Serializer::Serializer(); data.Set(filteredHistory); data.SaveToCache(_HistoryActualStorageName); } // Send Game.OnConsoleMessage.Fire(_Text); // Clear text _Text = ""; _CarriagePos = 0; } void _ConsoleInput(KeyCode key, string text) { if (Active) { if (key == KeyCode::Up && _HistoryCur > 0) { _HistoryCur--; _Text = _History[_HistoryCur]; _CarriagePos = _Text.length(); } else if (key == KeyCode::Down) { if (_HistoryCur + 1 < _History.length()) { _HistoryCur++; _Text = _History[_HistoryCur]; _CarriagePos = _Text.length(); } else { _HistoryCur = _History.length(); _Text = ""; _CarriagePos = 0; } } else { TextInput::Input(key, text); } } if (key == KeyCode::Return || key == KeyCode::Numpadenter) { Toggle(); } } void Input(KeyCode key, string text) override { Text::Input(key, text); } void _Draw(bool callCallback) override { if (!_ActiveSelf || !_NeedDraw) { return; } if (callCallback) { _DrawCallback(); } string text = Text; if (_CarriagePos < 0) { _CarriagePos = 0; } if (_CarriagePos > text.length()) { _CarriagePos = text.length(); } text = text.substr(0, _CarriagePos) + (Game.FrameTime.milliseconds % 800 < 400 ? "!" : ".") + text.substr(_CarriagePos); Game.DrawText(text, _AbsolutePos, _Size, _TextColor, TextFormat(_TextFont, _TextFlags, _TextSkipLines)); Object::_Draw(false); if (callCallback) { OnPostDraw(); } } void SetDisableDeactivation(bool enable) { _DisableDeactivation = enable; } void SetHistoryStorage(string storageName) { // Set storage name _HistoryStorageName = storageName; } void SetHistoryMaxLength(int length) { _HistoryMaxLength = length; } }; class ItemView : Grid { int get_UserData() final { return _UserData; } int get_UserDataExt() final { return _UserDataExt; } bool get_UseSorting() final { return _UseSorting; } int _UserData; int _UserDataExt; bool _UseSorting; Item?[] _Items; int _ItemsGridSize; bool _Dirty; void _Construct() override { Grid::_Construct(); _Items = {}; _IsNotCatchable = true; } void _FixClone() override { _Items = _Items.clone(); Grid::_FixClone(); } void _ActiveChanged() override { if (_Dirty && Active) { _Dirty = false; Resort(); } Grid::_ActiveChanged(); } // Callbacks Item[]? OnGetItems() { return null; } int OnCheckItem(Item item) { return -1; } void _Init() override { _ItemsGridSize = _GridSize; Grid::_Init(); } Item? GetItem(int cellIndex) { Item? item = _Items[cellIndex]; return item != null && !item.IsDestroyed ? item : null; } bool HasItemId(ident itemId) { for (int i = 0; i < _Items.length(); i++) { Item? item = _Items[i]; if (item != null && !item.IsDestroyed && item.Id == itemId) { return true; } } return false; } bool HasSourceItemId(ident itemId) { Item[]? items = OnGetItems(); if (items == null) { return false; } for (int i = 0; i < items.length(); i++) { Item item = items[i]; if (item.Id == itemId) { return true; } } return false; } void Resort() { if (Active) { ResizeGrid(0); } else { _Dirty = true; } } void ResizeGrid(int size) override { Item?[] newItems = {}; Item[]? maybeItems = OnGetItems(); Item[] sourceItems = maybeItems != null ? maybeItems : array<Item>(); // A destroyed item here is expected, not an invariant break: the grid is most often rebuilt // *because* the server removed an item, and the supplier still hands back the pre-refresh // list. Drop those entries and render what is left instead of failing the whole refresh. Item[] items = {}; for (int i = 0; i < sourceItems.length(); i++) { if (!sourceItems[i].IsDestroyed) { items.insertLast(sourceItems[i]); } } if (!_UseSorting) { newItems.resize(_ItemsGridSize); for (int i = 0; i < items.length(); i++) { int itemIndex = OnCheckItem(items[i]); if (itemIndex >= 0 && itemIndex < newItems.length()) { newItems[itemIndex] = items[i]; } } } else { int[] sortValues = {}; for (int i = 0; i < items.length(); i++) { int sortValue = OnCheckItem(items[i]); if (sortValue >= 0) { bool added = false; for (int j = 0; j < newItems.length(); j++) { if (sortValues[j] > sortValue) { newItems.insertAt(j, items[i]); sortValues.insertAt(j, sortValue); added = true; break; } } if (!added) { newItems.insertLast(items[i]); sortValues.insertLast(sortValue); } } } } bool isChanged = newItems.length() != _Items.length(); for (int i = 0; i < newItems.length() && !isChanged; i++) { Item? newItem = newItems[i]; Item? oldItem = _Items[i]; isChanged = !AreSameItemHandles(newItem, oldItem); } if (isChanged) { _Items = newItems; Grid::ResizeGrid(_Items.length()); } } void SetUserData(int data) { _UserData = data; } void SetUserDataExt(int data) { _UserDataExt = data; } void SetUseSorting(bool enable) { _UseSorting = enable; } bool AreSameItemHandles(Item? first, Item? second) { if (first == null || second == null) { return first == null && second == null; } if (first.IsDestroyed || second.IsDestroyed) { return false; } return first == second; } }; // Implementation dict<GuiScreen, CreateScreenFunc> ScreenCreators = {}; Screen[] Screens = {}; Screen? CursorScreen; Object[] FocusedObjects = {}; Object? PressedObject; nanotime PressedObjectRepeatTime; Object? LastPressedObject; Object? HoveredObject; DragAndDropHandler[] DragAndDropHandlers = {}; IDropMenu? DropMenu; Console? CurConsole; // // Public API // void RegisterScreen(GuiScreen screenNum, CreateScreenFunc screenFunc) { UnregisterScreen(screenNum); if (screenNum == GuiScreen::None) { return; } ScreenCreators[screenNum] = screenFunc; // Precache Screen screen = CreateScreen(screenNum); if (screen.IsMultiinstance) { screen._Remove(); Screens.removeAt(Screens.findByRef(screen)); } } void ShowScreen(GuiScreen screenNum) { ShowScreen(screenNum, dict<string, any> = {}); } void ShowScreen(GuiScreen screenNum, dict<string, any> params) { Settings.Hex.ScrollMouseUp = false; Settings.Hex.ScrollMouseRight = false; Settings.Hex.ScrollMouseDown = false; Settings.Hex.ScrollMouseLeft = false; Settings.Hex.ScrollKeybUp = false; Settings.Hex.ScrollKeybRight = false; Settings.Hex.ScrollKeybDown = false; Settings.Hex.ScrollKeybLeft = false; // Manage multiinstance for (int i = Screens.length() - 1; i >= 0; i--) { // Find instance Screen screen = Screens[i]; if (screen.Num != screenNum) { continue; } // Move to top created instance if (!screen.IsMultiinstance) { ShowHideScreen(screen, params, true); return; } } // Create new instance Screen screen = CreateScreen(screenNum); ShowHideScreen(screen, params, true); } void HideScreen() { Screen? screen = GetActiveScreen(); if (screen != null) { HideScreen(screen.Num); } } void HideScreen(GuiScreen screenNum) { for (int i = Screens.length() - 1; i >= 0; i--) { // Find instance Screen screen = Screens[i]; if (!screen.Active || screen.Num != screenNum) { continue; } HideScreen(screen); // Hide only one screen per call break; } } void HideScreen(Screen screen) { if (screen.Active) { ShowHideScreen(screen, null, false); } } void HideAllScreens() { while (true) { Screen? screen = GetActiveScreen(); if (screen != null) { HideScreen(screen); } else { break; } } } void BringToFront(GuiScreen screenNum) { Screen? screen = GetScreen(screenNum); if (screen != null) { BringToFront(screen); } } void BringToFront(Screen screen) { if (!screen.Active) { return; } int topmostIndex = -1; for (int i = Screens.length() - 1; i >= 0; i--) { if (!Screens[i].Active) { continue; } if (topmostIndex == -1) { topmostIndex = i; } if (Screens[i] == screen) { if (i == topmostIndex) { break; // Already topmost } Screen topmost = Screens[topmostIndex]; Screens.removeAt(i); Screens.insertLast(screen); topmost._Disappear(); screen._Appear(); break; } } } Screen? GetScreen(GuiScreen screenNum) { Screen? lastMultiinstanceScreen = null; for (int i = Screens.length() - 1; i >= 0; i--) { Screen screen = Screens[i]; if (screen.Num == screenNum) { if (!screen.IsMultiinstance) { return screen; } else { lastMultiinstanceScreen = screen; } } } return lastMultiinstanceScreen; } Screen? GetActiveScreen() { for (int i = Screens.length() - 1; i >= 0; i--) { Screen screen = Screens[i]; if (screen.Active) { return screen; } } return null; } Screen? GetActiveScreen(GuiScreen screenNum) { for (int i = Screens.length() - 1; i >= 0; i--) { Screen screen = Screens[i]; if (screen.Num == screenNum && screen.Active) { return screen; } } return null; } bool IsScreenActive(GuiScreen screenNum) { return GetActiveScreen(screenNum) != null; } Screen[] GetActiveScreens() { Screen[] result = {}; for (int i = 0; i < Screens.length(); i++) { Screen screen = Screens[i]; if (screen.Active) { result.insertLast(screen); } } return result; } Object[] GetFocusedObjects() { for (int i = FocusedObjects.length() - 1; i >= 0; i--) { if (!FocusedObjects[i].Active) { FocusedObjects[i]._Unfocus(); FocusedObjects.removeAt(i); } } return FocusedObjects.clone(); } void SetFocusedObject(Object? obj) { // Unfocus current Object[] objects = GetFocusedObjects(); FocusedObjects.clear(); for (int i = 0; i < objects.length(); i++) { objects[i]._Unfocus(); } if (obj == null) { return; } // Focus new Object? groupObj = obj.Parent; while (groupObj != null) { Object? cursor = groupObj; if (cursor == null || cursor.FocusGroup) { break; } groupObj = cursor.Parent; } if (groupObj != null) { AddFocusRecursively(groupObj); } else { obj._Focus(); FocusedObjects.insertLast(obj); } } void AddFocusRecursively(Object obj) { obj._Focus(); FocusedObjects.insertLast(obj); for (int i = 0; i < obj._Children.length(); i++) { AddFocusRecursively(obj._Children[i]); } } Object? GetPressedObject() { Object? pressedObject = PressedObject; if (pressedObject != null && !pressedObject.Active) { PressedObject = null; pressedObject._MouseUp(true); LastPressedObject = null; } return PressedObject; } Object? GetLastPressedObject() { Object? lastPressedObject = LastPressedObject; if (lastPressedObject != null && !lastPressedObject.Active) { LastPressedObject = null; } return LastPressedObject; } Object? GetDraggedObject() { Object? pressedObject = PressedObject; if (pressedObject != null && !pressedObject.Active) { PressedObject = null; pressedObject._MouseUp(true); } Object? draggedObject = PressedObject; return draggedObject != null && draggedObject._IsDragged ? draggedObject : null; } Object? GetHoveredObject() { Object? hoveredObject = HoveredObject; if (hoveredObject != null && !hoveredObject.Active) { HoveredObject = null; hoveredObject._Unhover(); } return HoveredObject; } void NextTextInput() { Screen? screen = GetActiveScreen(); if (screen == null) { return; } TextInput? curTextInput = null; Object[] objects = GetFocusedObjects(); for (int i = 0; i < objects.length() && curTextInput == null; i++) { curTextInput = cast<TextInput>(objects[i]); } FindNextTextInputSkipObj = curTextInput; TextInput? textInput = FindNextTextInput(screen); FindNextTextInputSkipObj = null; if (textInput == null && curTextInput != null) { textInput = FindNextTextInput(screen); } if (textInput != null && textInput != curTextInput) { SetFocusedObject(textInput); } } Object? FindNextTextInputSkipObj = null; TextInput? FindNextTextInput(Object obj) { if (FindNextTextInputSkipObj == null) { TextInput? textInput = cast<TextInput>(obj); if (textInput != null) { return textInput; } } else if (obj == FindNextTextInputSkipObj) { FindNextTextInputSkipObj = null; } for (int i = 0; i < obj._Children.length(); i++) { if (obj._Children[i]._ActiveSelf) { TextInput? textInput = FindNextTextInput(obj._Children[i]); if (textInput != null) { return textInput; } } } return null; } MessageBox[] CollectMessageBoxes() { MessageBox[] messageBoxes = {}; for (int i = Screens.length() - 1; i >= 0; i--) { CollectMessageBoxes(Screens[i], messageBoxes); } return messageBoxes; } void CollectMessageBoxes(Object obj, MessageBox[] messageBoxes) { MessageBox? mb = cast<MessageBox>(obj); if (mb != null) { messageBoxes.insertLast(mb); } for (int i = 0; i < obj._Children.length(); i++) { CollectMessageBoxes(obj._Children[i], messageBoxes); } } bool IsConsoleActive() { for (int i = Screens.length() - 1; i >= 0; i--) { if (IsConsoleActive(Screens[i])) { return true; } } return false; } bool IsConsoleActive(Object obj) { if (!obj.ActiveSelf) { return false; } Console? console = cast<Console>(obj); if (console != null) { return true; } for (int i = 0; i < obj._Children.length(); i++) { if (IsConsoleActive(obj._Children[i])) { return true; } } return false; } void AddDragAndDropHandler(DragAndDropHandler handler) { DragAndDropHandlers.insertLast(handler); } void SetDropMenu(IDropMenu dropMenu) { DropMenu = dropMenu; } bool CheckHit(ipos pos) { for (int i = 0; i < Screens.length(); i++) { if (Screens[i].Active && Screens[i].FindHit(pos) != null) { return true; } } return false; } bool IsModalScreenActive() { for (int i = Screens.length() - 1; i >= 0; i--) { if (Screens[i].Active && Screens[i].IsModal) { return true; } } return false; } // // Engine callbacks // [[Async]] void EngineCallback_Start() { // Register custom screens GuiScreens::InitializeScreens(); } void EngineCallback_Draw() { for (int i = 0; i < Screens.length(); i++) { Screen screen = Screens[i]; if (screen.Active) { screen._Draw(true); } } } void EngineCallback_DrawCursor() { // Dragged object Object? draggedObject = GetDraggedObject(); if (draggedObject != null) { ipos pos; isize size; draggedObject.GetWholeSize(pos, size); draggedObject.Draw(ipos(Game.MousePos.x - size.width / 2, Game.MousePos.y - size.height / 2)); return; } // Drop menu IDropMenu? dropMenu = DropMenu; if (dropMenu != null) { dropMenu.Draw(); } // Cursor Screen? cursorScreen = CursorScreen; if (cursorScreen != null) { cursorScreen._Draw(true); } } void EngineCallback_MouseDown(MouseButton button) { if (button == MouseButton::Left && !FocusedObjects.isEmpty()) { SetFocusedObject(null); } // Release mouse from current object Object? pressedObject = PressedObject; if (pressedObject != null) { PressedObject = null; pressedObject._MouseUp(true); } // Process mouse down Screen? screen = GetActiveScreen(); if (screen == null) { return; } // Global callback screen._GlobalMouseDown(button); // Drop menu IDropMenu? dropMenu = DropMenu; if (dropMenu != null) { dropMenu.MouseDown(button); } // Check hit on current screen Object? hitObj = screen.FindMouseHit(); if (hitObj != null) { // Handle pressed object PressedObject = hitObj; PressedObjectRepeatTime = Game.FrameTime + Time::Milliseconds(500); LastPressedObject = hitObj; hitObj._MouseDown(button); // Handle focused object if (button == MouseButton::Left) { SetFocusedObject(hitObj); } return; } // Close on miss if (button == MouseButton::Left && screen.IsCloseOnMiss) { ShowHideScreen(screen, null, false); return; } // Switch to another screen if (button == MouseButton::Left && !screen.IsModal) { int screenIndex = Screens.findByRef(screen); for (int i = screenIndex - 1; i >= 0; i--) { Screen nextScreen = Screens[i]; if (!nextScreen.Active) { continue; } if (nextScreen.IsModal || nextScreen.FindMouseHit() != null) { // Show screen to top and click on it ShowHideScreen(nextScreen, null, true); EngineCallback_MouseDown(button); return; } } } } void EngineCallback_MouseUp(MouseButton button) { // Global handler Screen? screen = GetActiveScreen(); if (screen != null) { screen._GlobalMouseUp(button); } // Release mouse from current object GetPressedObject(); // Refresh state Object? pressed = PressedObject; if (pressed != null && button == pressed._PressedButton) { Object obj = pressed; PressedObject = null; bool isDragged = obj._IsDragged; obj._MouseUp(false); if (obj.IsMouseHit()) { if (screen != null) { screen._GlobalMouseClick(button); } obj.MouseClick(button); } if (isDragged && screen != null) { Object? target = screen.FindMouseHit(); for (int i = 0; i < DragAndDropHandlers.length(); i++) { if (DragAndDropHandlers[i](obj, target)) { break; } } } } // Drop menu IDropMenu? dropMenu = DropMenu; if (dropMenu != null) { dropMenu.MouseUp(button); } } void EngineCallback_MouseMove(ipos offset) { bool realMove = offset != ZERO_IPOS; // Drop menu IDropMenu? dropMenu = DropMenu; if (dropMenu != null) { if (dropMenu.PreMouseMove(offset)) { return; } } // Move mouse for active screen Screen? screen = GetActiveScreen(); Object? hoveredObj; if (screen != null) { if (realMove) { screen._GlobalMouseMove(); } hoveredObj = screen.FindMouseHit(); if (realMove && hoveredObj != null) { hoveredObj._MouseMove(); } } // Change hover object GetHoveredObject(); // Refresh state Object? currentHovered = HoveredObject; if (currentHovered != hoveredObj) { if (currentHovered != null) { HoveredObject = null; currentHovered._Unhover(); } HoveredObject = hoveredObj; if (hoveredObj != null) { hoveredObj._Hover(); } } // Drop menu if (dropMenu != null) { dropMenu.PostMouseMove(offset); } } void EngineCallback_KeyDown(KeyCode key, string text) { // Global input handler for (int i = Screens.length() - 1; i >= 0; i--) { if (Screens[i].Active) { Screens[i]._GlobalInput(key, text); } } // Console input Console? curConsole = CurConsole; if (curConsole != null) { curConsole._ConsoleInput(key, text); } // Focused object input if (curConsole == null || !curConsole.Active) { Screen? screen = GetActiveScreen(); Object[] objects = GetFocusedObjects(); for (int i = 0; i < objects.length(); i++) { if (objects[i] != screen) { objects[i].Input(key, text); } } } } void EngineCallback_KeyUp(KeyCode key) { // ... } void EngineCallback_InputLost() { Object? hoveredObject = HoveredObject; if (hoveredObject != null) { HoveredObject = null; hoveredObject._Unhover(); } SetFocusedObject(null); Object? pressedObject = PressedObject; if (pressedObject != null) { PressedObject = null; pressedObject._MouseUp(true); } IDropMenu? dropMenu = DropMenu; if (dropMenu != null) { dropMenu.InputLost(); } } void EngineCallback_Loop() { if (Game.IsConnecting()) { Input::ReleaseMouse(); } GetPressedObject(); // Refresh state Object? pressedObject = PressedObject; if (pressedObject != null) { nanotime tick = Game.FrameTime; if (tick >= PressedObjectRepeatTime && pressedObject.IsMouseHit()) { PressedObjectRepeatTime = tick + Time::Milliseconds(40); Screen? screen = GetActiveScreen(); if (screen != null) { screen._GlobalMousePressed(pressedObject._PressedButton); } pressedObject._MousePressed(pressedObject._PressedButton); } } EngineCallback_MouseMove(ipos(0, 0)); // Drop menu IDropMenu? dropMenu = DropMenu; if (dropMenu != null) { dropMenu.Loop(); } } void RefreshItemViewsByItemId(ident itemId) { if (itemId == ZERO_IDENT) { return; } ItemView[] itemViews = {}; CollectItemView(itemViews); for (int i = 0; i < itemViews.length(); i++) { ItemView itemView = itemViews[i]; if (itemView.HasItemId(itemId) || itemView.HasSourceItemId(itemId)) { itemView.Resort(); } } } void RefreshItemViewsByUserDataExt(int userDataExt) { RefreshItemViewsByUserDataExts({userDataExt}); } void RefreshItemViewsByUserDataExts(int[] userDataExts) { if (userDataExts.isEmpty()) { return; } ItemView[] itemViews = {}; CollectItemView(itemViews); for (int i = 0; i < itemViews.length(); i++) { if (userDataExts.find(itemViews[i].UserDataExt) != -1) { itemViews[i].Resort(); } } } void CollectItemView(ItemView[] itemViews) { for (int i = Screens.length() - 1; i >= 0; i--) { CollectItemView(Screens[i], itemViews); } } void CollectItemView(Object obj, ItemView[] itemViews) { ItemView? itemView = cast<ItemView>(obj); if (itemView != null) { itemViews.insertLast(itemView); } for (int i = 0; i < obj._Children.length(); i++) { CollectItemView(obj._Children[i], itemViews); } } // // Custom callbacks // void Callback_OnResolutionChanged() { for (int i = 0; i < Screens.length(); i++) { Screens[i]._RefreshPositionRecursive(); } } void Callback_OnLanguageChanged() { for (int i = 0; i < Screens.length(); i++) { Screens[i]._RefreshTextRecursive(); Screens[i]._RefreshPositionRecursive(); } } // // Internal // Screen CreateScreen(GuiScreen screenNum) { verify(ScreenCreators.exists(screenNum), "No screen creator registered for this screen", screenNum); CreateScreenFunc screenFunc = ScreenCreators[screenNum]; Screen screen = screenFunc(); screen._Num = screenNum; screen._ActiveSelf = false; screen._IsRegistered = true; screen._InputKeyPressed = Input::GetKeyPressed(); screen._InputMousePressed = Input::GetMousePressed(); if (screenNum != GuiScreen::Cursor) { Screens.insertLast(screen); } else { CursorScreen = screen; screen._ActiveSelf = true; } screen._Init(); return screen; } void UnregisterScreen(GuiScreen screenNum) { for (int i = Screens.length() - 1; i >= 0; i--) { Screen screen = Screens[i]; if (screen.Num == screenNum) { if (screen.Active) { ShowHideScreen(screen, null, false); } if (!screen.IsMultiinstance) { screen._Remove(); Screens.removeAt(i); } screen._IsRegistered = false; } } } void ShowHideScreen(Screen screen, dict<string, any> params, bool show) { // Clean hovered/focused/pressed objects Object? hoveredObject = HoveredObject; if (hoveredObject != null) { HoveredObject = null; hoveredObject._Unhover(); } SetFocusedObject(null); Object? pressedObject = PressedObject; if (pressedObject != null) { PressedObject = null; pressedObject._MouseUp(true); } if (show) { // Hide active screen Screen? activeScreen = GetActiveScreen(); if (activeScreen != null && activeScreen != screen) { activeScreen._Disappear(); } // Focus and hover new elements TextInput? textInput = FindNextTextInput(screen); if (textInput != null) { SetFocusedObject(textInput); } Object? newHovered = screen.FindMouseHit(); HoveredObject = newHovered; if (newHovered != null) { newHovered._Hover(); } // Move to top if (Screens.last() != screen) { Screens.removeAt(Screens.findByRef(screen)); Screens.insertLast(screen); } // Callback if (!screen.Active) { screen.SetActive(true); } screen._Show(params); // Params may change, so need call again screen._Appear(); Game.OnScreenChange.Fire(true, screen.Num, params); } else { // Ignore redundant hide if (!screen.Active) { return; } // Callbacks screen._Disappear(); screen._Hide(); screen.SetActive(false); Game.OnScreenChange.Fire(false, screen.Num, params); // Remove multiinstance if (screen.IsMultiinstance) { screen._Remove(); Screens.removeAt(Screens.findByRef(screen)); } // Appear behind screen if (screen.IsMultiinstance || screen == Screens.last()) { Screen? appearScreen = GetActiveScreen(); // Move to top if (appearScreen != null) { Screens.removeAt(Screens.findByRef(appearScreen)); Screens.insertLast(appearScreen); appearScreen._Appear(); } } } } int GetScreenCount(GuiScreen screenNum) { int count = 0; for (int i = Screens.length() - 1; i >= 0; i--) { if (Screens[i].Num == screenNum) { count++; } } return count; } void Highlight(GuiScreen screenNum, string objName, timespan duration = Time::Seconds(5), int skip = 0, bool all = false) { if (all) { Screen? screen = GetScreen(screenNum); int count = screen != null ? screen.Count(objName) : 0; for (int i = 0; i < count; i++) { Game.StartTimeEvent(Time::Asap(), HighlightProcess, {screenNum, objName, duration.milliseconds, i}); } } else { Game.StartTimeEvent(Time::Asap(), HighlightProcess, {screenNum, objName, duration.milliseconds, skip}); } } [[TimeEvent]] [[Async]] void HighlightProcess(any[] data) { GuiScreen screenNum = GuiScreen(int(data[0])); string objName = data[1]; timespan duration = Time::Milliseconds(data[2]); int skip = data[3]; Screen? screen = GetScreen(screenNum); if (screen == null) { return; } Object? obj = screen.Find(objName, skip: skip); Panel? panel = cast<Panel>(obj); Text? text = cast<Text>(obj); if (obj == null || (panel == null && text == null)) { return; } if (obj._Highlighted) { return; } obj._Highlighted = true; Sprite::Sprite? panelSpr = panel != null ? panel.BackgroundImage : null; ucolor panelColor = panelSpr != null ? panelSpr.Color : ZERO_UCOLOR; ucolor textColor = text != null ? text.TextColor : ZERO_UCOLOR; nanotime startTime = Game.FrameTime; while (true) { timespan elapsedTime = Game.FrameTime - startTime; if (elapsedTime > duration) { break; } float intensity = Math::RoundToInt(elapsedTime.milliseconds % 1000) / 500.0; intensity = intensity < 1.0 ? intensity : 2.0 - intensity; ucolor color = panelSpr != null ? panelColor : textColor; color.red = color.red + Math::RoundToInt(float(255 - color.red) * intensity); color.green = color.green + Math::RoundToInt(float(255 - color.green) * intensity); color.blue = color.blue + Math::RoundToInt(float(255 - color.blue) * intensity); if (panelSpr != null) { panelSpr.Color = color; } else if (text != null) { text.SetTextColor(color); } Yield(10); } if (panelSpr != null) { panelSpr.Color = panelColor; } else if (text != null) { text.SetTextColor(textColor); } obj._Highlighted = false; } #endif }