From 5000060b04bed23cbbd88ba43ed546220c4b7c69 Mon Sep 17 00:00:00 2001 From: whiteshark0 Date: Tue, 2 Nov 2010 12:51:22 +0000 Subject: create experimental songmanagement branch - nicer abstraction of songs - split songloading from handling songs - cleanup singscreen git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/branches/experimental@2706 b956fd51-792f-4845-bead-9b4dfca2ff2c --- songmanagement/src/menu/UDisplay.pas | 747 +++++++++++++++++++++++++++++++++++ 1 file changed, 747 insertions(+) create mode 100644 songmanagement/src/menu/UDisplay.pas (limited to 'songmanagement/src/menu/UDisplay.pas') diff --git a/songmanagement/src/menu/UDisplay.pas b/songmanagement/src/menu/UDisplay.pas new file mode 100644 index 00000000..6ec8e2ed --- /dev/null +++ b/songmanagement/src/menu/UDisplay.pas @@ -0,0 +1,747 @@ +{* UltraStar Deluxe - Karaoke Game + * + * UltraStar Deluxe is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + *} + +unit UDisplay; + +interface + +{$IFDEF FPC} + {$MODE Delphi} +{$ENDIF} + +{$I switches.inc} + +uses + UCommon, + Math, + SDL, + gl, + glu, + SysUtils, + UMenu, + UPath, + UMusic, + UHookableEvent; + +type + TDisplay = class + private + ePreDraw: THookableEvent; + eDraw: THookableEvent; + + // fade-to-black + BlackScreen: boolean; + + FadeEnabled: boolean; // true if fading is enabled + FadeFailed: boolean; // true if fading is possible (enough memory, etc.) + FadeStartTime: cardinal; // time when fading starts, 0 means that the fade texture must be initialized + DoneOnShow: boolean; // true if passed onShow after fading + + FadeTex: array[0..1] of GLuint; + TexW, TexH: Cardinal; + + FPSCounter: cardinal; + LastFPS: cardinal; + NextFPSSwap: cardinal; + + OSD_LastError: string; + + { software cursor data } + Cursor_X: double; + Cursor_Y: double; + Cursor_Pressed: boolean; + Cursor_HiddenByScreen: boolean; // hides software cursor and deactivate auto fade in + + // used for cursor fade out when there is no movement + Cursor_Visible: boolean; + Cursor_LastMove: cardinal; + Cursor_Fade: boolean; + + procedure DrawDebugInformation; + + { called by MoveCursor and OnMouseButton to update last move and start fade in } + procedure UpdateCursorFade; + public + NextScreen: PMenu; + CurrentScreen: PMenu; + + // popup data + NextScreenWithCheck: Pmenu; + CheckOK: boolean; + + // FIXME: Fade is set to 0 in UMain and other files but not used here anymore. + Fade: real; + + constructor Create; + destructor Destroy; override; + + procedure InitFadeTextures(); + + procedure SaveScreenShot; + + function Draw: boolean; + + { calls ParseInput of cur or next Screen if assigned } + function ParseInput(PressedKey: cardinal; CharCode: UCS4Char; PressedDown : boolean): boolean; + + { sets SDL_ShowCursor depending on options set in Ini } + procedure SetCursor; + + { called when cursor moves, positioning of software cursor } + procedure MoveCursor(X, Y: double); + + { called when left or right mousebutton is pressed or released } + procedure OnMouseButton(Pressed: boolean); + { fades to specific screen (playing specified sound) } + function FadeTo(Screen: PMenu; const aSound: TAudioPlaybackStream = nil): PMenu; + + { abort fading to the current screen, may be used in OnShow, or during fade process } + procedure AbortScreenChange; + + { draws software cursor } + procedure DrawCursor; + end; + +var + Display: TDisplay; + +const + { constants for screen transition + time in milliseconds } + FADE_DURATION = 400; + { constants for software cursor effects + time in milliseconds } + CURSOR_FADE_IN_TIME = 500; // seconds the fade in effect lasts + CURSOR_FADE_OUT_TIME = 2000; // seconds the fade out effect lasts + CURSOR_AUTOHIDE_TIME = 5000; // seconds until auto fade out starts if there is no mouse movement + +implementation + +uses + TextGL, + UCommandLine, + UGraphic, + UIni, + UImage, + ULog, + UMain, + UTexture, + UTime, + ULanguage, + UPathUtils; + +constructor TDisplay.Create; +begin + inherited Create; + + // create events for plugins + ePreDraw := THookableEvent.Create('Display.PreDraw'); + eDraw := THookableEvent.Create('Display.Draw'); + + // init popup + CheckOK := false; + NextScreen := nil; + NextScreenWithCheck := nil; + BlackScreen := false; + + // init fade + FadeStartTime := 0; + FadeEnabled := (Ini.ScreenFade = 1); + FadeFailed := false; + DoneOnShow := false; + + glGenTextures(2, PGLuint(@FadeTex)); + InitFadeTextures(); + + // set LastError for OSD to No Error + OSD_LastError := 'No Errors'; + + // software cursor default values + Cursor_LastMove := 0; + Cursor_Visible := false; + Cursor_Pressed := false; + Cursor_X := -1; + Cursor_Y := -1; + Cursor_Fade := false; + Cursor_HiddenByScreen := true; +end; + +destructor TDisplay.Destroy; +begin + glDeleteTextures(2, @FadeTex); + inherited Destroy; +end; + +procedure TDisplay.InitFadeTextures(); +var + i: integer; +begin + TexW := Round(Power(2, Ceil(Log2(ScreenW div Screens)))); + TexH := Round(Power(2, Ceil(Log2(ScreenH)))); + + for i := 0 to 1 do + begin + glBindTexture(GL_TEXTURE_2D, FadeTex[i]); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexImage2D(GL_TEXTURE_2D, 0, 3, TexW, TexH, 0, GL_RGB, GL_UNSIGNED_BYTE, nil); + end; +end; + +function TDisplay.Draw: boolean; +var + S: integer; + FadeStateSquare: real; + FadeW, FadeH: real; + FadeCopyW, FadeCopyH: integer; + currentTime: cardinal; + glError: glEnum; + +begin + Result := true; + + for S := 1 to Screens do + begin + ScreenAct := S; + + //if Screens = 1 then ScreenX := 0; + //if (Screens = 2) and (S = 1) then ScreenX := -1; + //if (Screens = 2) and (S = 2) then ScreenX := 1; + ScreenX := 0; + + glViewPort((S-1) * ScreenW div Screens, 0, ScreenW div Screens, ScreenH); + + // popup check was successful... move on + if CheckOK then + begin + if assigned(NextScreenWithCheck) then + begin + NextScreen := NextScreenWithCheck; + NextScreenWithCheck := nil; + CheckOk := false; + end + else + begin + // on end of game fade to black before exit + BlackScreen := true; + end; + end; + + if (not assigned(NextScreen)) and (not BlackScreen) then + begin + ePreDraw.CallHookChain(false); + CurrentScreen.Draw; + + // popup + if (ScreenPopupError <> nil) and ScreenPopupError.Visible then + ScreenPopupError.Draw + else if (ScreenPopupInfo <> nil) and ScreenPopupInfo.Visible then + ScreenPopupInfo.Draw + else if (ScreenPopupCheck <> nil) and ScreenPopupCheck.Visible then + ScreenPopupCheck.Draw; + + // fade + FadeStartTime := 0; + if ((Ini.ScreenFade = 1) and (not FadeFailed)) then + FadeEnabled := true + else + FadeEnabled := false; + + eDraw.CallHookChain(false); + end + else + begin + // disable fading if initialization failed + if (FadeEnabled and FadeFailed) then + begin + FadeEnabled := false; + end; + + if (FadeEnabled and not FadeFailed) then + begin + // create fading texture if we're just starting + if FadeStartTime = 0 then + begin + // draw screen that will be faded + ePreDraw.CallHookChain(false); + CurrentScreen.Draw; + eDraw.CallHookChain(false); + + // clear OpenGL errors, otherwise fading might be disabled due to some + // older errors in previous OpenGL calls. + glGetError(); + + FadeCopyW := ScreenW div Screens; + FadeCopyH := ScreenH; + + // it is possible that our fade textures are too small after a window + // resize. In that case resize the fade texture to fit the requirements. + if (TexW < FadeCopyW) or (TexH < FadeCopyH) then + InitFadeTextures(); + + // copy screen to texture + glBindTexture(GL_TEXTURE_2D, FadeTex[S-1]); + glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, (S-1) * ScreenW div Screens, 0, + FadeCopyW, FadeCopyH); + + glError := glGetError(); + if (glError <> GL_NO_ERROR) then + begin + FadeFailed := true; + Log.LogError('Fading disabled: ' + gluErrorString(glError), 'TDisplay.Draw'); + end; + + if not BlackScreen and (S = 1) and not DoneOnShow then + begin + NextScreen.OnShow; + DoneOnShow := true; + end; + + + // set fade time once on second screen (or first if screens = 1) + if (Screens = 1) or (S = 2) then + FadeStartTime := SDL_GetTicks; + end; // end texture creation in first fading step + + if not BlackScreen then + begin + ePreDraw.CallHookChain(false); + NextScreen.Draw; // draw next screen + eDraw.CallHookChain(false); + end + else if ScreenAct = 1 then + begin + // draw black screen + glClearColor(0, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT); + end; + + // and draw old screen over it... slowly fading out + if (FadeStartTime = 0) then + FadeStateSquare := 0 // for first screen if screens = 2 + else + FadeStateSquare := sqr((SDL_GetTicks - FadeStartTime) / FADE_DURATION); + + if (FadeStateSquare < 1) then + begin + FadeW := (ScreenW div Screens)/TexW; + FadeH := ScreenH/TexH; + + glBindTexture(GL_TEXTURE_2D, FadeTex[S-1]); + // TODO: check if glTexEnvi() gives any speed improvement + //glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glColor4f(1, 1, 1, 1-FadeStateSquare); + + glEnable(GL_TEXTURE_2D); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_BLEND); + glBegin(GL_QUADS); + glTexCoord2f((0+FadeStateSquare/2)*FadeW, (0+FadeStateSquare/2)*FadeH); + glVertex2f(0, RenderH); + + glTexCoord2f((0+FadeStateSquare/2)*FadeW, (1-FadeStateSquare/2)*FadeH); + glVertex2f(0, 0); + + glTexCoord2f((1-FadeStateSquare/2)*FadeW, (1-FadeStateSquare/2)*FadeH); + glVertex2f(RenderW, 0); + + glTexCoord2f((1-FadeStateSquare/2)*FadeW, (0+FadeStateSquare/2)*FadeH); + glVertex2f(RenderW, RenderH); + glEnd; + glDisable(GL_BLEND); + glDisable(GL_TEXTURE_2D); + + // reset to default + //glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + end; + end + + // there is no need to init next screen if it is a black screen + else if not BlackScreen then + begin + NextScreen.OnShow; + end; + + if ((FadeStartTime + FADE_DURATION < SDL_GetTicks) or + (not FadeEnabled) or FadeFailed) and + ((Screens = 1) or (S = 2)) then + begin + // fade out complete... + FadeStartTime := 0; + DoneOnShow := false; + CurrentScreen.onHide; + CurrentScreen.ShowFinish := false; + CurrentScreen := NextScreen; + NextScreen := nil; + if not BlackScreen then + begin + CurrentScreen.OnShowFinish; + CurrentScreen.ShowFinish := true; + end + else + begin + Result := false; + Break; + end; + end; + end; // if + + // Draw OSD only on first Screen if Debug Mode is enabled + if ((Ini.Debug = 1) or (Params.Debug)) and (S = 1) then + DrawDebugInformation; + + if not BlackScreen then + DrawCursor; + end; // for +end; + +{ sets SDL_ShowCursor depending on options set in Ini } +procedure TDisplay.SetCursor; +var + Cursor: Integer; +begin + Cursor := 0; + + if (CurrentScreen <> @ScreenSing) or (Cursor_HiddenByScreen) then + begin // hide cursor on singscreen + if (Ini.Mouse = 0) and (Ini.FullScreen = 0) then + // show sdl (os) cursor in window mode even when mouse support is off + Cursor := 1 + else if (Ini.Mouse = 1) then + // show sdl (os) cursor when hardware cursor is selected + Cursor := 1; + + if (Ini.Mouse <> 2) then + Cursor_HiddenByScreen := false; + end + else if (Ini.Mouse <> 2) then + Cursor_HiddenByScreen := true; + + + SDL_ShowCursor(Cursor); + + if (Ini.Mouse = 2) then + begin + if Cursor_HiddenByScreen then + begin + // show software cursor + Cursor_HiddenByScreen := false; + Cursor_Visible := false; + Cursor_Fade := false; + end + else if (CurrentScreen = @ScreenSing) then + begin + // hide software cursor in singscreen + Cursor_HiddenByScreen := true; + Cursor_Visible := false; + Cursor_Fade := false; + end; + end; +end; + +{ called by MoveCursor and OnMouseButton to update last move and start fade in } +procedure TDisplay.UpdateCursorFade; +var + Ticks: cardinal; +begin + Ticks := SDL_GetTicks; + + { fade in on movement (or button press) if not first movement } + if (not Cursor_Visible) and (Cursor_LastMove <> 0) then + begin + if Cursor_Fade then // we use a trick here to consider progress of fade out + Cursor_LastMove := Ticks - round(CURSOR_FADE_IN_TIME * (1 - (Ticks - Cursor_LastMove)/CURSOR_FADE_OUT_TIME)) + else + Cursor_LastMove := Ticks; + + Cursor_Visible := true; + Cursor_Fade := true; + end + else if not Cursor_Fade then + begin + Cursor_LastMove := Ticks; + end; +end; + +{ called when cursor moves, positioning of software cursor } +procedure TDisplay.MoveCursor(X, Y: double); +begin + if (Ini.Mouse = 2) and + ((X <> Cursor_X) or (Y <> Cursor_Y)) then + begin + Cursor_X := X; + Cursor_Y := Y; + + UpdateCursorFade; + end; +end; + +{ called when left or right mousebutton is pressed or released } +procedure TDisplay.OnMouseButton(Pressed: boolean); +begin + if (Ini.Mouse = 2) then + begin + Cursor_Pressed := Pressed; + + UpdateCursorFade; + end; +end; + +{ draws software cursor } +procedure TDisplay.DrawCursor; +var + Alpha: single; + Ticks: cardinal; + DrawX: double; +begin + if (Ini.Mouse = 2) and ((Screens = 1) or ((ScreenAct - 1) = (Round(Cursor_X+16) div RenderW))) then + begin // draw software cursor + Ticks := SDL_GetTicks; + + if (Cursor_Visible) and (Cursor_LastMove + CURSOR_AUTOHIDE_TIME <= Ticks) then + begin // start fade out after 5 secs w/o activity + Cursor_Visible := false; + Cursor_LastMove := Ticks; + Cursor_Fade := true; + end; + + // fading + if Cursor_Fade then + begin + if Cursor_Visible then + begin // fade in + if (Cursor_LastMove + CURSOR_FADE_IN_TIME <= Ticks) then + Cursor_Fade := false + else + Alpha := sin((Ticks - Cursor_LastMove) * 0.5 * pi / CURSOR_FADE_IN_TIME) * 0.7; + end + else + begin //fade out + if (Cursor_LastMove + CURSOR_FADE_OUT_TIME <= Ticks) then + Cursor_Fade := false + else + Alpha := cos((Ticks - Cursor_LastMove) * 0.5 * pi / CURSOR_FADE_OUT_TIME) * 0.7; + end; + end; + + // no else if here because we may turn off fade in if block + if not Cursor_Fade then + begin + if Cursor_Visible then + Alpha := 0.7 // alpha when cursor visible and not fading + else + Alpha := 0; // alpha when cursor is hidden + end; + + if (Alpha > 0) and (not Cursor_HiddenByScreen) then + begin + DrawX := Cursor_X; + if (ScreenAct = 2) then + DrawX := DrawX - RenderW; + glColor4f(1, 1, 1, Alpha); + glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + glDisable(GL_DEPTH_TEST); + + if (Cursor_Pressed) and (Tex_Cursor_Pressed.TexNum > 0) then + glBindTexture(GL_TEXTURE_2D, Tex_Cursor_Pressed.TexNum) + else + glBindTexture(GL_TEXTURE_2D, Tex_Cursor_Unpressed.TexNum); + + glBegin(GL_QUADS); + glTexCoord2f(0, 0); + glVertex2f(DrawX, Cursor_Y); + + glTexCoord2f(0, 1); + glVertex2f(DrawX, Cursor_Y + 32); + + glTexCoord2f(1, 1); + glVertex2f(DrawX + 32, Cursor_Y + 32); + + glTexCoord2f(1, 0); + glVertex2f(DrawX + 32, Cursor_Y); + glEnd; + + glDisable(GL_BLEND); + glDisable(GL_TEXTURE_2D); + end; + end; +end; + +function TDisplay.ParseInput(PressedKey: cardinal; CharCode: UCS4Char; PressedDown : boolean): boolean; +begin + if (assigned(NextScreen)) then + Result := NextScreen^.ParseInput(PressedKey, CharCode, PressedDown) + else if (assigned(CurrentScreen)) then + Result := CurrentScreen^.ParseInput(PressedKey, CharCode, PressedDown) + else + Result := True; +end; + +{ abort fading to the next screen, may be used in OnShow, or during fade process } +procedure TDisplay.AbortScreenChange; + var + Temp: PMenu; +begin + // this is some kind of "hack" it is based on the + // code that is used to change the screens in TDisplay.Draw + // we should rewrite this whole behaviour, as it is not well + // structured and not well extendable. Also we should offer + // a possibility to change screens to plugins + // change this code when restructuring is done + if (assigned(NextScreen)) then + begin + // we have to swap the screens + Temp := CurrentScreen; + CurrentScreen := NextScreen; + NextScreen := Temp; + + // and call the OnShow procedure of the previous screen + // because it was already called by default fade procedure + NextScreen.OnShow; + + end; +end; + +{ fades to specific screen (playing specified sound) + returns old screen } +function TDisplay.FadeTo(Screen: PMenu; const aSound: TAudioPlaybackStream = nil): PMenu; +begin + Result := CurrentScreen; + if (Result <> nil) then + begin + if (aSound <> nil) then + Result.FadeTo(Screen, aSound) + else + Result.FadeTo(Screen); + end; +end; + +procedure TDisplay.SaveScreenShot; +var + Num: integer; + FileName: IPath; + Prefix: UTF8String; + ScreenData: PChar; + Surface: PSDL_Surface; + Success: boolean; + Align: integer; + RowSize: integer; +begin + // Exit if Screenshot-path does not exist or read-only + if (ScreenshotsPath.IsUnset) then + Exit; + + for Num := 1 to 9999 do + begin + // fill prefix to 4 digits with leading '0', e.g. '0001' + Prefix := Format('screenshot%.4d', [Num]); + FileName := ScreenshotsPath.Append(Prefix + '.png'); + if not FileName.Exists() then + break; + end; + + // we must take the row-alignment (4byte by default) into account + glGetIntegerv(GL_PACK_ALIGNMENT, @Align); + // calc aligned row-size + RowSize := ((ScreenW*3 + (Align-1)) div Align) * Align; + + GetMem(ScreenData, RowSize * ScreenH); + glReadPixels(0, 0, ScreenW, ScreenH, GL_RGB, GL_UNSIGNED_BYTE, ScreenData); + // on big endian machines (powerpc) this may need to be changed to + // Needs to be tests. KaMiSchi Sept 2008 + // in this case one may have to add " glext, " to the list of used units + // glReadPixels(0, 0, ScreenW, ScreenH, GL_BGR, GL_UNSIGNED_BYTE, ScreenData); + Surface := SDL_CreateRGBSurfaceFrom( + ScreenData, ScreenW, ScreenH, 24, RowSize, + $0000FF, $00FF00, $FF0000, 0); + + // Success := WriteJPGImage(FileName, Surface, 95); + // Success := WriteBMPImage(FileName, Surface); + Success := WritePNGImage(FileName, Surface); + if Success then + ScreenPopupInfo.ShowPopup(Format(Language.Translate('SCREENSHOT_SAVED'), [FileName.GetName.ToUTF8()])) + else + ScreenPopupError.ShowPopup(Language.Translate('SCREENSHOT_FAILED')); + + SDL_FreeSurface(Surface); + FreeMem(ScreenData); +end; + +//------------ +// DrawDebugInformation - procedure draw fps and some other informations on screen +//------------ +procedure TDisplay.DrawDebugInformation; +var + Ticks: cardinal; +begin + // Some White Background for information + glEnable(GL_BLEND); + glDisable(GL_TEXTURE_2D); + glColor4f(1, 1, 1, 0.5); + glBegin(GL_QUADS); + glVertex2f(690, 44); + glVertex2f(690, 0); + glVertex2f(800, 0); + glVertex2f(800, 44); + glEnd; + glDisable(GL_BLEND); + + // set font specs + SetFontStyle(ftNormal); + SetFontSize(21); + SetFontItalic(false); + glColor4f(0, 0, 0, 1); + + // calculate fps + Ticks := SDL_GetTicks(); + if (Ticks >= NextFPSSwap) then + begin + LastFPS := FPSCounter * 4; + FPSCounter := 0; + NextFPSSwap := Ticks + 250; + end; + + Inc(FPSCounter); + + // draw text + + // fps + SetFontPos(695, 0); + glPrint ('FPS: ' + InttoStr(LastFPS)); + + // rspeed + SetFontPos(695, 13); + glPrint ('RSpeed: ' + InttoStr(Round(1000 * TimeMid))); + + // lasterror + SetFontPos(695, 26); + glColor4f(1, 0, 0, 1); + glPrint (OSD_LastError); + + glColor4f(1, 1, 1, 1); +end; + +end. -- cgit v1.2.3