562 lines
16 KiB
C++
562 lines
16 KiB
C++
//- sixten: Views
|
|
inline workspace_view *W_CreateNewView(workspace_view_kind Kind, workspace_panel *Parent)
|
|
{
|
|
arena *Arena = ArenaAlloc(Kilobytes(4), true);
|
|
workspace_view *View = PushStruct(Arena, workspace_view);
|
|
View->Arena = Arena;
|
|
View->Kind = Kind;
|
|
View->Parent = Parent;
|
|
|
|
switch(View->Kind)
|
|
{
|
|
case W_ViewKind_Settings:
|
|
{
|
|
View->Data = PushStruct(View->Arena, workspace_view_settings);
|
|
} break;
|
|
|
|
case W_ViewKind_FileLister:
|
|
{
|
|
workspace_view_file_lister *Lister = PushStruct(View->Arena, workspace_view_file_lister);
|
|
View->Data = Lister;
|
|
Lister->Path = StrLit("data");
|
|
|
|
Lister->SelectedItem = -1;
|
|
} break;
|
|
|
|
case W_ViewKind_TextEditor:
|
|
{
|
|
View->Data = PushStruct(View->Arena, workspace_view_text_editor);
|
|
|
|
workspace_view_text_editor *Editor = (workspace_view_text_editor *)View->Data;
|
|
Editor->ProcessingArena = ArenaAlloc(Gigabytes(1));
|
|
Editor->Text = MutableStringAllocate(Gigabytes(1));
|
|
Editor->HistoryArena = ArenaAlloc(Gigabytes(1));
|
|
|
|
SenDLLInit(&Editor->History.Sentinel);
|
|
Editor->History.At = &Editor->History.Sentinel;
|
|
Editor->SavePoint = Editor->History.At;
|
|
|
|
workspace_text_data TextData = W_TextDataFromString(Editor->ProcessingArena, Editor->Text.String);
|
|
Editor->Tokens = TextData.Tokens;
|
|
Editor->Lines = TextData.Lines;
|
|
|
|
Editor->FontSize = 13.0f;
|
|
} break;
|
|
|
|
case W_ViewKind_NavEditor:
|
|
{
|
|
View->Data = PushStruct(View->Arena, workspace_view_nav_editor);
|
|
} break;
|
|
|
|
case W_ViewKind_ImageViewer:
|
|
{
|
|
View->Data = PushStruct(View->Arena, workspace_view_image_viewer);
|
|
workspace_view_image_viewer *Viewer = (workspace_view_image_viewer *)View->Data;
|
|
Viewer->Scale = 1.0f;
|
|
//- sixten(TODO): write an image viewer
|
|
// beign with loading the image from the file lister load thingy
|
|
// show it on build
|
|
// release the handle when done (and add a way to remove texture ig)
|
|
} break;
|
|
|
|
case W_ViewKind_Error:
|
|
{
|
|
View->Data = PushStruct(View->Arena, workspace_view_error);
|
|
} break;
|
|
|
|
default: break;
|
|
}
|
|
|
|
DLLInsertLast(Parent->FirstView, Parent->LastView, View);
|
|
|
|
if(View->Parent)
|
|
{
|
|
Parent->CurrentView = View;
|
|
}
|
|
|
|
return(View);
|
|
}
|
|
|
|
inline void W_DestroyView(workspace_view *View)
|
|
{
|
|
switch(View->Kind)
|
|
{
|
|
case W_ViewKind_TextEditor:
|
|
{
|
|
workspace_view_text_editor *Editor = (workspace_view_text_editor *)View->Data;
|
|
ArenaRelease(Editor->ProcessingArena);
|
|
MutableStringRelease(&Editor->Text);
|
|
ArenaRelease(Editor->HistoryArena);
|
|
} break;
|
|
|
|
case W_ViewKind_ImageViewer:
|
|
{
|
|
workspace_view_image_viewer *Viewer = (workspace_view_image_viewer *)View->Data;
|
|
GlobalRenderCommands->DeallocateTexture(Viewer->Texture);
|
|
} break;
|
|
|
|
default: break;
|
|
}
|
|
|
|
// sixten(NOTE): This function does not ensure that the view is not being used anywhere else.
|
|
ArenaRelease(View->Arena);
|
|
}
|
|
|
|
inline b32 W_ViewIsCurrent(workspace_view *View)
|
|
{
|
|
workspace *Workspace = W_GetState();
|
|
|
|
b32 Result = (Workspace->CurrentPanel && Workspace->CurrentPanel->CurrentView == View);
|
|
return(Result);
|
|
}
|
|
|
|
inline string W_NameFromView(arena *Arena, workspace_view *View)
|
|
{
|
|
string Result = StrLit("Unnamed view");
|
|
switch(View->Kind)
|
|
{
|
|
case W_ViewKind_Startup: { Result = StrLit("Welcome"); } break;
|
|
case W_ViewKind_Settings: { Result = StrLit("Settings"); } break;
|
|
case W_ViewKind_TextEditor:
|
|
{
|
|
workspace_view_text_editor *Editor = (workspace_view_text_editor *)View->Data;
|
|
if(AreEqual(Editor->FileName, StrLit("")))
|
|
{
|
|
Result = StrLit("Open File");
|
|
}
|
|
else
|
|
{
|
|
if(Editor->History.At == Editor->SavePoint)
|
|
{
|
|
Result = PushString(Arena, Editor->FileName);
|
|
}
|
|
else
|
|
{
|
|
Result = PushFormat(Arena, "* %S", Editor->FileName);
|
|
}
|
|
}
|
|
} break;
|
|
case W_ViewKind_SceneView: { Result = StrLit("Scene View"); } break;
|
|
case W_ViewKind_NavEditor: { Result = StrLit("Navigation Editor"); } break;
|
|
case W_ViewKind_FileLister: { Result = StrLit("File Lister"); } break;
|
|
case W_ViewKind_ImageViewer:
|
|
{
|
|
workspace_view_image_viewer *Viewer = (workspace_view_image_viewer *)View->Data;
|
|
Result = PushString(Arena, Viewer->FileName);
|
|
} break;
|
|
case W_ViewKind_Error: { Result = StrLit("Error"); } break;
|
|
default: {} break;
|
|
}
|
|
|
|
return(Result);
|
|
}
|
|
|
|
static b32 W_ImageViewerSetup(workspace_view *View, string Name, string Contents)
|
|
{
|
|
b32 Result = false;
|
|
workspace_view_image_viewer *Viewer = (workspace_view_image_viewer *)View->Data;
|
|
Viewer->Texture = EmptyRenderHandle();
|
|
if(Contents.Count && Contents.Data)
|
|
{
|
|
s32 Width, Height, BPP;
|
|
u8 *TextureData = stbi_load_from_memory(Contents.Data, Contents.Count, &Width, &Height, &BPP, 0);
|
|
if(TextureData)
|
|
{
|
|
render_texture_format TextureFormat = Render_TextureFormat_Invalid;
|
|
switch(BPP)
|
|
{
|
|
InvalidDefaultCase;
|
|
case 1: { TextureFormat = Render_TextureFormat_R8; } break;
|
|
case 3: { TextureFormat = Render_TextureFormat_RGB8; } break;
|
|
case 4: { TextureFormat = Render_TextureFormat_RGBA8; } break;
|
|
}
|
|
|
|
Viewer->Texture = GlobalRenderCommands->AllocateTexture(V2S32(Width, Height), TextureFormat, true, TextureData);
|
|
|
|
Result = true;
|
|
|
|
stbi_image_free(TextureData);
|
|
}
|
|
}
|
|
Viewer->FileName = PushString(View->Arena, Name);
|
|
|
|
return(Result);
|
|
}
|
|
|
|
//- sixten: Builder code
|
|
|
|
static void W_BuildSettingsTabButton(workspace_view_settings *Settings, char *Name, workspace_settings_category Category)
|
|
{
|
|
b32 IsSelected = (Settings->Category == Category);
|
|
|
|
v4 Color = LinearBlend(Theme_TextColor, Theme_HighlightBorderColor, AC_AnimateValueF(IsSelected, IsSelected, 0.3, "Workspace Settings %s %p", Name, Settings));
|
|
|
|
UI_SetNextFont(Font_Bold);
|
|
UI_SetNextHeight(UI_TextContent(0, 1));
|
|
UI_SetNextTextColor(Color);
|
|
|
|
ui_box *Box = UI_MakeBoxF(UI_BoxFlag_DrawText |
|
|
UI_BoxFlag_Clickable,
|
|
Name);
|
|
|
|
ui_signal Signal = UI_SignalFromBox(Box);
|
|
if(Signal.Hovering)
|
|
{
|
|
Platform.SetCursor(PlatformCursor_Hand);
|
|
}
|
|
if(Signal.Pressed)
|
|
{
|
|
Settings->Category = Category;
|
|
}
|
|
}
|
|
|
|
static b32 UI_DropdownSelection(char **Alternatives, s32 AlternativeCount, b32 *Open, s32 *Selected)
|
|
{
|
|
b32 Result = false;
|
|
b32 ActiveInDropdown = false;
|
|
|
|
UI_SetNextLayoutAxis(Axis2_X);
|
|
ui_box *DropdownBox = UI_MakeBoxF(UI_BoxFlag_DrawBackground |
|
|
UI_BoxFlag_DrawBorder |
|
|
UI_BoxFlag_HotAnimation |
|
|
UI_BoxFlag_ActiveAnimation |
|
|
UI_BoxFlag_Clickable,
|
|
"Dropdown");
|
|
UI_Parent(DropdownBox)
|
|
{
|
|
UI_Width(UI_Percent(1, 0)) UI_LabelF(Alternatives[*Selected]);
|
|
UI_BackgroundColor(Theme_BorderColor) UI_Width(UI_Pixels(1, 1)) UI_MakeBoxF(UI_BoxFlag_DrawBackground, "");
|
|
UI_Width(UI_Pixels(25, 1)) UI_Font(Font_Icons) UI_LabelF("%U", *Open?FontIcon_DownDir:FontIcon_RightDir);
|
|
}
|
|
|
|
ui_signal DropdownSignal = UI_SignalFromBox(DropdownBox);
|
|
if(DropdownSignal.Pressed)
|
|
{
|
|
*Open = !(*Open);
|
|
}
|
|
|
|
if(AreEqual(UI_ActiveKey(), DropdownBox->Key))
|
|
{
|
|
ActiveInDropdown = true;
|
|
}
|
|
|
|
r32 OpenTransition = AC_AnimateValueF(*Open, 0, 0.175, "UI Dropdown %p%p", Alternatives, Open);
|
|
|
|
if(OpenTransition > 0.1)
|
|
{
|
|
UI_Tooltip
|
|
{
|
|
UI_SetNextFixedP(V2(DropdownBox->Rect.Min.x, DropdownBox->Rect.Max.y));
|
|
UI_SetNextCornerRadius(4);
|
|
UI_SetNextWidth(UI_Pixels(200, 1));
|
|
UI_SetNextHeight(UI_ChildrenSum(OpenTransition, 1));
|
|
UI_Parent(UI_MakeBoxF(UI_BoxFlag_Clip |
|
|
UI_BoxFlag_DrawDropShadow |
|
|
UI_BoxFlag_FloatingX |
|
|
UI_BoxFlag_FloatingY, "Dropdown Contents %p%p", Alternatives, Open))
|
|
{
|
|
UI_PushWidth(UI_Percent(1, 1));
|
|
|
|
for(s64 Index = 0;
|
|
Index < Round(AlternativeCount*OpenTransition);
|
|
++Index)
|
|
{
|
|
ui_signal ButtonSignal = UI_ButtonF(Alternatives[Index]);
|
|
if(AreEqual(UI_ActiveKey(), ButtonSignal.Box->Key))
|
|
{
|
|
ActiveInDropdown = true;
|
|
}
|
|
|
|
if(ButtonSignal.Pressed)
|
|
{
|
|
*Selected = Index;
|
|
Result = true;
|
|
}
|
|
}
|
|
|
|
UI_PopWidth();
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!ActiveInDropdown && !AreEqual(UI_ActiveKey(), UI_EmptyKey()))
|
|
{
|
|
*Open = false;
|
|
}
|
|
|
|
return(Result);
|
|
}
|
|
|
|
|
|
UI_CUSTOM_DRAW_CALLBACK(W_ImageViewerUICallback)
|
|
{
|
|
workspace_view_image_viewer *Viewer = (workspace_view_image_viewer *)Data;
|
|
v2_r32 ImageDim = ConvertV2ToR32(DimFromTexture(Viewer->Texture));
|
|
v2_r32 ScaleImageDim = ImageDim*Viewer->Scale;
|
|
|
|
v4 Color = Color_White;
|
|
range2_r32 Dest;
|
|
Dest.Min = (Box->Rect.Max+Box->Rect.Min)*0.5-ScaleImageDim*0.5+Viewer->Offset*Viewer->Scale;
|
|
Dest.Max = Dest.Min+ScaleImageDim;
|
|
range2_r32 Source = Range2R32(V2R32(0, 0), ImageDim);
|
|
PushTexturedQuad(Group, Dest, Source, Color, Color, Color, Color, 0, 0, 0, Viewer->Texture);
|
|
}
|
|
|
|
static void W_BuildImageViewer(workspace_view *View)
|
|
{
|
|
workspace_view_image_viewer *Viewer = (workspace_view_image_viewer *)View->Data;
|
|
|
|
//- sixten: find image dim
|
|
v2_r32 ImageDim = ConvertV2ToR32(DimFromTexture(Viewer->Texture));
|
|
|
|
UI_WidthFill UI_HeightFill
|
|
{
|
|
ui_box *ImageBox = UI_MakeBoxF(UI_BoxFlag_DrawBorder|UI_BoxFlag_Clickable, "Workspace View Image Viewer %p", View);
|
|
UI_EquipBoxCustomDrawCallback(ImageBox, W_ImageViewerUICallback, Viewer);
|
|
|
|
//- sixten: handle scrolling
|
|
if(AreEqual(UI_HotKey(), ImageBox->Key))
|
|
{
|
|
for(platform_event *Event = UI_EventList()->First;
|
|
Event != 0;
|
|
Event = Event->Next)
|
|
{
|
|
if(Event->Type == PlatformEvent_MouseScroll && Event->Scroll.y != 0)
|
|
{
|
|
Viewer->Scale *= Pow(1.10f, Event->Scroll.y);
|
|
Platform_ConsumeEvent(UI_EventList(), Event);
|
|
}
|
|
}
|
|
}
|
|
|
|
ui_signal Signal = UI_SignalFromBox(ImageBox);
|
|
if(Signal.Dragging)
|
|
{
|
|
if(Signal.Pressed)
|
|
{
|
|
UI_StoreDragV2(Viewer->Offset);
|
|
}
|
|
v2_r32 StartOffset = UI_GetDragV2();
|
|
v2_r32 EndOffset = StartOffset+Signal.DragDelta*(1.0f/Viewer->Scale);
|
|
|
|
Viewer->Offset = EndOffset;
|
|
}
|
|
}
|
|
UI_WidthFill UI_Height(UI_Em(3, 1))
|
|
UI_Row(UI_BoxFlag_DrawBackground|UI_BoxFlag_DrawBorder)
|
|
{
|
|
UI_Width(UI_TextContent(15, 1))
|
|
{
|
|
UI_Label(Viewer->FileName);
|
|
UI_LabelF("Width: %i Height: %i", (int)ImageDim.x, (int)ImageDim.y);
|
|
}
|
|
UI_Spacer(UI_Percent(1, 0));
|
|
}
|
|
}
|
|
|
|
static void W_BuildSceneView(workspace_view *View)
|
|
{
|
|
workspace *Workspace = W_GetState();
|
|
SV_BuildSceneView(Workspace->Input);
|
|
}
|
|
|
|
static void W_BuildSettings(workspace_view *View)
|
|
{
|
|
workspace_view_settings *Settings = (workspace_view_settings *)View->Data;
|
|
workspace *Workspace = W_GetState();
|
|
|
|
UI_Height(UI_ChildrenSum(1, 1))
|
|
UI_Column() UI_Padding(UI_Pixels(50, 0))
|
|
UI_Row() UI_Padding(UI_Pixels(50, 0))
|
|
{
|
|
UI_Size(UI_TextContent(0, 1), UI_TextContent(10, 1))
|
|
UI_Font(Font_Bold) UI_FontSize(36)
|
|
UI_LabelF("Settings");
|
|
}
|
|
|
|
UI_LayoutAxis(Axis2_X)
|
|
UI_Size(UI_Percent(1, 0), UI_Percent(1, 0))
|
|
UI_Parent(UI_MakeBoxF(0, ""))
|
|
{
|
|
UI_Width(UI_Pixels(300, 1))
|
|
UI_Parent(UI_MakeBoxF(0, "Navigation"))
|
|
{
|
|
UI_Row() UI_Padding(UI_Pixels(50, 1))
|
|
UI_Width(UI_Percent(1, 0)) UI_Column() UI_Padding(UI_Percent(1, 0))
|
|
UI_Height(UI_ChildrenSum(1, 1)) UI_LayoutAxis(Axis2_Y) UI_Parent(UI_MakeBoxF(0, ""))
|
|
{
|
|
W_BuildSettingsTabButton(Settings, "All", W_Settings_All);
|
|
UI_Spacer(UI_Pixels(30, 1));
|
|
W_BuildSettingsTabButton(Settings, "General", W_Settings_General);
|
|
UI_Spacer(UI_Pixels(30, 1));
|
|
W_BuildSettingsTabButton(Settings, "Developer", W_Settings_Developer);
|
|
|
|
UI_Spacer(UI_Pixels(150, 1));
|
|
}
|
|
}
|
|
|
|
UI_CornerRadius(5)
|
|
UI_Width(UI_Pixels(1.25, 1))
|
|
UI_BackgroundColor(Color_Grey)
|
|
UI_MakeBoxF(UI_BoxFlag_DrawBackground, "Separator");
|
|
|
|
UI_Padding(UI_Pixels(70, 0))
|
|
UI_LayoutAxis(Axis2_Y)
|
|
UI_Width(UI_Percent(1, 0))
|
|
UI_Parent(UI_MakeBoxF(0, "Tab"))
|
|
UI_Size(UI_TextContent(0, 1), UI_TextContent(10, 1))
|
|
{
|
|
UI_SetNextSize(UI_Percent(1, 0), UI_Percent(1, 0));
|
|
UI_Scroll(0, &Settings->GlobalScroll, UI_BoxFlag_Clip)
|
|
{
|
|
workspace_settings_category Category = Settings->Category;
|
|
if(!Category || (Category == W_Settings_General))
|
|
{
|
|
UI_Font(Font_Bold) UI_FontSize(36) UI_LabelF("General");
|
|
|
|
char *Alternatives[] = {"15 Hz", "30 Hz", "60 Hz", "120 Hz", "144 Hz", "Uncapped", "V-Sync"};
|
|
s64 AlternativeMapping[] = {15, 30, 60, 120, 144, -1, 0};
|
|
|
|
s32 DropdownSelected;
|
|
FindIndexOfElement(DropdownSelected, AlternativeMapping, 0, Workspace->Input->RefreshRate);
|
|
|
|
UI_Row()
|
|
{
|
|
UI_LabelF("Refresh Rate:");
|
|
UI_Spacer(UI_Pixels(10, 1));
|
|
|
|
UI_SetNextWidth(UI_Pixels(200, 1));
|
|
UI_SetNextCornerRadius(4);
|
|
if(UI_DropdownSelection(Alternatives, ArrayCount(Alternatives),
|
|
&Settings->GeneralDropdownOpen, &DropdownSelected))
|
|
{
|
|
Workspace->Input->RefreshRate = AlternativeMapping[DropdownSelected];
|
|
Settings->GeneralDropdownOpen = false;
|
|
}
|
|
}
|
|
|
|
UI_Spacer(UI_Pixels(50, 1));
|
|
}
|
|
|
|
if(!Category || (Category == W_Settings_Developer))
|
|
{
|
|
UI_Font(Font_Bold) UI_FontSize(36) UI_LabelF("Developer");
|
|
UI_Checkbox(&DEBUG_DebugSettings->RenderUIDebugRects, StrLit("Render UI Debug Rects"));
|
|
UI_Spacer(UI_Pixels(5, 1));
|
|
UI_Checkbox(&DEBUG_DebugSettings->RenderFPSCounter, StrLit("Render FPS Counter"));
|
|
UI_Spacer(UI_Pixels(5, 1));
|
|
UI_Checkbox(&DEBUG_DebugSettings->ListHotAndActive, StrLit("List Hot & Active"));
|
|
|
|
UI_Spacer(UI_Pixels(50, 1));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
UI_Spacer(UI_Pixels(50, 1));
|
|
}
|
|
|
|
static void W_BuildView(workspace_view *View)
|
|
{
|
|
r32 ViewHighlightTransition =
|
|
AC_AnimateValueF(W_ViewIsCurrent(View), 0, 0.25, "Workspace View Highlight %p", View);
|
|
UI_SetNextBorderColor(LinearBlend(Theme_BorderColor, Theme_HighlightBorderColor, ViewHighlightTransition));
|
|
UI_PushBackgroundColor(Theme_BackgroundColor);
|
|
UI_SetNextCornerRadius(3);
|
|
|
|
ui_box *ViewBox = UI_MakeBoxF(UI_BoxFlag_Clickable |
|
|
UI_BoxFlag_DrawBackground |
|
|
UI_BoxFlag_DrawBorder |
|
|
UI_BoxFlag_Clip,
|
|
"Workspace View %p", View);
|
|
|
|
UI_Parent(ViewBox)
|
|
UI_Size(UI_Percent(1, 0), UI_Percent(1, 0))
|
|
{
|
|
switch(View->Kind)
|
|
{
|
|
case W_ViewKind_None: {} break;
|
|
|
|
case W_ViewKind_Startup:
|
|
{
|
|
UI_Row() UI_Padding(UI_Pixels(50, 0))
|
|
UI_Width(UI_ChildrenSum(1, 1)) UI_Column() UI_Padding(UI_Pixels(50, 0))
|
|
{
|
|
UI_Size(UI_TextContent(0, 1), UI_TextContent(10, 1))
|
|
{
|
|
UI_Font(Font_Bold) UI_FontSize(36)
|
|
UI_LabelF("Welcome to VN");
|
|
UI_TextColor(Theme_BorderColor) UI_LabelF("An impractical way to make a game");
|
|
|
|
UI_Spacer(UI_Percent(1, 0));
|
|
|
|
UI_Checkbox(&DEBUG_DebugSettings->ShowWelcomeMessage, StrLit("Show this message on startup"));
|
|
}
|
|
}
|
|
} break;
|
|
|
|
case W_ViewKind_Settings:
|
|
{
|
|
W_BuildSettings(View);
|
|
} break;
|
|
|
|
case W_ViewKind_ImageViewer:
|
|
{
|
|
W_BuildImageViewer(View);
|
|
} break;
|
|
|
|
case W_ViewKind_TextEditor:
|
|
{
|
|
W_BuildTextEditor(View);
|
|
} break;
|
|
|
|
case W_ViewKind_SceneView:
|
|
{
|
|
W_BuildSceneView(View);
|
|
} break;
|
|
case W_ViewKind_NavEditor:
|
|
{
|
|
W_BuildNavEditor(View);
|
|
} break;
|
|
case W_ViewKind_FileLister:
|
|
{
|
|
workspace_file_lister_action Action = W_BuildFileLister(View);
|
|
if(Action.HasRequestedFile)
|
|
{
|
|
workspace_file_lister_action *ActionOnDataArena = W_FileListerActionCopy(W_GetState()->CommandDataArena, &Action);
|
|
W_IssueCommand(W_Command_OpenFile, PointerToU64(ActionOnDataArena));
|
|
|
|
W_IssueCommand(W_Command_CloseView, PointerToU64(View));
|
|
}
|
|
} break;
|
|
|
|
case W_ViewKind_Error:
|
|
{
|
|
workspace_view_error *Error = (workspace_view_error *)View->Data;
|
|
UI_WidthFill UI_HeightFill
|
|
UI_Column()
|
|
{
|
|
UI_Label(Error->Message);
|
|
|
|
UI_Height(UI_Em(2, 1)) UI_Row()
|
|
{
|
|
UI_Spacer(UI_Percent(1, 0));
|
|
|
|
UI_Width(UI_TextContent(15, 1)) UI_CornerRadius(4.0f)
|
|
if(UI_Button(StrLit("Close View")).Clicked)
|
|
{
|
|
W_IssueCommand(W_Command_CloseView, PointerToU64(View));
|
|
}
|
|
UI_Spacer(UI_Em(1, 1));
|
|
}
|
|
UI_Spacer(UI_Em(1, 1));
|
|
}
|
|
} break;
|
|
}
|
|
}
|
|
|
|
UI_PopBackgroundColor();
|
|
|
|
UI_SignalFromBox(ViewBox);
|
|
}
|