Add UI cropping
This commit is contained in:
@@ -12,9 +12,6 @@
|
||||
#include "input/input.h"
|
||||
#include "log/log.h"
|
||||
#include "engine/engine.h"
|
||||
#include "display/shader/shaderunlit.h"
|
||||
#include "display/text/text.h"
|
||||
#include "display/spritebatch/spritebatch.h"
|
||||
|
||||
console_t CONSOLE;
|
||||
|
||||
@@ -63,20 +60,6 @@ void consoleUpdate(void) {
|
||||
}
|
||||
}
|
||||
|
||||
errorret_t consoleDraw(void) {
|
||||
if(!CONSOLE.visible) errorOk();
|
||||
|
||||
for(uint32_t i = 0; i < CONSOLE_HISTORY_MAX; i++) {
|
||||
errorChain(textDraw(
|
||||
0, FONT_DEFAULT.tileset->tileHeight * i,
|
||||
CONSOLE.line[i],
|
||||
COLOR_RED,
|
||||
&FONT_DEFAULT
|
||||
));
|
||||
}
|
||||
return spriteBatchFlush();
|
||||
}
|
||||
|
||||
void consoleDispose(void) {
|
||||
#ifdef DUSK_CONSOLE_POSIX
|
||||
threadMutexDispose(&CONSOLE.printMutex);
|
||||
|
||||
@@ -45,13 +45,6 @@ void consolePrint(const char_t *message, ...);
|
||||
*/
|
||||
void consoleUpdate(void);
|
||||
|
||||
/**
|
||||
* Renders the console history to the screen (UI space).
|
||||
*
|
||||
* @return The error return value.
|
||||
*/
|
||||
errorret_t consoleDraw(void);
|
||||
|
||||
/**
|
||||
* Disposes of the console.
|
||||
*/
|
||||
|
||||
@@ -13,6 +13,13 @@
|
||||
|
||||
screen_t SCREEN;
|
||||
|
||||
const screencropaspectinfo_t SCREEN_CROP_ASPECTS[SCREEN_CROP_ASPECT_COUNT] = {
|
||||
[SCREEN_CROP_ASPECT_4_3] = { .ratio = 4.0f / 3.0f },
|
||||
[SCREEN_CROP_ASPECT_3_2] = { .ratio = 3.0f / 2.0f },
|
||||
[SCREEN_CROP_ASPECT_16_10] = { .ratio = 16.0f / 10.0f },
|
||||
[SCREEN_CROP_ASPECT_16_9] = { .ratio = 16.0f / 9.0f },
|
||||
};
|
||||
|
||||
errorret_t screenInit() {
|
||||
memoryZero(&SCREEN, sizeof(screen_t));
|
||||
|
||||
@@ -53,10 +60,7 @@ errorret_t screenBind() {
|
||||
SCREEN.width = frameBufferGetWidth(FRAMEBUFFER_BOUND);
|
||||
SCREEN.height = frameBufferGetHeight(FRAMEBUFFER_BOUND);
|
||||
SCREEN.aspect = frameBufferGetAspect(FRAMEBUFFER_BOUND);
|
||||
SCREEN.scanX = 0;
|
||||
SCREEN.scanY = 0;
|
||||
SCREEN.scanWidth = SCREEN.width;
|
||||
SCREEN.scanHeight = SCREEN.height;
|
||||
screenUpdateScan();
|
||||
|
||||
// No needd for a framebuffer.
|
||||
#ifdef DUSK_DISPLAY_SIZE_DYNAMIC
|
||||
@@ -73,10 +77,7 @@ errorret_t screenBind() {
|
||||
SCREEN.width = SCREEN.fixedSize.width;
|
||||
SCREEN.height = SCREEN.fixedSize.height;
|
||||
SCREEN.aspect = (float_t)SCREEN.width / (float_t)SCREEN.height;
|
||||
SCREEN.scanX = 0;
|
||||
SCREEN.scanY = 0;
|
||||
SCREEN.scanWidth = SCREEN.width;
|
||||
SCREEN.scanHeight = SCREEN.height;
|
||||
screenUpdateScan();
|
||||
|
||||
if(SCREEN.framebufferReady) {
|
||||
// Is current framebuffer the correct size?
|
||||
@@ -114,10 +115,7 @@ errorret_t screenBind() {
|
||||
SCREEN.width = fbWidth;
|
||||
SCREEN.height = fbHeight;
|
||||
SCREEN.aspect = (float_t)SCREEN.width / (float_t)SCREEN.height;
|
||||
SCREEN.scanX = 0;
|
||||
SCREEN.scanY = 0;
|
||||
SCREEN.scanWidth = SCREEN.width;
|
||||
SCREEN.scanHeight = SCREEN.height;
|
||||
screenUpdateScan();
|
||||
|
||||
if(SCREEN.framebufferReady) {
|
||||
errorChain(frameBufferDispose(&SCREEN.framebuffer));
|
||||
@@ -148,10 +146,7 @@ errorret_t screenBind() {
|
||||
SCREEN.width = newFbWidth;
|
||||
SCREEN.height = newFbHeight;
|
||||
SCREEN.aspect = curFbAspect;
|
||||
SCREEN.scanX = 0;
|
||||
SCREEN.scanY = 0;
|
||||
SCREEN.scanWidth = SCREEN.width;
|
||||
SCREEN.scanHeight = SCREEN.height;
|
||||
screenUpdateScan();
|
||||
errorChain(frameBufferBind(&SCREEN.framebuffer));
|
||||
errorOk();
|
||||
}
|
||||
@@ -168,10 +163,7 @@ errorret_t screenBind() {
|
||||
SCREEN.width = newFbWidth;
|
||||
SCREEN.height = newFbHeight;
|
||||
SCREEN.aspect = (float_t)SCREEN.width / (float_t)SCREEN.height;
|
||||
SCREEN.scanX = 0;
|
||||
SCREEN.scanY = 0;
|
||||
SCREEN.scanWidth = SCREEN.width;
|
||||
SCREEN.scanHeight = SCREEN.height;
|
||||
screenUpdateScan();
|
||||
SCREEN.framebufferReady = true;
|
||||
|
||||
// Bind FB
|
||||
@@ -191,10 +183,7 @@ errorret_t screenBind() {
|
||||
SCREEN.width = newFbWidth;
|
||||
SCREEN.height = newFbHeight;
|
||||
SCREEN.aspect = (float_t)SCREEN.width / (float_t)SCREEN.height;
|
||||
SCREEN.scanX = 0;
|
||||
SCREEN.scanY = 0;
|
||||
SCREEN.scanWidth = SCREEN.width;
|
||||
SCREEN.scanHeight = SCREEN.height;
|
||||
screenUpdateScan();
|
||||
|
||||
if(fbWidth == newFbWidth && fbHeight == newFbHeight) {
|
||||
// No need to use framebuffer.
|
||||
@@ -241,10 +230,7 @@ errorret_t screenBind() {
|
||||
SCREEN.width = newFbWidth;
|
||||
SCREEN.height = newFbHeight;
|
||||
SCREEN.aspect = (float_t)SCREEN.width / (float_t)SCREEN.height;
|
||||
SCREEN.scanX = 0;
|
||||
SCREEN.scanY = 0;
|
||||
SCREEN.scanWidth = SCREEN.width;
|
||||
SCREEN.scanHeight = SCREEN.height;
|
||||
screenUpdateScan();
|
||||
|
||||
if(fbWidth == newFbWidth && fbHeight == newFbHeight) {
|
||||
// No need to use framebuffer.
|
||||
@@ -286,10 +272,7 @@ errorret_t screenBind() {
|
||||
float_t fbAspect = fbWidth / fbHeight;
|
||||
SCREEN.width = (int32_t)floorf(SCREEN.height * fbAspect);
|
||||
SCREEN.aspect = (float_t)SCREEN.width / (float_t)SCREEN.height;
|
||||
SCREEN.scanX = 0;
|
||||
SCREEN.scanY = 0;
|
||||
SCREEN.scanWidth = SCREEN.width;
|
||||
SCREEN.scanHeight = SCREEN.height;
|
||||
screenUpdateScan();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -423,6 +406,46 @@ errorret_t screenRender() {
|
||||
errorThrow("Invalid screen mode.");
|
||||
}
|
||||
|
||||
void screenUpdateScan(void) {
|
||||
SCREEN.scanX = 0;
|
||||
SCREEN.scanY = 0;
|
||||
SCREEN.scanWidth = SCREEN.width;
|
||||
SCREEN.scanHeight = SCREEN.height;
|
||||
|
||||
#ifdef DUSK_DISPLAY_SIZE_DYNAMIC
|
||||
// Find the smallest standard ratio >= the screen aspect.
|
||||
float_t targetRatio = -1.0f;
|
||||
for(int32_t i = 0; i < SCREEN_CROP_ASPECT_COUNT; i++) {
|
||||
if(SCREEN_CROP_ASPECTS[i].ratio >= SCREEN.aspect) {
|
||||
targetRatio = SCREEN_CROP_ASPECTS[i].ratio;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(targetRatio < 0.0f) {
|
||||
// Wider than all entries: cap at last (16:9).
|
||||
targetRatio = SCREEN_CROP_ASPECTS[
|
||||
SCREEN_CROP_ASPECT_COUNT - 1
|
||||
].ratio;
|
||||
}
|
||||
|
||||
if(targetRatio > SCREEN.aspect) {
|
||||
// Letterbox: target is wider, restrict height.
|
||||
int32_t targetH = (int32_t)floorf(
|
||||
(float_t)SCREEN.width / targetRatio
|
||||
);
|
||||
SCREEN.scanY = (SCREEN.height - targetH) / 2;
|
||||
SCREEN.scanHeight = targetH;
|
||||
} else if(targetRatio < SCREEN.aspect) {
|
||||
// Pillarbox: target is narrower, restrict width.
|
||||
int32_t targetW = (int32_t)floorf(
|
||||
(float_t)SCREEN.height * targetRatio
|
||||
);
|
||||
SCREEN.scanX = (SCREEN.width - targetW) / 2;
|
||||
SCREEN.scanWidth = targetW;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
errorret_t screenDispose() {
|
||||
#ifdef DUSK_DISPLAY_SIZE_DYNAMIC
|
||||
if(SCREEN.framebufferReady) {
|
||||
|
||||
@@ -17,6 +17,27 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
SCREEN_CROP_ASPECT_4_3,
|
||||
SCREEN_CROP_ASPECT_3_2,
|
||||
SCREEN_CROP_ASPECT_16_10,
|
||||
SCREEN_CROP_ASPECT_16_9,
|
||||
SCREEN_CROP_ASPECT_COUNT
|
||||
} screencropaspect_t;
|
||||
|
||||
typedef struct {
|
||||
float_t ratio;
|
||||
} screencropaspectinfo_t;
|
||||
|
||||
/**
|
||||
* Standard crop aspect ratios, sorted narrowest to widest.
|
||||
* screenUpdateScan() selects the smallest ratio >= the current
|
||||
* screen aspect; screens wider than the widest entry are capped
|
||||
* at the last entry (16:9).
|
||||
*/
|
||||
extern const screencropaspectinfo_t
|
||||
SCREEN_CROP_ASPECTS[SCREEN_CROP_ASPECT_COUNT];
|
||||
|
||||
typedef enum {
|
||||
SCREEN_MODE_BACKBUFFER,
|
||||
|
||||
@@ -116,6 +137,17 @@ errorret_t screenUnbind();
|
||||
*/
|
||||
errorret_t screenRender();
|
||||
|
||||
/**
|
||||
* Updates the scan area for the current screen dimensions.
|
||||
* Defaults to the full viewport. On dynamic displays, finds the
|
||||
* smallest SCREEN_CROP_ASPECTS entry whose ratio is >= the
|
||||
* screen aspect and centers a crop window of that ratio.
|
||||
* Screens narrower than the target are letterboxed; screens
|
||||
* wider than the target are pillarboxed. Screens wider than
|
||||
* all entries are pillarboxed to the last entry (16:9).
|
||||
*/
|
||||
void screenUpdateScan(void);
|
||||
|
||||
/**
|
||||
* Disposes the screen system.
|
||||
*
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
ui.c
|
||||
uiconsole.c
|
||||
uicrop.c
|
||||
uifps.c
|
||||
uielement.c
|
||||
uiframe.c
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "ui/uielement.h"
|
||||
#include "ui/uifullbox.h"
|
||||
#include "ui/uiloading.h"
|
||||
#include "ui/uicrop.h"
|
||||
#include "time/time.h"
|
||||
#include "log/log.h"
|
||||
|
||||
@@ -20,6 +21,7 @@ ui_t UI;
|
||||
|
||||
errorret_t uiInit(void) {
|
||||
memoryZero(&UI, sizeof(ui_t));
|
||||
uiCropInit();
|
||||
uiFullboxInit(&UI_FULLBOX_UNDER);
|
||||
uiFullboxInit(&UI_FULLBOX_OVER);
|
||||
uiLoadingInit();
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "uiconsole.h"
|
||||
#include "console/console.h"
|
||||
#include "display/screen/screen.h"
|
||||
#include "display/text/text.h"
|
||||
#include "display/spritebatch/spritebatch.h"
|
||||
|
||||
errorret_t uiConsoleDraw(void) {
|
||||
if(!CONSOLE.visible) errorOk();
|
||||
|
||||
float_t lineH = (float_t)FONT_DEFAULT.tileset->tileHeight;
|
||||
for(uint32_t i = 0; i < CONSOLE_HISTORY_MAX; i++) {
|
||||
errorChain(textDraw(
|
||||
(float_t)SCREEN.scanX,
|
||||
(float_t)SCREEN.scanY + lineH * (float_t)i,
|
||||
CONSOLE.line[i],
|
||||
COLOR_RED,
|
||||
&FONT_DEFAULT
|
||||
));
|
||||
}
|
||||
return spriteBatchFlush();
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "error/error.h"
|
||||
|
||||
/**
|
||||
* Renders the console history into the scan-safe area.
|
||||
* No-ops when the console is not visible.
|
||||
*
|
||||
* @return Any error that occurs.
|
||||
*/
|
||||
errorret_t uiConsoleDraw(void);
|
||||
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "uicrop.h"
|
||||
#include "display/screen/screen.h"
|
||||
#include "display/spritebatch/spritebatch.h"
|
||||
#include "display/shader/shaderunlit.h"
|
||||
#include "display/texture/texture.h"
|
||||
|
||||
uicrop_t UI_CROP;
|
||||
|
||||
void uiCropInit(void) {
|
||||
UI_CROP.color = COLOR_BLACK;
|
||||
}
|
||||
|
||||
errorret_t uiCropDraw(void) {
|
||||
if(
|
||||
SCREEN.scanX == 0 &&
|
||||
SCREEN.scanY == 0 &&
|
||||
SCREEN.scanWidth == SCREEN.width &&
|
||||
SCREEN.scanHeight == SCREEN.height
|
||||
) errorOk();
|
||||
|
||||
float_t x0 = (float_t)SCREEN.scanX;
|
||||
float_t y0 = (float_t)SCREEN.scanY;
|
||||
float_t x1 = (float_t)(SCREEN.scanX + SCREEN.scanWidth);
|
||||
float_t y1 = (float_t)(SCREEN.scanY + SCREEN.scanHeight);
|
||||
float_t w = (float_t)SCREEN.width;
|
||||
float_t h = (float_t)SCREEN.height;
|
||||
|
||||
spritebatchsprite_t sprites[4];
|
||||
int32_t count = 0;
|
||||
|
||||
if(SCREEN.scanX > 0) {
|
||||
sprites[count++] = (spritebatchsprite_t){
|
||||
.min = { 0.0f, 0.0f, 0.0f },
|
||||
.max = { x0, h, 0.0f },
|
||||
.uvMin = { 0.0f, 0.0f },
|
||||
.uvMax = { 1.0f, 1.0f }
|
||||
};
|
||||
}
|
||||
|
||||
if(SCREEN.scanX + SCREEN.scanWidth < SCREEN.width) {
|
||||
sprites[count++] = (spritebatchsprite_t){
|
||||
.min = { x1, 0.0f, 0.0f },
|
||||
.max = { w, h, 0.0f },
|
||||
.uvMin = { 0.0f, 0.0f },
|
||||
.uvMax = { 1.0f, 1.0f }
|
||||
};
|
||||
}
|
||||
|
||||
if(SCREEN.scanY > 0) {
|
||||
sprites[count++] = (spritebatchsprite_t){
|
||||
.min = { x0, 0.0f, 0.0f },
|
||||
.max = { x1, y0, 0.0f },
|
||||
.uvMin = { 0.0f, 0.0f },
|
||||
.uvMax = { 1.0f, 1.0f }
|
||||
};
|
||||
}
|
||||
|
||||
if(SCREEN.scanY + SCREEN.scanHeight < SCREEN.height) {
|
||||
sprites[count++] = (spritebatchsprite_t){
|
||||
.min = { x0, y1, 0.0f },
|
||||
.max = { x1, h, 0.0f },
|
||||
.uvMin = { 0.0f, 0.0f },
|
||||
.uvMax = { 1.0f, 1.0f }
|
||||
};
|
||||
}
|
||||
|
||||
if(count == 0) errorOk();
|
||||
|
||||
shadermaterial_t material = {
|
||||
.unlit = {
|
||||
.color = UI_CROP.color,
|
||||
.texture = &TEXTURE_WHITE
|
||||
}
|
||||
};
|
||||
errorChain(spriteBatchBuffer(sprites, count, &SHADER_UNLIT, material));
|
||||
return spriteBatchFlush();
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "error/error.h"
|
||||
#include "display/color.h"
|
||||
|
||||
typedef struct {
|
||||
color_t color;
|
||||
} uicrop_t;
|
||||
|
||||
extern uicrop_t UI_CROP;
|
||||
|
||||
/**
|
||||
* Initializes the crop bars to opaque black.
|
||||
*/
|
||||
void uiCropInit(void);
|
||||
|
||||
/**
|
||||
* Renders solid-color bars covering every area outside the
|
||||
* current scan-safe region. No-ops when the scan area equals
|
||||
* the full viewport.
|
||||
*
|
||||
* @return Any error that occurs.
|
||||
*/
|
||||
errorret_t uiCropDraw(void);
|
||||
@@ -7,15 +7,19 @@
|
||||
|
||||
#include "uielement.h"
|
||||
#include "assert/assert.h"
|
||||
#include "console/console.h"
|
||||
#include "ui/uifps.h"
|
||||
#include "engine/engine.h"
|
||||
#include "ui/uitextbox.h"
|
||||
#include "ui/uifullbox.h"
|
||||
#include "ui/uiloading.h"
|
||||
#include "ui/uiplayerpos.h"
|
||||
#include "ui/uicrop.h"
|
||||
#include "ui/uiconsole.h"
|
||||
|
||||
uielement_t UI_ELEMENTS[] = {
|
||||
// Crop bars: black bars outside the scan-safe area.
|
||||
{ .type = UI_ELEMENT_TYPE_NATIVE, .draw = uiCropDraw },
|
||||
|
||||
// Fullbox under: above scene, below system UI.
|
||||
{ .type = UI_ELEMENT_TYPE_NATIVE, .draw = uiFullboxUnderDraw },
|
||||
|
||||
@@ -27,7 +31,7 @@ uielement_t UI_ELEMENTS[] = {
|
||||
|
||||
|
||||
// These render above the fullbox overlay.
|
||||
{ .type = UI_ELEMENT_TYPE_NATIVE, .draw = consoleDraw },
|
||||
{ .type = UI_ELEMENT_TYPE_NATIVE, .draw = uiConsoleDraw },
|
||||
{ .type = UI_ELEMENT_TYPE_NATIVE, .draw = uiFPSDraw },
|
||||
{ .type = UI_ELEMENT_TYPE_NATIVE, .draw = uiPlayerPosDraw },
|
||||
{ .type = UI_ELEMENT_TYPE_NATIVE, .draw = uiLoadingDraw },
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
*/
|
||||
|
||||
#include "uiplayerpos.h"
|
||||
#include "display/screen/screen.h"
|
||||
#include "display/text/text.h"
|
||||
#include "display/spritebatch/spritebatch.h"
|
||||
#include "rpg/entity/entity.h"
|
||||
@@ -38,7 +39,10 @@ errorret_t uiPlayerPosDraw() {
|
||||
(int_t)chunkPos.z
|
||||
);
|
||||
|
||||
float_t y = (float_t)FONT_DEFAULT.tileset->tileHeight;
|
||||
errorChain(textDraw(0, y, text, COLOR_GREEN, &FONT_DEFAULT));
|
||||
float_t y = (float_t)SCREEN.scanY +
|
||||
(float_t)FONT_DEFAULT.tileset->tileHeight;
|
||||
errorChain(textDraw(
|
||||
(float_t)SCREEN.scanX, y, text, COLOR_GREEN, &FONT_DEFAULT
|
||||
));
|
||||
return spriteBatchFlush();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user