#include "stage.h" #include #include "loader.h" #include "tween.h" /* Copyright (c) 2013-2021 Devine Lu Linvega 2018 web port by rezmason 2021 C89 port by rezmason Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE. */ static RegionRect windowRect = {0, 0, 0, 0}; static SDL_Rect nudgeRect = {0, 0, 0, 0}; static double _totalTime; static double nudgeX = 0; static double nudgeY = 0; static Tween nudgeTween; static Tween tintTween; static SDL_Color tintColor = {0xFF, 0xFF, 0xFF, 0xFF}; static SDL_Color tintGoalColor = {0xFF, 0xFF, 0xFF, 0xFF}; #ifdef DRAW_REGION_BORDERS static SDL_Rect regionBorderDest; static SDL_Texture *regionBorderTexture = NULL; #endif static void resizeBounds(Region *region) { RegionRect *layout = ®ion->layout; RegionRect *bounds = ®ion->bounds; RegionRect *scaleRect; /* STRETCH NODE regions are relative to the node region */ if(region->layoutMode == LAYOUTMODE_STRETCH_NODE) { scaleRect = &(regions[BILLBOARD_NODE].contentRect); } else { scaleRect = &windowRect; } bounds->x = layout->x * scaleRect->w + scaleRect->x; bounds->y = layout->y * scaleRect->h + scaleRect->y; bounds->w = layout->w * scaleRect->w; bounds->h = layout->h * scaleRect->h; } static void resizeContent(Region *region) { RegionRect *bounds = ®ion->bounds; RegionRect *contentRect = ®ion->contentRect; LayoutMode layoutMode = region->layoutMode; int requiresScale = layoutMode == LAYOUTMODE_CONTAIN || layoutMode == LAYOUTMODE_COVER; int requiresCrop = layoutMode == LAYOUTMODE_COVER; contentRect->x = bounds->x; contentRect->y = bounds->y; contentRect->w = bounds->w; contentRect->h = bounds->h; /* The region's aspect ratio is compared with its image's, which is then resized and optionally cropped. */ if(bounds->h > 0 && requiresScale) { int sourceW = (region->image != NULL) ? region->image->w : 1; int sourceH = (region->image != NULL) ? region->image->h : 1; double sourceAspectRatio = (double)sourceW / (double)sourceH; double boundsRatio = bounds->w / bounds->h; if((boundsRatio > sourceAspectRatio) == requiresCrop) { contentRect->h = bounds->w / sourceAspectRatio; contentRect->y += (bounds->h - contentRect->h) / 2; } else { contentRect->w = bounds->h * sourceAspectRatio; contentRect->x += (bounds->w - contentRect->w) / 2; } } region->dest.x = contentRect->x; region->dest.y = contentRect->y; region->dest.w = contentRect->w; region->dest.h = contentRect->h; } static void resizeRegion(Region *region) { resizeBounds(region); if(region->bounds.h > 0) { resizeContent(region); } } /* "Lazier" images are only stored in memory if they're onscreen and have an alpha greater than zero. */ static void updateLazierImage(Region *region) { Image *image = region->image; if(image == NULL || image->loadType != LOADTYPE_LAZIER) { return; } if(region->alphaTween.value <= 0 && image->texture != NULL) { Loader_UnloadImage(image); } else if(region->alphaTween.value > 0 && image->texture == NULL) { Loader_LoadImage(image); resizeRegion(region); } } static void updateRegionFade(Region *region) { Tween_Update(®ion->alphaTween, _totalTime); updateLazierImage(region); } static void renderRegion(Region *region, SDL_Renderer *renderer, int nudge) { Image *image = region->image; if(region->alphaTween.value > 0 && image != NULL) { SDL_SetTextureColorMod(image->texture, tintColor.r, tintColor.g, tintColor.b); SDL_SetTextureAlphaMod(image->texture, 0xFF * region->alphaTween.value); if(nudge && nudgeTween.value < 1) { /* While the nudge tween animates, the regions' render destionations are offset by the nudge X and Y, scaled by the tween value. */ nudgeRect.x = region->dest.x + windowRect.w * nudgeX * (1. - nudgeTween.value); nudgeRect.y = region->dest.y + windowRect.h * nudgeY * (1. - nudgeTween.value); nudgeRect.w = region->dest.w; nudgeRect.h = region->dest.h; SDL_RenderCopy(renderer, image->texture, NULL, &nudgeRect); } else { SDL_RenderCopy(renderer, image->texture, NULL, ®ion->dest); } } #ifdef DRAW_REGION_BORDERS { /* This cyan, 1-pixel "border texture" is stretched along the edge of the region to render a thin border around it. */ if(regionBorderTexture == NULL) { SDL_Surface *surface = SDL_CreateRGBSurface(0, 1, 1, 32, 0xFF0000, 0xFF00, 0xFF, 0); SDL_Rect rect = {0, 0, 1, 1}; SDL_FillRect(surface, &rect, 0xFFFFFF); regionBorderTexture = SDL_CreateTextureFromSurface(renderer, surface); SDL_FreeSurface(surface); } if(image != NULL) { SDL_SetTextureColorMod(regionBorderTexture, 0xFF, 0xFF, 0xFF); } else { SDL_SetTextureColorMod(regionBorderTexture, 0x72, 0xDE, 0xC2); } regionBorderDest.x = region->bounds.x; regionBorderDest.y = region->bounds.y; regionBorderDest.h = 1; regionBorderDest.w = region->bounds.w; SDL_RenderCopy(renderer, regionBorderTexture, NULL, ®ionBorderDest); regionBorderDest.y = region->bounds.y + region->bounds.h - 1; SDL_RenderCopy(renderer, regionBorderTexture, NULL, ®ionBorderDest); regionBorderDest.x = region->bounds.x; regionBorderDest.y = region->bounds.y; regionBorderDest.w = 1; regionBorderDest.h = region->bounds.h; SDL_RenderCopy(renderer, regionBorderTexture, NULL, ®ionBorderDest); regionBorderDest.x = region->bounds.x + region->bounds.w - 1; SDL_RenderCopy(renderer, regionBorderTexture, NULL, ®ionBorderDest); } #endif } void Stage_Init() { } void Stage_Quit() { int i; #ifdef DRAW_REGION_BORDERS if(regionBorderTexture != NULL) { SDL_DestroyTexture(regionBorderTexture); regionBorderTexture = NULL; } #endif for(i = 0; i < NumberOfRegions; i++) { regions[i].image = NULL; } } void Stage_SetImage(RegionName name, Image *image) { if(regions[name].image == image) { return; } /* "Lazy" images are only stored in memory if they're onscreen. */ if(regions[name].image != NULL && regions[name].image->loadType == LOADTYPE_LAZY) { Loader_UnloadImage(regions[name].image); } regions[name].image = image; if(image->loadType == LOADTYPE_LAZY && image->texture == NULL) { Loader_LoadImage(image); } resizeRegion(®ions[name]); } /* Sometimes, two images need to switch regions, such as when one background fades to reveal the next, and we don't want those images, lazy as they are, to be unloaded and reloaded. This function lets us swap them without unloading them. */ void Stage_SwapImages(RegionName name1, RegionName name2) { Image *image = regions[name1].image; regions[name1].image = regions[name2].image; regions[name2].image = image; } void Stage_Fade(RegionName name, double startAlpha, double endAlpha, double duration, double delay) { if(duration <= 0) { Stage_StopFade(name, 0); Stage_SetAlpha(name, endAlpha); } else { Region *region = ®ions[name]; Tween_Start(®ion->alphaTween, startAlpha, endAlpha, _totalTime + delay, duration, NULL); Tween_Update(®ion->alphaTween, _totalTime); updateLazierImage(region); } } void Stage_StopFade(RegionName name, int complete) { Region *region = ®ions[name]; Tween_Stop(®ion->alphaTween, complete); if(complete) { updateLazierImage(region); } } void Stage_SetAlpha(RegionName name, double alpha) { Stage_StopFade(name, 0); regions[name].alphaTween.value = alpha; updateLazierImage(®ions[name]); } void Stage_Tint(SDL_Color color, double duration, double delay) { tintGoalColor = color; if(duration <= 0) { Tween_Stop(&tintTween, 0); tintTween.value = 1; } else { Tween_Start(&tintTween, 0, 1, _totalTime + delay, duration, NULL); Tween_Update(&tintTween, _totalTime); } } void Stage_StopTint() { Tween_Stop(&tintTween, 1); tintGoalColor.r = 0xFF; tintGoalColor.g = 0xFF; tintGoalColor.b = 0xFF; } void Stage_Nudge(double x, double y, double duration) { if(duration <= 0) { return; } nudgeX = x; nudgeY = y; Tween_Start(&nudgeTween, 0, 1, _totalTime, duration, NULL); Tween_Update(&nudgeTween, _totalTime); } void Stage_Update(double totalTime, double deltaTime) { int i, tintOffset; (void)deltaTime; _totalTime = totalTime; Tween_Update(&nudgeTween, _totalTime); Tween_Update(&tintTween, _totalTime); tintOffset = 0xFF * (1.0 - tintTween.value); tintColor.r = tintGoalColor.r * tintTween.value + tintOffset; tintColor.g = tintGoalColor.g * tintTween.value + tintOffset; tintColor.b = tintGoalColor.b * tintTween.value + tintOffset; for(i = 0; i < NumberOfRegions; i++) { updateRegionFade(®ions[i]); } } void Stage_Resize(int windowW, int windowH) { int i; windowRect.w = windowW; windowRect.h = windowH; for(i = 0; i < NumberOfRegions; i++) { resizeRegion(®ions[i]); } } void Stage_Render(SDL_Renderer *renderer) { int i, nudge; for(i = 0; i < NumberOfRegions; i++) { nudge = i == BILLBOARD_NODE || regions[i].layoutMode == LAYOUTMODE_STRETCH_NODE; renderRegion(®ions[i], renderer, nudge); } } int Stage_HitTest(RegionName name, int x, int y) { RegionRect *rect = ®ions[name].contentRect; x -= rect->x; y -= rect->y; return x >= 0 && y >= 0 && x <= rect->w && y <= rect->h; } int Stage_PointNearBorder(int x, int y, int locus) { return x < locus || y < locus || x > windowRect.w - 2 * locus || y > windowRect.h - 2 * locus; }