Added text visibility ratio and worked on ui text.

This commit is contained in:
Kapendev 2024-12-18 08:37:48 +02:00
parent d47e02b1d3
commit e6263492b8
5 changed files with 112 additions and 100 deletions

14
TOUR.md
View file

@ -144,12 +144,13 @@ Draw options are used for configuring drawing parameters. The data structure loo
```d
struct DrawOptions {
Vec2 origin = Vec2(0.0f); /// The origin point of the drawn object.
Vec2 scale = Vec2(1.0f); /// The scale of the drawn object.
float rotation = 0.0f; /// The rotation of the drawn object, in degrees.
Color color = white; /// The color of the drawn object.
Hook hook = Hook.topLeft; /// An value representing the origin point of the drawn object when origin is set to zero.
Flip flip = Flip.none; /// An value representing flipping orientations.
float rotation = 0.0f; /// The rotation of the drawn object, in degrees.
Color color = white; /// The color of the drawn object.
Hook hook = Hook.topLeft; /// A value representing the origin point of the drawn object when origin is set to zero.
Flip flip = Flip.none; /// A value representing flipping orientations.
Alignment alignment = Alignment.left; /// A value represeting alignment orientations.
int alignmentWidth = 0; /// The width of the aligned object. Used as a hint and it is not enforced. Mostly used for text drawing.
float visibilityRatio = 1.0f; /// Controls the visibility ratio of the object, where 0.0 means fully hidden and 1.0 means fully visible. Mostly used for text drawing.
}
```
@ -161,6 +162,7 @@ this(Vec2 scale);
this(Color color);
this(Hook hook);
this(Flip flip);
this(Alignment alignment, int alignmentWidth = 0);
```
## Loading and Saving Resources

View file

@ -11,6 +11,7 @@ void ready() {
bool update(float dt) {
prepareUi();
setUiFocus(0);
setUiStartPoint(Vec2(8));
// Toggle the limit of the drag handle.
if (uiButton(Vec2(80, 30), "Limit: {}".format(handleOptions.dragLimit))) {

View file

@ -2,7 +2,7 @@
import parin;
auto buttonSize = Vec2(32);
auto buttonSize = Vec2(20);
void ready() {
lockResolution(320, 180);
@ -15,12 +15,12 @@ bool update(float dt) {
setUiStartPoint(Vec2(8));
// Create a layout for arranging subsequent UI items.
useUiLayout(Layout.h);
if (uiButton(buttonSize, "1")) println(1);
if (uiButton(buttonSize, "2")) println(2);
uiText(Vec2(70, buttonSize.y), "Button 1", UiButtonOptions(Alignment.left));
if (uiButton(buttonSize, "")) println(1);
// Create a new layout under the previous layout.
useUiLayout(Layout.h);
if (uiButton(buttonSize, "3")) println(3);
if (uiButton(buttonSize, "4")) println(4);
uiText(Vec2(70, buttonSize.y), "Button 22", UiButtonOptions(Alignment.left));
if (uiButton(buttonSize, "")) println(22);
return false;
}

View file

@ -36,10 +36,12 @@ Sz engineEnvArgsBufferLength;
IStr[64] engineDroppedFilePathsBuffer;
rl.FilePathList engineDroppedFilePathsDataBuffer;
/// A type representing layout orientations.
enum Layout : ubyte {
v, /// Vertical layout.
h, /// Horizontal layout.
/// A type representing flipping orientations.
enum Flip : ubyte {
none, /// No flipping.
x, /// Flipped along the X-axis.
y, /// Flipped along the Y-axis.
xy, /// Flipped along both X and Y axes.
}
/// A type representing alignment orientations.
@ -49,12 +51,10 @@ enum Alignment : ubyte {
right, /// Align to the right.
}
/// A type representing flipping orientations.
enum Flip : ubyte {
none, /// No flipping.
x, /// Flipped along the X-axis.
y, /// Flipped along the Y-axis.
xy, /// Flipped along both X and Y axes.
/// A type representing layout orientations.
enum Layout : ubyte {
v, /// Vertical layout.
h, /// Horizontal layout.
}
/// A type representing texture filtering modes.
@ -194,8 +194,10 @@ struct DrawOptions {
float rotation = 0.0f; /// The rotation of the drawn object, in degrees.
Color color = white; /// The color of the drawn object.
Hook hook = Hook.topLeft; /// A value representing the origin point of the drawn object when origin is set to zero.
Alignment alignment = Alignment.left; /// A value represeting alignment orientations.
Flip flip = Flip.none; /// A value representing flipping orientations.
Alignment alignment = Alignment.left; /// A value represeting alignment orientations.
int alignmentWidth = 0; /// The width of the aligned object. Used as a hint and it is not enforced. Mostly used for text drawing.
float visibilityRatio = 1.0f; /// Controls the visibility ratio of the object, where 0.0 means fully hidden and 1.0 means fully visible. Mostly used for text drawing.
@safe @nogc nothrow:
@ -219,15 +221,16 @@ struct DrawOptions {
this.hook = hook;
}
/// Initializes the options with the given alignment.
this(Alignment alignment) {
this.alignment = alignment;
}
/// Initializes the options with the given flip.
this(Flip flip) {
this.flip = flip;
}
/// Initializes the options with the given alignment.
this(Alignment alignment, int alignmentWidth = 0) {
this.alignment = alignment;
this.alignmentWidth = alignmentWidth;
}
}
/// Represents an identifier for a managed resource.
@ -1863,49 +1866,39 @@ float deltaWheel() {
Vec2 measureTextSize(Font font, IStr text, DrawOptions options = DrawOptions()) {
if (font.isEmpty || text.length == 0) return Vec2();
// NOTE: No idea what is happening here. Maybe I should try to make it look more like the drawText function.
auto result = Vec2();
auto tempByteCounter = 0; // Used to count longer text line num chars.
auto byteCounter = 0;
auto textWidth = 0.0f;
auto tempTextWidth = 0.0f; // Used to count longer text line width.
auto lineCodepointCount = 0;
auto maxLineCodepointCount = 0;
auto textWidth = 0;
auto maxTextWidth = 0;
auto textHeight = font.size;
auto letter = 0; // Current character.
auto index = 0; // Index position in texture font.
auto i = 0;
while (i < text.length) {
byteCounter += 1;
auto next = 0;
letter = rl.GetCodepointNext(&text[i], &next);
index = rl.GetGlyphIndex(font.data, letter);
i += next;
if (letter != '\n') {
if (font.data.glyphs[index].advanceX != 0) {
textWidth += font.data.glyphs[index].advanceX;
auto codepointIndex = 0;
while (codepointIndex < text.length) {
lineCodepointCount += 1;
auto codepointByteCount = 0;
auto codepoint = rl.GetCodepointNext(&text[codepointIndex], &codepointByteCount);
auto glyphIndex = rl.GetGlyphIndex(font.data, codepoint);
if (codepoint != '\n') {
if (font.data.glyphs[glyphIndex].advanceX) {
textWidth += font.data.glyphs[glyphIndex].advanceX;
} else {
textWidth += font.data.recs[index].width + font.data.glyphs[index].offsetX;
textWidth += cast(int) (font.data.recs[glyphIndex].width + font.data.glyphs[glyphIndex].offsetX);
}
} else {
if (tempTextWidth < textWidth) {
tempTextWidth = textWidth;
}
byteCounter = 0;
if (maxTextWidth < textWidth) maxTextWidth = textWidth;
lineCodepointCount = 0;
textWidth = 0;
textHeight += font.lineSpacing;
}
if (tempByteCounter < byteCounter) {
tempByteCounter = byteCounter;
}
if (maxLineCodepointCount < lineCodepointCount) maxLineCodepointCount = lineCodepointCount;
codepointIndex += codepointByteCount;
}
if (tempTextWidth < textWidth) {
tempTextWidth = textWidth;
}
result.x = floor(tempTextWidth * options.scale.x + ((tempByteCounter - 1) * font.runeSpacing * options.scale.x));
result.y = floor(textHeight * options.scale.y);
return result;
if (maxTextWidth < textWidth) maxTextWidth = textWidth;
if (maxTextWidth < options.alignmentWidth) maxTextWidth = options.alignmentWidth;
return Vec2(
floor(maxTextWidth * options.scale.x + ((maxLineCodepointCount - 1) * font.runeSpacing * options.scale.x)),
floor(textHeight * options.scale.y),
);
}
/// Measures the size of the specified text when rendered with the given font and draw options.
@ -2281,21 +2274,28 @@ void drawText(Font font, IStr text, Vec2 position, DrawOptions options = DrawOpt
// Get some info about the text.
linesBuffer.clear();
linesWidthBuffer.clear();
auto textHeight = font.size;
auto maxLineWidth = 0;
auto lineStartIndex = 0;
foreach (i, c; text) {
if (c == '\n' || i == text.length - 1) {
linesBuffer.append(text[lineStartIndex .. i + (c != '\n')]);
linesWidthBuffer.append(cast(short) measureTextSize(font, linesBuffer[$ - 1]).x);
auto codepointCount = 0;
auto codepointIndex = 0;
while (codepointIndex < text.length) {
codepointCount += 1;
auto codepointByteCount = 0;
auto codepoint = rl.GetCodepointNext(&text[codepointIndex], &codepointByteCount);
if (codepoint == '\n' || codepointIndex == text.length - codepointByteCount) {
linesBuffer.append(text[lineStartIndex .. codepointIndex + (codepoint != '\n')]);
linesWidthBuffer.append(cast(short) (measureTextSize(font, linesBuffer[$ - 1]).x));
if (maxLineWidth < linesWidthBuffer[$ - 1]) maxLineWidth = linesWidthBuffer[$ - 1];
lineStartIndex = cast(int) i + 1;
lineStartIndex = cast(int) (codepointIndex + 1);
if (codepoint == '\n') textHeight += font.lineSpacing;
}
codepointIndex += codepointByteCount;
}
lineStartIndex = 0;
if (maxLineWidth < options.alignmentWidth) maxLineWidth = options.alignmentWidth;
// Prepare the the text for drawing.
auto textSize = measureTextSize(font, text);
auto origin = Rect(textSize).origin(options.hook);
auto origin = Rect(maxLineWidth, textHeight).origin(options.hook);
rl.rlPushMatrix();
if (isPixelSnapped || isPixelPerfect) {
rl.rlTranslatef(floor(position.x), floor(position.y), 0.0f);
@ -2306,19 +2306,21 @@ void drawText(Font font, IStr text, Vec2 position, DrawOptions options = DrawOpt
rl.rlScalef(options.scale.x, options.scale.y, 1.0f);
rl.rlTranslatef(floor(-origin.x), floor(-origin.y), 0.0f);
// Draw the text.
auto textOffsetY = 0; // Offset between lines (on linebreak '\n').
codepointIndex = 0;
auto maxCodepointCount = cast(int) (codepointCount * clamp(options.visibilityRatio, 0.0f, 1.0f));
auto textOffsetY = 0; // Offset between lines.
foreach (i, line; linesBuffer) {
auto textOffsetX = 0; // Offset X to next character to draw.
auto textOffsetX = 0; // Offset betweem characters.
if (options.alignment == Alignment.center) {
textOffsetX = maxLineWidth / 2 - linesWidthBuffer[i] / 2;
} else if (options.alignment == Alignment.right) {
textOffsetX = maxLineWidth - linesWidthBuffer[i];
}
auto codepointIndex = 0;
while (codepointIndex < line.length) {
// Get next codepoint from byte string and glyph index in font.
auto lineCodepointIndex = 0;
while (lineCodepointIndex < line.length) {
if (codepointIndex >= maxCodepointCount) break; // This does break the codepoint index, but who cares.
auto codepointByteCount = 0;
auto codepoint = rl.GetCodepointNext(&line[codepointIndex], &codepointByteCount);
auto codepoint = rl.GetCodepointNext(&line[lineCodepointIndex], &codepointByteCount);
auto glyphIndex = rl.GetGlyphIndex(font.data, codepoint);
if (codepoint != ' ' && codepoint != '\t') {
auto runeOptions = DrawOptions();
@ -2330,11 +2332,13 @@ void drawText(Font font, IStr text, Vec2 position, DrawOptions options = DrawOpt
} else {
textOffsetX += cast(int) (font.data.recs[glyphIndex].width) + font.runeSpacing;
}
// Move text bytes counter to next codepoint.
lineCodepointIndex += codepointByteCount;
codepointIndex += codepointByteCount;
}
textOffsetY += font.lineSpacing;
codepointIndex += 1; // Adding the new line.
}
codepointIndex -= text[$ - 1] != '\n'; // Removing one extra new line.
rl.rlPopMatrix();
}

View file

@ -6,13 +6,9 @@
// Version: v0.0.29
// ---
// TODO: Clean maybe the UiState struct and prepareUi func.
// TODO: Think about overlapping UI items.
// TODO: Look at the text alignment code again. Maybe add a clamp so text gets all the button always and stuff.
// TODO: Add way to get item point for some stuff. This is nice when making lists.
// TODO: Add focus style.
// TODO: Add way to align text in buttons.
// TODO: Look at the API again.
// TODO: Test the ui code and think how to make it better while working on real stuff.
/// The `ui` module functions as a immediate mode UI library.
module parin.ui;
@ -50,6 +46,8 @@ struct UiButtonOptions {
UiDragLimit dragLimit;
Vec2 dragLimitX = Vec2(-100000.0f, 100000.0f);
Vec2 dragLimitY = Vec2(-100000.0f, 100000.0f);
Alignment textAlignment = Alignment.center;
short textAlignmentMargin = 4;
@safe @nogc nothrow:
@ -60,6 +58,11 @@ struct UiButtonOptions {
this(UiDragLimit dragLimit) {
this.dragLimit = dragLimit;
}
this(Alignment textAlignment, short textAlignmentMargin = 4) {
this.textAlignment = textAlignment;
this.textAlignmentMargin = textAlignmentMargin;
}
}
struct UiState {
@ -70,7 +73,7 @@ struct UiState {
Vec2 viewportPoint;
Vec2 viewportSize;
Vec2 viewportScale = Vec2(1);
Vec2 viewportScale = Vec2(1.0f);
Vec2 startPoint;
short margin;
Layout layout;
@ -352,12 +355,18 @@ void drawUiButton(Vec2 size, IStr text, Vec2 point, bool isHot, bool isActive, U
} else {
drawRect(area, options.idleColor);
}
auto textPoint = area.centerPoint;
if (options.textAlignment == Alignment.left) textPoint.x += options.textAlignmentMargin;
else if (options.textAlignment == Alignment.right) textPoint.x -= options.textAlignmentMargin;
auto textOptions = DrawOptions(options.textAlignment, cast(int) (size.x));
textOptions.hook = Hook.center;
if (options.isDisabled) {
auto tempOptions = DrawOptions(Hook.center);
tempOptions.color.a = defaultUiAlpha / 2;
drawText(options.font, text, area.centerPoint, tempOptions);
textOptions.color.a = defaultUiAlpha / 2;
drawText(options.font, text, textPoint, textOptions);
} else {
drawText(options.font, text, area.centerPoint, DrawOptions(Hook.center));
drawText(options.font, text, textPoint, textOptions);
}
}
@ -424,21 +433,17 @@ bool uiDragHandle(Vec2 size, ref Vec2 point, UiButtonOptions options = UiButtonO
}
}
void uiTexture(Texture texture, UiButtonOptions options = UiButtonOptions()) {
auto point = uiState.layoutStartPoint + uiState.layoutStartPointOffest;
drawRect(Rect(point, texture.size), black);
drawTexture(texture, point);
updateUiState(point, texture.size, false, false, false);
}
void uiTexture(TextureId texture, UiButtonOptions options = UiButtonOptions()) {
uiTexture(texture.get(), options);
}
void uiText(IStr text, UiButtonOptions options = UiButtonOptions()) {
void uiText(Vec2 size, IStr text, UiButtonOptions options = UiButtonOptions()) {
if (options.font.isEmpty) options.font = engineFont;
auto point = uiState.layoutStartPoint + uiState.layoutStartPointOffest;
auto size = measureTextSize(options.font, text);
drawText(options.font, text, point);
auto area = Rect(point, size);
auto textPoint = area.centerPoint;
if (options.textAlignment == Alignment.left) textPoint.x += options.textAlignmentMargin;
else if (options.textAlignment == Alignment.right) textPoint.x -= options.textAlignmentMargin;
auto textOptions = DrawOptions(options.textAlignment, cast(int) (size.x));
textOptions.hook = Hook.center;
drawText(options.font, text, textPoint, textOptions);
updateUiState(point, size, false, false, false);
}