/* * Copyright (C) 2002,2004 Daniel Heck * * 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; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /* * This file contains the code for initializing the video hardware and * for various visual effects in the widest sense. This includes * * a) managing and rendering the mouse cursor, * b) changing between windowed and fullscreen mode, * c) fading between different screen using one of a number of * predefined effects. * * The code in this file is independent of the game. For game-specific * display code, refer to file display.cc */ #include "enigma.hh" #include "video.hh" #include "lua.hh" #include "options.hh" #include "main.hh" #include "ecl.hh" #include "SDL.h" #include #include #include #include "config.h" #define SCREEN ecl::Screen::get_instance() using namespace std; using namespace ecl; using namespace video; using namespace enigma; namespace { class Video_SDL { SDL_Surface* sdlScreen; string caption; ecl::Screen* screen; bool initialized; public: Video_SDL(); ~Video_SDL(); bool init(int w, int h, int bpp, bool fullscreen); void toggle_fullscreen(); void set_fullscreen(bool on_off); bool is_fullscreen() const; void set_caption(const char *str); const string& get_caption() const { return caption; } ecl::Screen *get_screen() { return screen; } }; class MouseCursor { public: MouseCursor (); ~MouseCursor(); void set_image (ecl::Surface *s, int hotx_, int hoty_); void move (int newx, int newy); void redraw (); // Redraw if position/image changed void draw(); // Draw cursor if visible void show (); void hide (); Rect get_rect() const; Rect get_oldrect() const; bool has_changed() { return changedp; } int get_x() const { return x; } int get_y() const { return y; } private: // Private methods void grab_bg (); void init_bg(); void restore_bg(); // Variables Surface *background; // Copy of screen contents behind cursor Surface *cursor; // Pixmap of the cursor int x, y; int oldx, oldy; int hotx, hoty; // Coordinates of hotspot inside cursor image int visible; bool changedp; }; } /* -------------------- Video Engine -------------------- */ Video_SDL::Video_SDL() : sdlScreen(0), screen(0), initialized(false) {} Video_SDL::~Video_SDL() { SDL_WM_GrabInput(SDL_GRAB_OFF); // if (sdlScreen != 0 && fullScreen) // SDL_WM_ToggleFullScreen(sdlScreen); delete screen; } void Video_SDL::set_caption(const char *str) { caption = str; if (initialized) SDL_WM_SetCaption(str, 0); } bool Video_SDL::init(int w, int h, int bpp, bool fullscreen) { SDL_WM_SetCaption(caption.c_str(), 0); Uint32 flags = SDL_SWSURFACE; if (fullscreen) flags |= SDL_FULLSCREEN; // Try to initialize vide mode, return error code on failure sdlScreen = 0; bpp = SDL_VideoModeOK (w, h, bpp, flags); if (bpp == 0) return false; sdlScreen = SDL_SetVideoMode(w, h, bpp, flags); if (sdlScreen == 0) return false; // Video mode could be set screen = new Screen(sdlScreen); initialized = true; #if 0 // the Mac SDL port seems to ignore the following ShowCursor, // so we just set the Cursor to be invisible. SDL_Cursor *hiddenCursor=SDL_CreateCursor(NULL, NULL, 0, 0, 0, 0); SDL_SetCursor(hiddenCursor); SDL_FreeCursor(hiddenCursor); #endif // Hack to hide the cursor after switching between // window/fullscreen mode. SDL_ShowCursor (SDL_ENABLE); SDL_ShowCursor (SDL_DISABLE); SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY / 2, SDL_DEFAULT_REPEAT_INTERVAL / 2); return true; } bool Video_SDL::is_fullscreen() const { if (sdlScreen) return (sdlScreen->flags & SDL_FULLSCREEN) != 0; return false; } void Video_SDL::set_fullscreen(bool on_off) { if (on_off != is_fullscreen()) toggle_fullscreen(); } void Video_SDL::toggle_fullscreen() { SDL_WM_ToggleFullScreen (sdlScreen); } /* -------------------- MouseCursor -------------------- */ MouseCursor::MouseCursor () : background(0), cursor(0) { oldx = oldy = 0; hotx = hoty = 0; visible = 0; changedp = true; } MouseCursor::~MouseCursor() { delete background; delete cursor; } void MouseCursor::set_image (ecl::Surface *s, int hx, int hy) { delete cursor; cursor = s; hotx = hx; hoty = hy; if (visible > 0) { init_bg(); } } void MouseCursor::draw () { // we do not need a mouse cursor on a touchscreen device #ifndef ANDROID if (visible > 0) { grab_bg(); GC gc(SCREEN->get_surface()); blit (gc, x-hotx, y-hoty, cursor); SCREEN->update_rect (get_rect()); changedp = false; } #endif } void MouseCursor::redraw () { if (visible > 0 && changedp) { restore_bg (); draw(); } } void MouseCursor::move(int newx, int newy) { x = newx; y = newy; changedp = true; } void MouseCursor::show () { if (++visible == 1) { init_bg(); changedp=true; } } void MouseCursor::hide () { if (--visible == 0) { changedp = true; restore_bg(); delete background; background=0; } } Rect MouseCursor::get_rect() const { int scrx=x-hotx; int scry=y-hoty; return Rect(scrx,scry,cursor->width(), cursor->height()); } Rect MouseCursor::get_oldrect() const { int scrx=oldx-hotx; int scry=oldy-hoty; return Rect(scrx,scry,cursor->width(), cursor->height()); } void MouseCursor::init_bg() { assert (visible > 0); assert (cursor != 0); if (background != 0) { delete background; } background = ecl::MakeSurfaceLike (cursor->width(), cursor->height(), SCREEN->get_surface()); grab_bg(); } void MouseCursor::grab_bg () { assert (background != 0); GC gc(background); blit (gc, 0,0, SCREEN->get_surface(), get_rect()); oldx=x; oldy=y; } void MouseCursor::restore_bg () { if (background) { GC gc(SCREEN->get_surface()); blit (gc, oldx-hotx, oldy-hoty, background); SCREEN->update_rect (get_oldrect()); } } /* -------------------- Local Variables -------------------- */ namespace { Video_SDL *video_engine = 0; MouseCursor *cursor = 0; Surface *back_buffer = 0; /*! List of available video modes. */ video::VMInfo video_modes[] = { { VM_640x480, 640, 480, 32, "640x480", "models-32.lua", "gfx32/", 120, 78, "thumbs32", // thumbnail size/directory Rect (0, 0, 640, 416), // game area Rect (0, 416, 640, 64), // statusbar area Rect (10, 425, 117, 43), // time area Rect (100, 425, 30, 43), // moves area Rect (152, 433, 490, 52),// inventory area Rect (150, 434, 475, 35),// text area VM_None, true, }, { VM_640x512, 640, 512, 32, "640x512", "models-32.lua", "gfx32/", 120, 78, "thumbs32", // thumbnail size/directory Rect (0, 0, 640, 416), // game area Rect (0, 416, 640, 64), // statusbar area Rect (15, 420, 110, 40), // time area Rect (100, 420, 30, 40), // moves area Rect (152, 433, 490, 52),// inventory area Rect (150, 434, 475, 35), // text area VM_640x480, false, // 640x512 is deprecated! }, #ifndef ANDROID { VM_800x600, 800, 600, 40, "800x600", "models-40.lua", "gfx40/", // 160, 104, "thumbs40", // thumbnail size/directory 120, 78, "thumbs32", Rect (0, 0, 800, 520), // game area Rect (0, 520, 800, 80), // statusbar area Rect (15, 540, 140, 40), // time area Rect (125, 540, 30, 40), // moves area Rect (192, 539, 610, 46),// inventory area Rect (185, 545, 600, 39), // text area VM_640x480, true, }, { VM_1024x768, 1024, 768, 48, "1024x768", "models-48.lua", "gfx48/", // 160, 104, "thumbs40", // thumbnail size/directory 120, 78, "thumbs32", Rect (32, 0, 960, 624), // game area Rect (32, 624, 960, 96), // statusbar area Rect (50, 640, 170, 60), // time area Rect (185, 640, 30, 60), // moves area Rect (260, 650, 710, 46),// inventory area Rect (260, 655, 710, 40), // text area VM_640x480, true, }, #endif }; VideoModes current_video_mode = VM_None; } /* -------------------- Auxiliary functions -------------------- */ namespace { bool vm_available (int w, int h, int &bpp, bool &fullscreen) { Uint32 flags = SDL_HWSURFACE; if (fullscreen) flags |= SDL_FULLSCREEN; int newbpp = SDL_VideoModeOK (w, h, bpp, flags); if (newbpp != 0) { bpp = newbpp; return true; } return false; } /*! This function is installed as an event filter by video::Init. It intercepts mouse motions, which are used to update the position of the mouse cursor (but passed on to the event queue) */ int event_filter(const SDL_Event *e) { if (e->type == SDL_MOUSEMOTION) { cursor->move(e->motion.x, e->motion.y); cursor->redraw(); } return 1; } } /* -------------------- Functions -------------------- */ void video::SetMouseCursor(ecl::Surface *s, int hotx, int hoty) { cursor->set_image(s, hotx, hoty); cursor->redraw(); } void video::HideMouse() { cursor->hide(); cursor->redraw(); } void video::ShowMouse() { cursor->show(); cursor->redraw(); } int video::Mousex() { return cursor->get_x(); } int video::Mousey() { return cursor->get_y(); } /* -------------------- Input grabbing -------------------- */ TempInputGrab::TempInputGrab (bool onoff) { old_onoff = SetInputGrab (onoff); } TempInputGrab::~TempInputGrab() { SetInputGrab (old_onoff); } bool video::GetInputGrab() { return SDL_WM_GrabInput (SDL_GRAB_QUERY) == SDL_GRAB_ON; } bool video::SetInputGrab (bool onoff) { bool old_onoff = GetInputGrab (); if (onoff) { Screen *screen = GetScreen(); SDL_WarpMouse (screen->width()/2, screen->height()/2); SDL_Event e; while (SDL_PollEvent(&e)) { ; // swallow mouse motion event } SDL_WM_GrabInput (SDL_GRAB_ON); } else SDL_WM_GrabInput (SDL_GRAB_OFF); return old_onoff; } Surface* video::BackBuffer() { if (back_buffer==0) { back_buffer= Duplicate(SCREEN->get_surface()); } return back_buffer; } const video::VMInfo *video::GetInfo (VideoModes vm) { return &video_modes[vm]; } const video::VMInfo *video::GetInfo () { return GetInfo (current_video_mode); } bool video::ModeAvailable (VideoModes vm) { const VMInfo *vminfo = GetInfo (vm); string fname; return (vminfo->available && app.systemFS->findFile (vminfo->initscript, fname)); } void video::Init() { assert (NUMENTRIES(video_modes) == VM_COUNT); int vidmode = Clamp (options::GetInt("VideoMode"), 0, VM_COUNT-1); int oldvidmode = vidmode; video_engine = new Video_SDL(); int bpp = 16; //options::BitsPerPixel; assert(bpp==16 || bpp==32); while (true) { VMInfo *vminfo = &video_modes[vidmode]; int w = vminfo->width; int h = vminfo->height; bool fullscreen = options::GetBool("FullScreen"); if (ModeAvailable (static_cast (vidmode)) && vm_available (w, h, bpp, fullscreen) && video_engine->init (w, h, bpp, fullscreen)) { // Success! break; } // Video mode not available? Try the fallback video mode vidmode = vminfo->fallback_videomode; if (vidmode == VM_None) { // Give up :-( fprintf(stderr, "Couldn't open screen: %s\n", SDL_GetError()); exit(1); } } current_video_mode = static_cast(vidmode); if (vidmode != oldvidmode) { options::SetOption ("VideoMode", vidmode); } #ifndef MACOSX Surface *icn = enigma::GetImage("enigma_marble"); if(icn) SDL_WM_SetIcon(icn->get_surface(), NULL); #endif cursor = new MouseCursor; int x, y; SDL_GetMouseState(&x, &y); cursor->move(x,y); SDL_SetEventFilter(event_filter); UpdateGamma(); } void video::Shutdown() { SDL_SetEventFilter(0); delete video_engine; delete cursor; delete back_buffer; video_engine = 0; cursor = 0; back_buffer = 0; } void video::ChangeVideoMode() { MouseCursor *oldcursor = cursor; cursor = 0; Shutdown(); Init(); delete cursor; cursor = oldcursor; } ecl::Screen * video::GetScreen() { return SCREEN; } VideoModes video::GetVideoMode() { return current_video_mode; } bool video::IsFullScreen() { return video_engine->is_fullscreen(); } int video::GetColorDepth() { return SCREEN->get_surface()->bipp(); } bool video::SetFullscreen(bool onoff) { video_engine->set_fullscreen(onoff); bool is_fullscreen = video_engine->is_fullscreen(); if (onoff == is_fullscreen) { options::SetOption("FullScreen", is_fullscreen); } return is_fullscreen; } bool video::ToggleFullscreen() { return SetFullscreen (!video_engine->is_fullscreen()); } void video::SetCaption(const char *str) { video_engine->set_caption(str); } const string& video::GetCaption() { return video_engine->get_caption(); } void video::UpdateGamma() { float gamma = static_cast (options::GetDouble ("Gamma")); if (gamma < 0.25) gamma = 0.25; // Windows does not set gamma for values < 0.2271 SDL_SetGamma (gamma, gamma, gamma); } void video::Screenshot (const std::string &fname) { // auto-create the directory if necessary string directory; if (ecl::split_path (fname, &directory, 0) && !ecl::FolderExists(directory)) { ecl::FolderCreate (directory); } ecl::SavePNG (SCREEN->get_surface(), fname); enigma::Log << "Wrote screenshot to '" << fname << "\n"; } /* -------------------- Special Effects -------------------- */ void video::FX_Fade(FadeMode mode) { ecl::Screen *screen = ecl::Screen::get_instance(); Surface *d = screen->get_surface(); const double fadesec = 0.6; double v = 255/fadesec; ecl::Surface *buffer = Duplicate(d); double dt; double a = mode==FADEIN ? 0 : 255; GC gc (d); while (true) { Uint32 otime = SDL_GetTicks(); box(gc, d->size()); buffer->set_alpha(int(a)); blit(gc, 0,0,buffer); screen->update_all(); screen->flush_updates(); dt = (SDL_GetTicks()-otime)/1000.0; if (mode==FADEIN && (a+=v*dt) > 255) break; else if (mode==FADEOUT && (a-=v*dt) < 0) break; } if (mode==FADEIN) { buffer->set_alpha(255); blit(gc, 0,0,buffer); } else box (gc, d->size()); screen->update_all(); screen->flush_updates(); delete buffer; } void video::FX_Fly (Surface *newscr, int originx, int originy) { double rest_time = 0.5; double velx = -originx / rest_time; double vely = -originy / rest_time; double origx = originx; double origy = originy; Screen *scr = SCREEN; GC scrgc(scr->get_surface()); while (rest_time > 0) { Uint32 otime = SDL_GetTicks(); Rect r(static_cast(origx), static_cast(origy), scr->width(), scr->height()); blit (scrgc, r.x, r.y, newscr); scr->update_rect(r); scr->flush_updates(); double dt = (SDL_GetTicks()-otime)/1000.0; if (dt > rest_time) dt = rest_time; rest_time -= dt; origx += velx * dt; origy += vely * dt; } } namespace { class Effect_Push : public TransitionEffect { public: Effect_Push(ecl::Surface *newscr, int originx, int originy); void tick (double dtime); bool finished() const; private: double rest_time; ecl::Surface *newscr; std::auto_ptr oldscr; int originx, originy; double velx, vely; double accx, accy; double x, y; double t; }; } Effect_Push::Effect_Push(ecl::Surface *newscr_, int originx_, int originy_) : rest_time (0.7), newscr (newscr_), oldscr (Duplicate(SCREEN->get_surface())), originx (originx_), originy (originy_), velx (-2 * originx / rest_time), vely (-2 * originy / rest_time), accx (-0.5*velx/rest_time), accy (-0.5*vely/rest_time), x (originx), y (originy), t (0) { } void Effect_Push::tick (double dtime) { Screen *scr = SCREEN; GC scrgc(scr->get_surface()); if (rest_time > 0) { if (dtime > rest_time) dtime = rest_time; rest_time -= dtime; t+=dtime; x = (accx*t + velx)*t + originx; y = (accy*t + vely)*t + originy; blit (scrgc, (int)x-originx, (int)y, oldscr.get()); blit (scrgc, (int)x, (int)y-originy, oldscr.get()); blit (scrgc, (int)x-originx, (int)y-originy, oldscr.get()); blit (scrgc, (int)x, (int) y, newscr); scr->update_all(); scr->flush_updates(); } else { blit(scrgc, 0,0, newscr); scr->update_all(); scr->flush_updates(); } } bool Effect_Push::finished() const { return rest_time <= 0; } TransitionEffect * video::MakeEffect (TransitionModes tm, ecl::Surface *newscr) { int scrw = SCREEN->width(); int scrh = SCREEN->height(); switch (tm) { case TM_PUSH_RANDOM: { int xo=0, yo=0; while (xo==0 && yo==0) { xo = enigma::IntegerRand(-1,1)*scrw; yo = enigma::IntegerRand(-1,1)*scrh; } return new Effect_Push(newscr, xo, yo); } case TM_PUSH_N: return new Effect_Push (newscr, 0, -scrh); case TM_PUSH_S: return new Effect_Push (newscr, 0, +scrh); case TM_PUSH_E: return new Effect_Push (newscr, +scrw, 0); case TM_PUSH_W: return new Effect_Push (newscr, -scrw, 0); default: return 0; }; } void video::ShowScreen (TransitionModes tm, Surface *newscr) { int scrw = SCREEN->width(); int scrh = SCREEN->height(); switch (tm) { case TM_RANDOM: break; case TM_FADEOUTIN: break; case TM_SQUARES: break; case TM_FLY_N: FX_Fly (newscr, 0, -scrh); break; case TM_FLY_S: FX_Fly (newscr, 0, +scrh); break; case TM_FLY_E: FX_Fly (newscr, +scrw, 0); break; case TM_FLY_W: FX_Fly (newscr, -scrw, 0); break; default: break; } }