Add UI cropping

This commit is contained in:
2026-06-25 12:13:04 -05:00
parent 26fcaf6e75
commit c4969a36cc
12 changed files with 263 additions and 61 deletions
-17
View File
@@ -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);
-7
View File
@@ -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.
*/
+55 -32
View File
@@ -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) {
+32
View File
@@ -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.
*
+2
View File
@@ -7,6 +7,8 @@
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
ui.c
uiconsole.c
uicrop.c
uifps.c
uielement.c
uiframe.c
+2
View File
@@ -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();
+28
View File
@@ -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();
}
+17
View File
@@ -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);
+84
View File
@@ -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();
}
+30
View File
@@ -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);
+6 -2
View File
@@ -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 -2
View File
@@ -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();
}