thread_local scene_view *ThreadLocal_SceneView = 0; static void SV_SetState(scene_view *View) { ThreadLocal_SceneView = View; } static scene_view *SV_GetState() { return(ThreadLocal_SceneView); } static void SV_NewFrame(scene_view *View, platform_event_list *EventList, r32 dtForFrame) { SV_SetState(View); View->EventList = EventList; View->dtForFrame = dtForFrame; } static void SV_SetCurrentSource(compiled_scene *Compiled) { scene_view *SceneView = SV_GetState(); // sixten(TODO): extract runtime information required to seamlessly transition between compilations ArenaClear(SceneView->SceneArena); SceneView->Runtime.Compiled = S_CopyCompiledScene(SceneView->SceneArena, Compiled); S_ResetRuntime(&SceneView->Runtime); } static void SV_Init(scene_view *SceneView, memory_arena *TextboxArena) { SV_SetState(SceneView); SceneView->SceneArena = ArenaAllocate(Gigabytes(1)); SceneView->Runtime.ErrorArena = ArenaAllocate(Megabytes(1)); S_ResetRuntime(&SceneView->Runtime); SceneView->Textbox.Capacity = 4096; SceneView->Textbox.String.Data = PushArray(TextboxArena, u8, SceneView->Textbox.Capacity); SceneView->Textbox.String.Count = 0; } struct text_properties { font_id Font; r32 FontSize; r32 LineHeight; }; static void RenderAnimatedText(render_group *Group, glyph_atlas *Atlas, text_properties Properties, string Text, r32 CharsToRender, v2_r32 P, r32 WrapWidth) { v2_r32 Offset = V2R32(0, 0); u8 *TextBegin = Text.Data; u8 *TextEnd = TextBegin+Text.Count; u8 *Byte = TextBegin; u8 *WordBegin = TextBegin; u8 *WordEnd = WordBegin; u8 *TrueWordEnd = WordBegin; string_decode LastVisibleDecode = {}; Byte = TextBegin; for(;Byte<=TextEnd;) { string_decode Decode = DecodeUTF8Codepoint(Byte, TextEnd-Byte); if(CharsToRender >= 1) { LastVisibleDecode = Decode; } if(Decode.Codepoint == ' ' || Byte==TextEnd) { string TrueWord = MakeString(WordBegin, TrueWordEnd); string Word = MakeString(WordBegin, WordEnd); r32 WordWidth = CalculateRasterizedTextWidth(Atlas, Properties.Font, Properties.FontSize, TrueWord); if(Offset.x+WordWidth > WrapWidth) { Offset.x = 0; Offset.y += Properties.LineHeight; } Offset.x += PushText(Group, Atlas, Properties.Font, P+Offset, Properties.FontSize, Color_White, Word); if(WordEnd != TrueWordEnd) { string LastChar = MakeString(WordEnd, LastVisibleDecode.Size); r32 TransitionAmount = Mod(CharsToRender, 1); Offset.y += (1-TransitionAmount)*5; PushText(Group, Atlas, Properties.Font, P+Offset, Properties.FontSize, SetAlpha(Color_White, TransitionAmount), LastChar); } WordBegin = TrueWordEnd; if(Byte == TextEnd || CharsToRender < 1) { break; } } Byte += Decode.Size; TrueWordEnd += Decode.Size; if(CharsToRender >= 1) { WordEnd += Decode.Size; CharsToRender -= 1; } } } static r32 CalculateGlobalScaleFromDim(v2_r32 Dim) { r32 GlobalScale = SquareRoot(Dim.x*Dim.y)/45; return(GlobalScale); } static r32 CalculateGlobalScaleFromRootBox(ui_box *Box) { v2 RenderDim = DimOfRange(Box->Rect); r32 GlobalScale = SquareRoot(RenderDim.x*RenderDim.y)/45; return(GlobalScale); } struct scene_textbox_data { textbox *Textbox; ui_box *SceneViewBox; }; UI_CUSTOM_DRAW_CALLBACK(BuildSceneTextboxDrawCallback) { scene_textbox_data *TextboxData = (scene_textbox_data *)Data; textbox *Textbox = TextboxData->Textbox; //- sixten: render textbox v4 TopColor = V4(0, 0, 0, 0.8f); v4 BottomColor = V4(0, 0, 0, 0.5f); range2_r32 Dest = Range2R32(Box->Rect.Min, Box->Rect.Max); PushQuad(Group, Dest, TopColor, TopColor, BottomColor, BottomColor, 0, 0, 0); //- sixten: render text string Text = Textbox->String; r32 CharsRevealed = Textbox->CharsRevealed; r32 GlobalScale = CalculateGlobalScaleFromRootBox(TextboxData->SceneViewBox); text_properties Properties = {}; Properties.Font = Font_Fancy; Properties.FontSize = GlobalScale; Properties.LineHeight = GlobalScale*1.5f; r32 Padding = 1.5f*GlobalScale; v2 Offset = V2R32(Padding, Padding); RenderAnimatedText(Group, Atlas, Properties, Text, CharsRevealed, Box->Rect.Min+Offset, DimOfRange(Box->Rect).x-2*Padding); } UI_CUSTOM_DRAW_CALLBACK(BuildSceneDrawCallback) { scene_view *SceneView = (scene_view *)Data; v2 RenderDim = DimOfRange(Box->Rect); r32 GlobalScale = CalculateGlobalScaleFromDim(RenderDim); //- sixten: render background // sixten(TODO, but soon): Currently we add Box->Rect.Min to everything, but that should really be a transform // on the render group. range2_r32 BackgroundDest = Range2R32(Box->Rect.Min, RenderDim+Box->Rect.Min); range2_r32 BackgroundSource = Range2R32(V2R32(0, 0), ConvertV2ToR32(DimFromTexture(SceneView->BackgroundTexture))); PushTexturedQuad(Group, BackgroundDest, BackgroundSource, Color_White, Color_White, Color_White, Color_White, 0, 0, 0, SceneView->BackgroundTexture); //- sixten: render characters for(s32 CharacterIndex = 0; CharacterIndex < SceneView->CharacterCount; CharacterIndex += 1) { scene_view_character_data *Character = SceneView->OnscreenCharacters + CharacterIndex; v4_r32 BlendColor = LinearBlend(Color_White, Color_Black, 0.5-Character->TalkingT*0.5); BlendColor.a = Character->ActiveT; r32 Scale = (Character->TextureScale + Character->TalkingT*0.001)*(0.95+Character->ActiveT*0.05)*GlobalScale; render_handle CharacterHandle = Character->Texture; v2_r32 CharacterDim = ConvertV2ToR32(DimFromTexture(CharacterHandle)); v2_r32 CharacterOriginP = V2R32(RenderDim.x*Character->PctP, RenderDim.y); v2_r32 CharacterMidP = V2R32(CharacterOriginP.x, CharacterOriginP.y - CharacterDim.y*Scale/2); range2_r32 CharacterDest = Range2R32(CharacterMidP-CharacterDim*0.5f*Scale, CharacterMidP+CharacterDim*0.5f*Scale); range2_r32 CharacterSource = Range2R32(V2R32(0, 0), CharacterDim); PushTexturedQuad(Group, CharacterDest, CharacterSource, BlendColor, BlendColor, BlendColor, BlendColor, 0, 0, 0, CharacterHandle); } } static void BuildScene(scene_view *View) { scene_runtime *Runtime = &View->Runtime; textbox *Textbox = &View->Textbox; UI_SetNextWidth(UI_Percent(1, 0)); UI_SetNextHeight(UI_Percent(1, 0)); UI_SetNextLayoutAxis(Axis2_Y); ui_box *Box = UI_MakeBox(0, StrLit("Scene View")); UI_EquipBoxCustomDrawCallback(Box, BuildSceneDrawCallback, View); UI_Parent(Box) { UI_WidthFill UI_Height(UI_Percent(1, 0)) UI_Row() UI_FillPadding UI_Column() UI_FillPadding { b32 FoundOffset = false; s64 Offset = 0; for(s32 BranchIndex = 0; BranchIndex < Runtime->BranchCount; BranchIndex += 1) { branch_case *Branch = &Runtime->Branches[BranchIndex]; if(UI_ButtonF("%S#%i", Branch->Name, BranchIndex).Clicked) { Offset = Branch->Offset; FoundOffset = true; } } if(FoundOffset) { Runtime->IP += 1+Offset; Runtime->BranchCount = 0; } } UI_SetNextWidth(UI_Percent(1, 1)); UI_SetNextHeight(UI_Percent(0.3, 1)); ui_box *TextBox = UI_MakeBox(0, StrLit("Scene Textbox")); scene_textbox_data *TextboxData = PushStruct(UI_FrameArena(), scene_textbox_data); TextboxData->Textbox = Textbox; TextboxData->SceneViewBox = Box; UI_EquipBoxCustomDrawCallback(TextBox, BuildSceneTextboxDrawCallback, TextboxData); } } static void BuildErrorScreen(scene_runtime *Runtime, vn_input *Input) { UI_SetNextLayoutAxis(Axis2_X); UI_Parent(UI_MakeBox(UI_BoxFlag_DrawBackground, StrLit("Container"))) { UI_Padding(UI_Em(3, 1)) UI_Width(UI_Percent(1, 0)) UI_Column() UI_Padding(UI_Em(3, 1)) { UI_Font(Font_Bold) UI_Size(UI_TextContent(0, 1), UI_TextContent(0, 1)) UI_FontSize(32) UI_LabelF("A runtime error has occurred"); s64 ErrorIndex = 0; for(scene_runtime_error *Error = Runtime->FirstError; Error != 0; Error = Error->Next, ErrorIndex += 1) { UI_Spacer(UI_Em(3, 1)); UI_SetNextCornerRadius(3); UI_Size(UI_Percent(1, 1), UI_Percent(1, 0)) UI_Parent(UI_MakeBoxF(UI_BoxFlag_DrawDropShadow|UI_BoxFlag_DrawBorder, "%i", ErrorIndex)) UI_Size(UI_TextContent(30, 1), UI_TextContent(30, 1)) { UI_LabelF("Message: %S", Error->Message); } } UI_Spacer(UI_Em(3, 1)); UI_Size(UI_Percent(1, 1), UI_Em(2, 1)) UI_Row() UI_Width(UI_TextContent(30, 1)) UI_CornerRadius(4) { ui_signal IgnoreSignal = UI_ButtonF("Ignore"); if(IgnoreSignal.Hovering) { UI_TooltipLabel(StrLit("Continue running the script, may lead to more errors."), UI_MouseP()); } if(IgnoreSignal.Clicked) { Runtime->FirstError = Runtime->LastError = 0; ArenaClear(Runtime->ErrorArena); } UI_Spacer(UI_Em(1, 1)); ui_signal RestartSignal = UI_ButtonF("Restart"); if(RestartSignal.Hovering) { UI_TooltipLabel(StrLit("Restarts the script, may lose progress."), UI_MouseP()); } if(RestartSignal.Clicked) { Runtime->FirstError = Runtime->LastError = 0; Runtime->IP = 0; Runtime->CurrentProc = 0; ArenaClear(Runtime->ErrorArena); } UI_Spacer(UI_Em(1, 1)); if(UI_ButtonF("Exit Program").Clicked) { Input->ExitRequested = true; } } } } } static scene_view_character_data *SV_CharacterDataFromName(string Name) { scene_view_character_data *Result = 0; scene_view *View = SV_GetState(); for(s32 CharacterIndex = 0; CharacterIndex < View->CharacterCount; CharacterIndex += 1) { scene_view_character_data *Character = View->OnscreenCharacters + CharacterIndex; if(AreEqual(Character->Name, Name)) { Result = Character; break; } } //- sixten: create character if not initialized if(!Result && View->CharacterCount < ArrayCount(View->OnscreenCharacters)) { s32 CharacterIndex = View->CharacterCount; View->CharacterCount += 1; Result = View->OnscreenCharacters + CharacterIndex; *Result = {}; Result->Name = Name; Result->Active = true; Result->PctP = (r32)(CharacterIndex + 1) / (View->CharacterCount + 1); } return(Result); } static render_handle SV_CharacterTextureFromAction(scene_character_action *Action) { render_handle Result = EmptyRenderHandle(); scene_view *View = SV_GetState(); if(AreEqual(StrLit("arthur"), Action->Target)) { switch(Action->State) { case CR_State_Normal: { Result = View->TestNormal; } break; case CR_State_Happy: { Result = View->TestHappy; } break; default: break; } } return(Result); } static void SV_Update(memory_arena *FrameArena) { scene_view *SceneView = SV_GetState(); textbox *Textbox = &SceneView->Textbox; scene_runtime *Runtime = &SceneView->Runtime; platform_event_list *EventList = SceneView->EventList; r32 dtForFrame = SceneView->dtForFrame; compiled_scene *Compiled = &Runtime->Compiled; //- sixten: update the characters for(s32 CharacterIndex = 0; CharacterIndex < SceneView->CharacterCount; CharacterIndex += 1) { scene_view_character_data *Data = SceneView->OnscreenCharacters + CharacterIndex; AC_AnimateValueDirect(Data->Active, 0.3f, &Data->ActiveT); AC_AnimateValueDirect(Data->Talking, 0.3f, &Data->TalkingT); r32 TargetPctP = (r32)(CharacterIndex+1)/(SceneView->CharacterCount+1); AC_AnimateValueDirect(TargetPctP, 0.3f, &Data->PctP); } //- sixten: prune any unactive characters for(s32 CharacterIndex = 0; CharacterIndex < SceneView->CharacterCount; CharacterIndex += 1) { scene_view_character_data *Data = SceneView->OnscreenCharacters + CharacterIndex; if(!Data->Active && Data->ActiveT < 0.01) { Move(Data, Data+1, SceneView->CharacterCount-CharacterIndex-1); SceneView->CharacterCount -= 1; CharacterIndex -= 1; } } if(Compiled && Compiled->IsValid) { b32 PlayerAction = (Platform_KeyPress(EventList, Key_Space)||Platform_KeyPress(EventList, Key_MouseLeft)); if(DLLIsEmpty(Runtime->FirstError)) { b32 AdvanceOnAwait = (Textbox->CharsRevealed >= Textbox->String.Count) && PlayerAction; for(;;) { scene_runtime_result RunResult = S_Run(Runtime, FrameArena, AdvanceOnAwait); if(RunResult.ReachedAwait || RunResult.HadError) { break; } } } r32 CharsPerSecond = 35.0f;//10.0f; Textbox->CharsRevealed += dtForFrame*CharsPerSecond; Textbox->CharsRevealed = Min(Textbox->CharsRevealed, (r32)Textbox->String.Count); if(Textbox->CharsRevealed < Textbox->String.Count && PlayerAction) { Textbox->CharsRevealed = Textbox->String.Count; } //- sixten: apply the textbox actions for(textbox_action *Action = Runtime->FirstTextboxAction; Action != 0; Action = Action->Next) { if(Action->Kind == TextboxActionKind_Set) { string ReplaceString = Action->String; Textbox->String.Count = Min(ReplaceString.Count, Textbox->Capacity); Copy(Textbox->String.Data, ReplaceString.Data, Textbox->String.Count); Textbox->CharsRevealed = 0; } else if(Action->Kind == TextboxActionKind_Append) { string Addend = Action->String; Textbox->CharsRevealed = Textbox->String.Count; s64 NewCount = Min(Textbox->String.Count+Addend.Count, Textbox->Capacity-1); Copy(Textbox->String.Data+Textbox->String.Count, Action->String.Data, NewCount-Textbox->String.Count); Textbox->String.Count = NewCount; } else { InvalidCodepath; } } Runtime->FirstTextboxAction = Runtime->LastTextboxAction = 0; //- sixten: apply character actions for(scene_character_action *Action = Runtime->FirstCharacterAction; Action != 0; Action = Action->Next) { // sixten: find character scene_view_character_data *Data = SV_CharacterDataFromName(Action->Target); if(Action->State == CR_State_None) { Data->Active = false; } else { Data->Texture = SV_CharacterTextureFromAction(Action); Data->TextureScale = 0.017f; Data->Talking = true; } } Runtime->FirstCharacterAction = Runtime->LastCharacterAction = 0; } } static void SV_BuildSceneView(vn_input *Input) { scene_view *SceneView = SV_GetState(); scene_runtime_result LastRun = SceneView->Runtime.LastResult; if(LastRun.HadError) { BuildErrorScreen(&SceneView->Runtime, Input); } else if(SceneView->Runtime.Compiled.IsValid) { BuildScene(SceneView); #if 0 UI_Tooltip { UI_SetNextFixedP(V2R32(0, 0)); UI_SetNextSize(UI_ChildrenSum(1, 1), UI_ChildrenSum(1, 1)); UI_Column() UI_Size(UI_TextContent(15, 1), UI_TextContent(15, 1)) { UI_Row() { UI_LabelF("Character Count: %i", SceneView->CharacterCount); if(UI_ButtonF("+").Clicked) ++SceneView->CharacterCount; if(UI_ButtonF("-").Clicked) --SceneView->CharacterCount; } } } #endif } else { UI_LabelF("Invalid source"); } }