diff --git a/Themes/_fallback/BGAnimations/ScreenSystemLayer errorbg.lua b/Themes/_fallback/BGAnimations/ScreenSystemLayer errorbg.lua new file mode 100644 index 0000000000..94615c7876 --- /dev/null +++ b/Themes/_fallback/BGAnimations/ScreenSystemLayer errorbg.lua @@ -0,0 +1,14 @@ +return Def.ActorFrame{ + Def.Quad { + Name= "erp", + InitCommand= function(self) + self:setsize(SCREEN_WIDTH, SCREEN_HEIGHT) + self:horizalign(left) + self:vertalign(top) + self:diffuse(color("0,0,0,0")) + self:diffusealpha(.85) + end, + SetCoveredHeightCommand= function(self, param) + end + } +} diff --git a/Themes/_fallback/BGAnimations/ScreenSystemLayer overlay/default.lua b/Themes/_fallback/BGAnimations/ScreenSystemLayer overlay/default.lua index 0e638d6afe..f2c09ff8a4 100644 --- a/Themes/_fallback/BGAnimations/ScreenSystemLayer overlay/default.lua +++ b/Themes/_fallback/BGAnimations/ScreenSystemLayer overlay/default.lua @@ -64,27 +64,147 @@ t[#t+1] = Def.ActorFrame { CreditsText( PLAYER_2 ); }; -- Text -t[#t+1] = Def.ActorFrame { - Def.Quad { - InitCommand=cmd(zoomtowidth,SCREEN_WIDTH;zoomtoheight,30;horizalign,left;vertalign,top;y,SCREEN_TOP;diffuse,color("0,0,0,0")); - OnCommand=cmd(finishtweening;diffusealpha,0.85;); - OffCommand=cmd(sleep,3;linear,0.5;diffusealpha,0;); - }; - LoadFont("Common","Normal") .. { - Name="Text"; - InitCommand=cmd(maxwidth,750;horizalign,left;vertalign,top;y,SCREEN_TOP+10;x,SCREEN_LEFT+10;shadowlength,1;diffusealpha,0;); - OnCommand=cmd(finishtweening;diffusealpha,1;zoom,0.5); - OffCommand=cmd(sleep,3;linear,0.5;diffusealpha,0;); - }; - SystemMessageMessageCommand = function(self, params) - self:GetChild("Text"):settext( params.Message ); - self:playcommand( "On" ); - if params.NoAnimate then - self:finishtweening(); - end - self:playcommand( "Off" ); - end; - HideSystemMessageMessageCommand = cmd(finishtweening); -}; -return t; +-- Minor text formatting functions from Kyzentun. +-- TODO: Figure out why BitmapText:maxwidth doesn't do what I want. +local function width_limit_text(text, limit, natural_zoom) + natural_zoom= natural_zoom or 1 + if text:GetWidth() * natural_zoom > limit then + text:zoomx(limit / text:GetWidth()) + else + text:zoomx(natural_zoom) + end +end + +local function width_clip_text(text, limit) + local full_text= text:GetText() + local fits= text:GetZoomedWidth() <= limit + local prev_max= #full_text - 1 + local prev_min= 0 + if not fits then + while prev_max - prev_min > 1 do + local new_max= math.round((prev_max + prev_min) / 2) + text:settext(full_text:sub(1, 1+new_max)) + if text:GetZoomedWidth() <= limit then + prev_min= new_max + else + prev_max= new_max + end + end + text:settext(full_text:sub(1, 1+prev_min)) + end +end + +local function width_clip_limit_text(text, limit, natural_zoom) + natural_zoom= natural_zoom or text:GetZoomY() + local text_width= text:GetWidth() * natural_zoom + if text_width > limit * 2 then + text:zoomx(natural_zoom * .5) + width_clip_text(text, limit) + else + width_limit_text(text, limit, natural_zoom) + end +end + +local errorbg +local err_frame +local text_actors= {} +local line_height= 12 -- A good line height for Common Normal at .5 zoom. +local line_width= SCREEN_WIDTH - 20 + +local next_message_actor= 1 + +local min_message_time= { show= 4, hide= .125} +local message_time= { + show= min_message_time.show, hide= min_message_time.hide} + +function SetOverlayMessageTime(t, which) + if not min_message_time[which] then + SCREENMAN:SystemMessage("Attempted to set invalid overlay message time field: " .. tostring(which)) + return + end + if t < min_message_time[which] then + SCREENMAN:SystemMessage( + "Attempted to set overlay message " .. which .. + " time to below minimum of " .. min_message_time[which] .. ".") + return + end + message_time[which]= t +end + +local errbg_actor= LoadActor(THEME:GetPathB("ScreenSystemLayer", "errorbg")) +-- Force the name of the returned actor so that we can easily use playcommand on it later. +errbg_actor.Name= "errorbg" + +local frame_args= { + Name="Error frame", + InitCommand= function(self) + err_frame= self + errorbg= self:GetChild("errorbg") + self:y(-SCREEN_HEIGHT) + end, + SystemMessageMessageCommand = function(self, params) + err_frame:stoptweening() + err_frame:visible(true) + local covered_height= line_height * (next_message_actor-1) + err_frame:y(-SCREEN_HEIGHT + covered_height) + if errorbg then + errorbg:playcommand("SetCoveredHeight", {height= covered_height}) + end + err_frame:sleep(message_time.show) + err_frame:queuecommand("DecNextActor") + -- Shift the text on all the actors being shown up by one. + for i= next_message_actor, 1, -1 do + if text_actors[i] then + text_actors[i]:visible(true) + if i > 1 and text_actors[i-1] then + text_actors[i]:settext(text_actors[i-1]:GetText()) + else + text_actors[i]:settext(params.Message) + end + width_clip_limit_text(text_actors[i], line_width) + end + end + if next_message_actor <= #text_actors and not params.NoAnimate then + next_message_actor= next_message_actor + 1 + end + end, + DecNextActorCommand= function(self) + self:linear(message_time.hide) + self:y(self:GetY()-line_height) + if text_actors[next_message_actor] then + text_actors[next_message_actor]:visible(false) + end + next_message_actor= next_message_actor - 1 + if next_message_actor > 1 then + self:queuecommand("DecNextActor") + else + self:queuecommand("Off") + end + end, + OffCommand= cmd(visible,false), + HideSystemMessageMessageCommand = cmd(finishtweening), + errbg_actor, +} +-- Create enough text actors that we can fill the screen. +local num_text= SCREEN_HEIGHT / line_height +for i= 1, num_text do + frame_args[#frame_args+1]= LoadFont("Common","Normal") .. { + Name="Text" .. i, + InitCommand= function(self) + -- Put them in the list in reverse order so the ones at the bottom of the screen are used first. + text_actors[num_text-i+1]= self + self:horizalign(left) + self:vertalign(top) + self:x(SCREEN_LEFT + 10) + self:y(SCREEN_TOP + (line_height * (i-1)) + 2) + self:shadowlength(1) + self:zoom(.5) + self:visible(false) + end, + OffCommand= cmd(visible,false), + } +end +t[#t+1] = Def.ActorFrame(frame_args) + +return t diff --git a/Themes/_fallback/Languages/en.ini b/Themes/_fallback/Languages/en.ini index 237922b3cd..448a35b2e7 100644 --- a/Themes/_fallback/Languages/en.ini +++ b/Themes/_fallback/Languages/en.ini @@ -1317,6 +1317,7 @@ Profile=Profile Pull Back Camera=Pull Back Camera Restart=Restart Reload=Reload +Reload Overlay Screens=Reload Overlay Screens Reload Theme and Textures=Reload Theme and Textures Rendering Stats=Rendering Stats Screen Test Mode=Screen Test Mode diff --git a/src/ScreenDebugOverlay.cpp b/src/ScreenDebugOverlay.cpp index 3cdfe08a9f..e931f38e42 100644 --- a/src/ScreenDebugOverlay.cpp +++ b/src/ScreenDebugOverlay.cpp @@ -548,6 +548,7 @@ static LocalizedString RELOAD ( "ScreenDebugOverlay", "Reload" ); static LocalizedString RESTART ( "ScreenDebugOverlay", "Restart" ); static LocalizedString SCREEN_ON ( "ScreenDebugOverlay", "Send On To Screen" ); static LocalizedString SCREEN_OFF ( "ScreenDebugOverlay", "Send Off To Screen" ); +static LocalizedString RELOAD_OVERLAY_SCREENS( "ScreenDebugOverlay", "Reload Overlay Screens" ); static LocalizedString RELOAD_THEME_AND_TEXTURES( "ScreenDebugOverlay", "Reload Theme and Textures" ); static LocalizedString WRITE_PROFILES ( "ScreenDebugOverlay", "Write Profiles" ); static LocalizedString WRITE_PREFERENCES ( "ScreenDebugOverlay", "Write Preferences" ); @@ -1030,6 +1031,19 @@ class DebugLineReloadTheme : public IDebugLine } }; +class DebugLineReloadOverlayScreens : public IDebugLine +{ + virtual RString GetDisplayTitle() { return RELOAD_OVERLAY_SCREENS.GetValue(); } + virtual RString GetDisplayValue() { return RString(); } + virtual bool IsEnabled() { return true; } + virtual RString GetPageName() const { return "Theme"; } + virtual void DoAndLog( RString &sMessageOut ) + { + IDebugLine::DoAndLog(sMessageOut); + SCREENMAN->ReloadOverlayScreensAfterInputFinishes(); + } +}; + class DebugLineWriteProfiles : public IDebugLine { virtual RString GetDisplayTitle() { return WRITE_PROFILES.GetValue(); } @@ -1212,6 +1226,7 @@ DECLARE_ONE( DebugLineRestartCurrentScreen ); DECLARE_ONE( DebugLineCurrentScreenOn ); DECLARE_ONE( DebugLineCurrentScreenOff ); DECLARE_ONE( DebugLineReloadTheme ); +DECLARE_ONE( DebugLineReloadOverlayScreens ); DECLARE_ONE( DebugLineWriteProfiles ); DECLARE_ONE( DebugLineWritePreferences ); DECLARE_ONE( DebugLineMenuTimer ); diff --git a/src/ScreenManager.cpp b/src/ScreenManager.cpp index bd24d9d2cc..e9079c43e1 100644 --- a/src/ScreenManager.cpp +++ b/src/ScreenManager.cpp @@ -242,6 +242,7 @@ ScreenManager::ScreenManager() g_pSharedBGA = new Actor; + m_bReloadOverlayScreensAfterInput= false; m_bZeroNextUpdate = false; m_PopTopScreen = SM_Invalid; m_OnDonePreparingScreen = SM_Invalid; @@ -330,6 +331,11 @@ void ScreenManager::ReloadOverlayScreens() this->RefreshCreditsMessages(); } +void ScreenManager::ReloadOverlayScreensAfterInputFinishes() +{ + m_bReloadOverlayScreensAfterInput= true; +} + Screen *ScreenManager::GetTopScreen() { if( g_ScreenStack.empty() ) @@ -514,7 +520,14 @@ void ScreenManager::Input( const InputEventPlus &input ) // because anybody setting an input callback is probably doing it to // do something in addition to whatever the screen does. if(pScreen->PassInputToLua(input) || handled) + { + if(m_bReloadOverlayScreensAfterInput) + { + ReloadOverlayScreens(); + m_bReloadOverlayScreensAfterInput= false; + } return; + } } // Pass input to the topmost screen. If we have a new top screen pending, don't diff --git a/src/ScreenManager.h b/src/ScreenManager.h index d9b98649e6..304cb77d5b 100644 --- a/src/ScreenManager.h +++ b/src/ScreenManager.h @@ -51,6 +51,7 @@ public: void RefreshCreditsMessages(); void ThemeChanged(); void ReloadOverlayScreens(); + void ReloadOverlayScreensAfterInputFinishes(); /** * @brief Is this Screen in the main Screen stack, but not the bottommost Screen? @@ -79,6 +80,10 @@ private: // operations take a long time, and will cause a skip on the next update. bool m_bZeroNextUpdate; + // This exists so the debug overlay can reload the overlay screens without seg faulting. + // It's "AfterInput" because the debug overlay carries out actions in Input. + bool m_bReloadOverlayScreensAfterInput; + Screen *MakeNewScreen( const RString &sName ); void LoadDelayedScreen(); bool ActivatePreparedScreenAndBackground( const RString &sScreenName );