per_thread 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_Reset(void)
{
	scene_view *SceneView = SV_GetState();
	
	ArenaClear(SceneView->Runtime.ErrorArena);
	S_ResetRuntime(&SceneView->Runtime);
	
	// sixten: reset textbox
	SceneView->Textbox.String.Count = 0;
	
	// sixten: reset onscreen characters
	SceneView->CharacterCount = 0;
}

static void SV_LoadNavItems(void)
{
	scene_view *SceneView = SV_GetState();
	temp Scratch = GetScratch();
	string NavData = Platform_ReadEntireFile(Scratch.Arena, SceneView->Runtime.Compiled.NavFileName);
	
	if(NavData.Count != 0)
	{
		u8 *Byte = NavData.Data;
		SceneView->NavItemCount = *(u16 *)Byte;
		Byte += 2;
		
		SceneView->NavItems = PushArrayNoClear(SceneView->SceneArena, scene_nav_item, SceneView->NavItemCount);
		
		//- sixten: parse items
		for(u64 ItemIndex = 0; ItemIndex < SceneView->NavItemCount; ItemIndex += 1)
		{
			scene_nav_item *Item = &SceneView->NavItems[ItemIndex];
			for(;;)
			{
				switch(*Byte++)
				{
					case S_NavItemOp_TextureID:
					{
						string AssetName;
						AssetName.Count = *(u16 *)Byte;
						Byte += sizeof(u16);
						AssetName.Data = Byte;
						Byte += AssetName.Count;
						
						Item->TextureID = AssetID_Error;
						for(u64 AssetIndex = 0; AssetIndex < AssetID_COUNT; AssetIndex += 1)
						{
							if(AreEqual(MakeString(AssetNameLUT[AssetIndex]), AssetName))
							{
								Item->TextureID = AssetIndex;
								break;
							}
						}
					} goto Next;
					case S_NavItemOp_Scale:
					{
						Item->Scale = *(r32 *)Byte;
						Byte += sizeof(r32);
					} goto Next;
					case S_NavItemOp_Origin:
					{
						Item->Origin = *(v2_r32 *)Byte;
						Byte += sizeof(v2_r32);
					} goto Next;
					case S_NavItemOp_P:
					{
						Item->P = *(v2_r32 *)Byte;
						Byte += sizeof(v2_r32);
					} goto Next;
					case S_NavItemOp_HoverText:
					{
						Item->HoverText.Count = *(u16 *)Byte;
						Byte += 2;
						
						Item->HoverText.Data = PushArrayNoClear(SceneView->SceneArena, u8, Item->HoverText.Count);
						Copy(Item->HoverText.Data, Byte, Item->HoverText.Count);
						Byte += Item->HoverText.Count;
					} goto Next;
					case S_NavItemOp_Action:
					{
						Item->Action.Kind = (scene_nav_action_kind)*Byte;
						Byte += 1;
						
						Item->Action.Content.Count = *(u16 *)Byte;
						Byte += 2;
						
						Item->Action.Content.Data = PushArrayNoClear(SceneView->SceneArena, u8, Item->Action.Content.Count);
						Copy(Item->Action.Content.Data, Byte, Item->Action.Content.Count);
						Byte += Item->Action.Content.Count;
					} goto Next;
				}
				
				//- sixten: no op found, assume item done
				break;
				
				Next:;
			}
		}
	}
	ReleaseScratch(Scratch);
}

static void SV_SetCurrentSource(compiled_scene *Compiled)
{
	scene_view *SceneView = SV_GetState();
	scene_runtime *Runtime = &SceneView->Runtime;
	
	// sixten(TODO): extract runtime information required to seamlessly transition between compilations
	SV_Reset();
	
	ArenaClear(SceneView->SceneArena);
	Runtime->Compiled = S_CopyCompiledScene(SceneView->SceneArena, Compiled);
	
	SV_LoadNavItems();
	
	
	//- sixten: run top level
	Runtime->CurrentProc = Runtime->Compiled.GlobalScope;
	if(Runtime->CurrentProc)
	{
		temp Scratch = GetScratch(0, 0);
		for(;;)
		{
			scene_runtime_result RunResult = S_Run(Runtime, Scratch.Arena, false);
			if(RunResult.ReachedAwait || RunResult.HadError)
			{
				break;
			}
		}
		ReleaseScratch(Scratch);
	}
	
	//- sixten: find main proc
	Runtime->CurrentProc = S_FindProcByName(&Runtime->Compiled, StrLit("main"));
	Runtime->IP = 0;
}

static void SV_Init(scene_view *SceneView, arena *TextboxArena)
{
	SV_SetState(SceneView);
	
	SceneView->SceneArena = ArenaAlloc(Kilobytes(16), true);
	SceneView->Runtime.ErrorArena = ArenaAlloc(Kilobytes(4), true);
	
	SceneView->Textbox.Capacity = 4096;
	SceneView->Textbox.String.Data = PushArray(TextboxArena, u8, SceneView->Textbox.Capacity);
	
	SV_Reset();
}

static b32 SV_CurrentlyInProc(void)
{
	scene_view *SceneView = SV_GetState();
	b32 Result = (SceneView->Runtime.CurrentProc != 0);
	return(Result);
}

static scene_view_character_data *SV_FindTalkingCharacter(void)
{
	scene_view *SceneView = SV_GetState();
	scene_view_character_data *Result = 0;
	for(s32 CharacterIndex = 0; CharacterIndex < SceneView->CharacterCount; CharacterIndex += 1)
	{
		scene_view_character_data *Character = SceneView->Characters+CharacterIndex;
		if(Character->Talking)
		{
			Result = Character;
			break;
		}
	}
	return(Result);
}

static string SV_DisplayNameFromCharacter(scene_view_character_data *Character)
{
	scene_view *SceneView = SV_GetState();
	string Result = StrLit("invalid character");
	if(Character)
	{
		Result = StrLit("undefined name");
		
		scene_named_value *CharacterNameValue = S_FindGlobalVariableByName(&SceneView->Runtime, Character->Name, false);
		if(CharacterNameValue && CharacterNameValue->Value.Kind == S_ValueKind_String)
		{
			Result = CharacterNameValue->Value.String;
		}
	}
	return(Result);
}

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);
	v2_r32 ShadowOffset = V2R32(Properties.FontSize*0.1, Properties.FontSize*0.1);
	
	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;
			}
			
			PushText(Group, Atlas, Properties.Font, P+Offset+ShadowOffset, Properties.FontSize, Color_Black, Word);
			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 = UI_CalculateBoxDim(Box);
	r32 GlobalScale = SquareRoot(RenderDim.x*RenderDim.y)/45;
	return(GlobalScale);
}

struct scene_textbox_data
{
	textbox *Textbox;
	ui_box *SceneViewBox;
	string Name;
	r32 NameT;
};

UI_CUSTOM_DRAW_CALLBACK(BuildSceneTextboxDrawCallback)
{
	scene_textbox_data *TextboxData = (scene_textbox_data *)Data;
	textbox *Textbox = TextboxData->Textbox;
	
	r32 GlobalScale = CalculateGlobalScaleFromRootBox(TextboxData->SceneViewBox);
	
	//- sixten: render textbox
	{
		v4 TopColor = V4(0, 0, 0, 0.8f);
		v4 BottomColor = V4(0, 0, 0, 0.5f);
		range2_r32 Dest = Pad(Range2R32(Box->Rect.Min, Box->Rect.Max), V2(GlobalScale, GlobalScale)*2.0f);
		PushQuad(Group, Dest, TopColor, TopColor, BottomColor, BottomColor, 0, GlobalScale, 0);
	}
	
	//- sixten: render text
	{
		string Text = Textbox->String;
		r32 CharsRevealedT = Textbox->CharsRevealedT;
		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, CharsRevealedT, Box->Rect.Min+Offset, DimOfRange(Box->Rect).x-2*Padding);
	}
	
	//- sixten: render character name
	{
		string Name = TextboxData->Name;
		r32 T = TextboxData->NameT;
		v2 TextP = Box->Rect.Min + V2(1.5f*GlobalScale, -GlobalScale*T);
		PushText(Group, Atlas, Font_Fancy, TextP+V2(GlobalScale*0.1, GlobalScale*0.1), GlobalScale, SetAlpha(Color_Black, T), Name);
		PushText(Group, Atlas, Font_Fancy, TextP, GlobalScale, SetAlpha(Color_White, T), Name);
	}
	
}

static void SV_DrawBackground(scene_view *SceneView, ui_box *Box, render_group *Group)
{
	v2 RenderDim = DimOfRange(Box->Rect);
	
	render_handle Texture = TextureFromAssetID(SceneView->Runtime.Compiled.BackgroundTexture);
	
	//- sixten: render background
#if 0
	persist r32 Time = 0;
	Time += 1 / 1200.0f;
	r32 r = 30;
	v2_r32 Offset = V2(Sin(Time)+0.5*Sin(43+2.43*Time)+Sin(424+Time*16)*0.1, Sin(8+Time)+0.5*Sin(43+2.43*Time)+Sin(4242+Time*16)*0.1)*(1.0f/1.6f)*r;
	range2_r32 BackgroundDest = Range2R32(Box->Rect.Min-V2(r, r)+Offset, RenderDim+Box->Rect.Min+V2(r, r)+Offset);
	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, Texture);
#else
	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, Texture);
#endif
	
}

UI_CUSTOM_DRAW_CALLBACK(BuildSceneDrawCallback)
{
	scene_view *SceneView = (scene_view *)Data;
	
	SV_DrawBackground(SceneView, Box, Group);
	v2 RenderDim = DimOfRange(Box->Rect);
	r32 GlobalScale = CalculateGlobalScaleFromDim(RenderDim);
	
	//- sixten: render characters
	for(s32 CharacterIndex = 0; CharacterIndex < SceneView->CharacterCount; CharacterIndex += 1)
	{
		scene_view_character_data *Character = SceneView->Characters + CharacterIndex;
		
		v4_r32 BlendColor = LinearBlend(Color_White, Color_Black, 0.5-Character->TalkingT*0.5);
		BlendColor.a = Character->ActiveT;
		r32 Scale = (Character->Info.Scale + Character->TalkingT*0.001)*(0.95+Character->ActiveT*0.05)*GlobalScale;
		render_handle CharacterHandle = Character->Info.Texture;
		v2_r32 CharacterDim = ConvertV2ToR32(DimFromTexture(CharacterHandle));
		v2_r32 CharacterOriginP = V2R32(RenderDim.x*Character->PctP, RenderDim.y);
		v2_r32 CharacterMidP = Box->Rect.Min+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 b32 BuildSceneBranchButton(string Text, r32 GlobalScale)
{
	UI_SetNextFontSize(GlobalScale);
	UI_SetNextFont(Font_Fancy);
	UI_SetNextCornerRadius(5.0f);
	
	b32 Result = UI_Button(Text).Clicked;
	return(Result);
}

static void BuildProcView(scene_view *View, ui_box *Box, v2_r32 BoxDim)
{
	temp Scratch = GetScratch();
	
	r32 GlobalScale = CalculateGlobalScaleFromDim(BoxDim);
	r32 ActiveScale = GlobalScale * 0.75f;
	
	scene_runtime *Runtime = &View->Runtime;
	textbox *Textbox = &View->Textbox;
	
	//- sixten: build branches
	UI_FillPadding UI_WidthFill UI_Height(UI_Pixels(2*ActiveScale, 1)) 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(BuildSceneBranchButton(PushFormat(Scratch.Arena, "%S#%i", Branch->Name, BranchIndex), ActiveScale))
			{
				Offset = Branch->Offset;
				FoundOffset = true;
			}
			
			if(BranchIndex != Runtime->BranchCount - 1)
			{
				UI_Spacer(UI_Em(1, 1));
			}
		}
		if(FoundOffset)
		{
			Runtime->IP += 1+Offset;
			Runtime->BranchCount = 0;
		}
	}
	
	//- sixten: build textbox
	UI_Size(UI_Percent(1, 1), 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;
		
		scene_view_character_data *TalkingCharacter = SV_FindTalkingCharacter();
		TextboxData->Name = SV_DisplayNameFromCharacter(TalkingCharacter);
		TextboxData->NameT = AC_AnimateValueF(TalkingCharacter != 0, 0, 0.3f, "Scene View Talking Character %p", View);
		
		UI_EquipBoxCustomDrawCallback(TextBox, BuildSceneTextboxDrawCallback, TextboxData);
	}
	
	ReleaseScratch(Scratch);
}

struct scene_nav_item_info
{
	scene_nav_item *Item;
	ui_signal Signal;
};

UI_CUSTOM_DRAW_CALLBACK(BuildNavItemDrawCallback)
{
	scene_nav_item_info *Info = (scene_nav_item_info *)Data;
	render_handle Texture = TextureFromAssetID(Info->Item->TextureID);
	v2_r32 TextureDim = ConvertV2ToR32(DimFromTexture(Texture));
	
	range2_r32 DestRect = Range2R32(Box->Rect.Min, Box->Rect.Max);
	range2_r32 SourceRect = Range2R32(V2R32(0, 0), TextureDim);
	
	v4_r32 Color = LinearBlend(Color_Grey, Color_White, Info->Signal.Hovering);
	
	PushTexturedQuad(Group, DestRect, SourceRect, Color, Color, Color, Color, 0, 0, 0, Texture);
}

inline u32 U32FromRawR32(r32 Value)
{
	u32 Result = *(u32 *)&Value;
	return(Result);
}

static ui_signal SV_BuildNavItem(scene_nav_item *Item, r32 GlobalScale, v2_r32 GlobalDim)
{
	r32 AppliedScale = GlobalScale*Item->Scale;
	
	scene_nav_item_info *Data = PushStruct(UI_FrameArena(), scene_nav_item_info);
	Data->Item = Item;
	
	render_handle Texture = TextureFromAssetID(Item->TextureID);
	
	v2_r32 TextureDim = ConvertV2ToR32(DimFromTexture(Texture));
	v2_r32 TextureOrigin = Hadamard(TextureDim, Item->Origin);
	
	v2_r32 OffsetP = GlobalDim*(V2R32(1, 1) + Item->P)*0.5f;
	v2_r32 OriginP = TextureOrigin*AppliedScale;
	v2_r32 Dim = TextureDim*AppliedScale;
	range2_r32 Dest = Range2R32(OffsetP-OriginP, OffsetP-OriginP+Dim);
	
	UI_SetNextFixedP(Dest.Min);
	UI_SetNextSize(UI_Pixels(Dim.x, 1), UI_Pixels(Dim.y, 1));
	UI_SetNextHoverCursor(PlatformCursor_Hand);
	
	ui_box *ItemBox = UI_MakeBoxF(UI_BoxFlag_Clickable|UI_BoxFlag_FloatingX|UI_BoxFlag_FloatingY,
                                  "View Item Box %i %S %p",
                                  (U32FromRawR32(Item->Scale)<<4)+
                                  (U32FromRawR32(Item->Origin.x)<<17)+
                                  (U32FromRawR32(Item->Origin.y)<<11),
                                  Item->HoverText, Item);
	UI_EquipBoxCustomDrawCallback(ItemBox, BuildNavItemDrawCallback, Data);
	
	ui_signal Signal = UI_SignalFromBox(ItemBox);
	Data->Signal = Signal;
	
	return(Signal);
}

static ui_signal BuildNavItemAndLabel(scene_nav_item *Item, r32 GlobalScale, v2_r32 GlobalDim)
{
	ui_signal Signal = SV_BuildNavItem(Item, GlobalScale, GlobalDim);
	if(Item->HoverText.Count && Signal.Hovering)
	{
		UI_FontSize(GlobalScale*0.5f) UI_Font(Font_Fancy) UI_CornerRadius(4.0f)
			UI_TooltipLabel(Item->HoverText, UI_MouseP());
	}
	return(Signal);
}

static void BuildNavView(scene_view *View, ui_box *Box, v2_r32 BoxDim)
{
	r32 GlobalScale = CalculateGlobalScaleFromDim(BoxDim);
	
	for(int ItemIndex = 0; ItemIndex < View->NavItemCount; ++ItemIndex)
	{
		scene_nav_item *Item = View->NavItems+ItemIndex;
		if(BuildNavItemAndLabel(Item, GlobalScale, BoxDim).Clicked)
		{
			// sixten: apply the action
			if(Item->Action.Kind == S_NavAction_Proc)
			{
				S_SetCurrentProc(&View->Runtime, S_FindProcByName(&View->Runtime.Compiled, Item->Action.Content));
			}
			else if(Item->Action.Kind == S_NavAction_Scene)
			{
				temp Scratch = GetScratch();
				string Filepath = PushFormat(Scratch.Arena, "data/%S.vns", Item->Action.Content);;
				
				string SceneInput = Platform_ReadEntireFile(Scratch.Arena, Filepath);
				compiled_scene Scene = S_ScriptFromText(Scratch.Arena, SceneInput);
				SV_SetCurrentSource(&Scene);
				
				ReleaseScratch(Scratch);
			}
		}
	}
}

static void BuildScene(scene_view *View)
{
	v2_r32 ParentDim = UI_CalculateBoxDim(UI_TopParent());
	
	r32 TargetRatio = 16.0f/9.0f;
	r32 ActualRatio = ParentDim.x/ParentDim.y;
	
	v2_r32 BoxDim = ParentDim;
	
	if(ActualRatio>TargetRatio)
	{
		BoxDim.x = BoxDim.y*TargetRatio;
	}
	else
	{
		BoxDim.y = BoxDim.x/TargetRatio;
	}
	
	UI_SetNextWidth(UI_Pixels(BoxDim.x, 1));
	UI_SetNextHeight(UI_Pixels(BoxDim.y, 1));
	UI_SetNextLayoutAxis(Axis2_Y);
	UI_SetNextFixedP((ParentDim-BoxDim)*0.5f);
	ui_box *Box = UI_MakeBox(UI_BoxFlag_Clip|UI_BoxFlag_DrawDropShadow|UI_BoxFlag_FloatingX|UI_BoxFlag_FloatingY, StrLit("Scene View"));
	UI_EquipBoxCustomDrawCallback(Box, BuildSceneDrawCallback, View);
	
	UI_Parent(Box)
	{
		if(SV_CurrentlyInProc())
		{
			BuildProcView(View, Box, BoxDim);
		}
		else
		{
			BuildNavView(View, Box, BoxDim);
		}
	}
}

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->Errors.First; 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)
				{
					ZeroStruct(&Runtime->Errors);
					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)
				{
					SV_Reset();
				}
				
				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->Characters + CharacterIndex;
		if(AreEqual(Character->Name, Name))
		{
			Result = Character;
			break;
		}
	}
	
	//- sixten: create character if not initialized
	if(!Result && View->CharacterCount < ArrayCount(View->Characters))
	{
		s32 CharacterIndex = View->CharacterCount;
		View->CharacterCount += 1;
		
		Result = View->Characters + CharacterIndex;
		*Result = {};
		Result->Name = Name;
		Result->Active = true;
		Result->PctP = (r32)(CharacterIndex + 1) / (View->CharacterCount + 1);
	}
	
	return(Result);
}

static scene_view_character_texture_info SV_CharacterTextureFromAction(scene_character_action *Action)
{
	scene_view_character_texture_info Result = {};
	Result.Texture = EmptyRenderHandle();
	Result.Scale = 1.0f;
	
	scene_view *View = SV_GetState();
	
	if(AreEqual(StrLit("arthur"), Action->Target))
	{
		switch(Action->State)
		{
			case CR_State_Normal: { Result.Texture = View->TestNormal; } break;
			case CR_State_Happy: { Result.Texture = View->TestHappy; } break;
			default: break;
		}
		
		Result.Scale = 0.017f;
	}
	else if(AreEqual(StrLit("monika"), Action->Target))
	{
		switch(Action->State)
		{
			case CR_State_Leaning: { Result.Texture = View->MonikaLeaning; } break;
			default: break;
		}
		Result.Scale = 0.033f;
	}
	return(Result);
}

static r32 SV_CalculateTargetPctP(s32 TrueCharacterIndex)
{
	scene_view *View = SV_GetState();
	s32 CharacterCount = 0;
	s32 AssumedCharacterIndex = 0;
	for(s32 CharacterIndex = 0; CharacterIndex < View->CharacterCount; CharacterIndex += 1)
	{
		scene_view_character_data *Data = View->Characters + CharacterIndex;
		if(Data->Active)
		{
			CharacterCount += 1;
			if(CharacterIndex < TrueCharacterIndex)
			{
				AssumedCharacterIndex += 1;
			}
		}
	}
	r32 Result = (r32)(AssumedCharacterIndex + 1) / (Max(CharacterCount, 1) + 1);
	return(Result);
}

static void SV_UpdateInDialog(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;
	
	if(Compiled && Compiled->IsValid)
	{
		//- sixten: gather user input
		b32 MousePressed = false;
		for(platform_event *Event = EventList->First; Event != 0; Event = Event->Next)
		{
			if(Event->Type == PlatformEvent_Press && Event->Key == Key_MouseLeft)
			{
				MousePressed = true;
			}
		}
		
		b32 PlayerAction = (Platform_KeyPress(EventList, Key_Space)||MousePressed);
		
		//- sixten: run the scene
		if(!Runtime->LastResult.HadError)
		{
			b32 AdvanceOnAwait = (Textbox->CharsRevealed >= Textbox->String.Count) && PlayerAction;
			
			for(;;)
			{
				scene_runtime_result RunResult = S_Run(Runtime, FrameArena, AdvanceOnAwait);
				if(RunResult.ReachedAwait || RunResult.HadError)
				{
					break;
				}
			}
		}
		
		//- sixten: advance textbox
		{
			r32 CharsPerSecond = 25.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;
			}
			
			AC_AnimateValueDirect(Textbox->CharsRevealed, 0.05f, &Textbox->CharsRevealedT);
		}
		
		//- sixten: apply the textbox actions
		for(scene_textbox_action *Action = Runtime->FirstTextboxAction; Action != 0; Action = Action->Next)
		{
			if(Action->Kind == S_TextboxActionKind_Set)
			{
				string ReplaceString = Action->String;
				Textbox->String.Count = Min(ReplaceString.Count, Textbox->Capacity);
				Copy(Textbox->String.Data, ReplaceString.Data, Textbox->String.Count);
				Textbox->CharsRevealedT = 0;
				Textbox->CharsRevealed = 0;
			}
			else if(Action->Kind == S_TextboxActionKind_Append)
			{
				string Addend = Action->String;
				Textbox->CharsRevealedT = 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: update character state only if there has been a change
		if(Runtime->FirstCharacterAction != 0)
		{
			//- sixten: make all characters non-talking
			for(s32 CharacterIndex = 0; CharacterIndex < SceneView->CharacterCount; CharacterIndex += 1)
			{
				scene_view_character_data *Data = SceneView->Characters + CharacterIndex;
				Data->Talking = false;
			}
			
			//- 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
				{
					if(Action->State != CR_State_Invalid)
					{
						Data->Info = SV_CharacterTextureFromAction(Action);
					}
					Data->Talking = true;
					
					string CharacterName = Action->Target;
					scene_named_value *CharacterNameValue = S_FindGlobalVariableByName(Runtime, CharacterName, false);
					if(CharacterNameValue && CharacterNameValue->Value.Kind == S_ValueKind_String)
					{
						CharacterName = CharacterNameValue->Value.String;
					}
				}
			}
		}
		
		Runtime->FirstCharacterAction = Runtime->LastCharacterAction = 0;
	}
}

static void SV_Update(arena *FrameArena)
{
	scene_view *SceneView = SV_GetState();
	
	//- sixten: update the characters
	{
		for(s32 CharacterIndex = 0; CharacterIndex < SceneView->CharacterCount; CharacterIndex += 1)
		{
			scene_view_character_data *Data = SceneView->Characters + CharacterIndex;
			
			if(!SV_CurrentlyInProc())
			{
				Data->Active = false;
				Data->Talking = false;
			}
			
			AC_AnimateValueDirect(Data->Active, 0.5f, &Data->ActiveT);
			AC_AnimateValueDirect(Data->Talking, 0.4f, &Data->TalkingT);
			
			r32 TargetPctP;
			if(Data->Active)
			{
				TargetPctP = SV_CalculateTargetPctP(CharacterIndex);
			}
			else
			{
				TargetPctP = (r32)(CharacterIndex+1)/(SceneView->CharacterCount+1);
			}
			AC_AnimateValueDirect(TargetPctP, 0.4f, &Data->PctP);
		}
	}
	
	//- sixten: prune any unactive characters
	for(s32 CharacterIndex = 0; CharacterIndex < SceneView->CharacterCount; CharacterIndex += 1)
	{
		scene_view_character_data *Data = SceneView->Characters + CharacterIndex;
		if(!Data->Active && Data->ActiveT < 0.01)
		{
			Move(Data, Data+1, (SceneView->CharacterCount-CharacterIndex-1)*sizeof(scene_view_character_data));
			SceneView->CharacterCount -= 1;
			CharacterIndex -= 1;
		}
	}
	
	//- sixten: update scene
	if(SV_CurrentlyInProc())
	{
		SV_UpdateInDialog(FrameArena);
	}
	else
	{
		
	}
}

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);
	}
	else
	{
		UI_LabelF("Invalid source");
	}
}