diff --git a/examples/intro/scene.d b/examples/intro/scene.d deleted file mode 100644 index 0e4cbc0..0000000 --- a/examples/intro/scene.d +++ /dev/null @@ -1,84 +0,0 @@ -/// This example shows how to use the scene manager of Parin. - -import parin; -import parin.scene; - -auto sceneManager = SceneManager(); - -// The first scene. -struct Scene1 { - mixin extendScene; - - float counter = 0; - - void ready() { - // The scene manager generates a unique tag for each scene. - printfln("Entering scene 1 with tag {}.", sceneManager.tag); - setBackgroundColor(gray1); - } - - bool update(float dt) { - if (Keyboard.space.isPressed) { - sceneManager.enter!Scene2(); - } - - counter += 5 * dt; - - drawDebugText("Press space to change scene.", resolution * Vec2(0.5), DrawOptions(Hook.center)); - drawDebugText("Scene 1\nCounter: {}".format(cast(int) counter), Vec2(8)); - return false; - } - - void finish() { - // The scene tag can be used to free all resources associated with the current scene. - // In this example, no resources will be freed as there are none to free. - printfln("Exiting scene 1 with tag {}.", sceneManager.tag); - freeResources(sceneManager.tag); - } -} - -// The second scene. -struct Scene2 { - mixin extendScene; - - void ready() { - // The scene manager generates a unique tag for each scene. - printfln("Entering scene 2 with tag {}.", sceneManager.tag); - setBackgroundColor(gray2); - } - - bool update(float dt) { - if (Keyboard.space.isPressed) { - sceneManager.enter!Scene1(); - } - - drawDebugText("Press space to change scene.", resolution * Vec2(0.5), DrawOptions(Hook.center)); - drawDebugText("Scene 2\nNo counter here.", Vec2(8)); - return false; - } - - void finish() { - // The scene tag can be used to free all resources associated with the current scene. - // In this example, no resources will be freed as there are none to free. - printfln("Exiting scene 2 with tag {}.", sceneManager.tag); - freeResources(sceneManager.tag); - } -} - -void ready() { - lockResolution(320, 180); - // Enter the first scene. This will call the ready function of that scene. - sceneManager.enter!Scene1(); -} - -bool update(float dt) { - // Update the current scene. - return sceneManager.update(dt); -} - -void finish() { - // Free the scene manager. This will call the finish function of the current scene. - sceneManager.free(); -} - -mixin runGame!(ready, update, finish); diff --git a/source/parin/engine.d b/source/parin/engine.d index 1ca7a5f..98b6eb2 100644 --- a/source/parin/engine.d +++ b/source/parin/engine.d @@ -6,7 +6,6 @@ // Version: v0.0.29 // --- -// TODO: Add way to align text. I neeed this for the UI. // TODO: Test the resource loading code. // TODO: Think about the sound API. // TODO: Make sounds loop based on a variable and not on the file type. @@ -44,7 +43,7 @@ enum Layout : ubyte { } /// A type representing alignment orientations. -enum Align : ubyte { +enum Alignment : ubyte { left, /// Align to the left. center, /// Align to the center. right, /// Align to the right. @@ -190,12 +189,13 @@ enum Gamepad : ubyte { /// A structure containing options for configuring drawing parameters. 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; /// 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. + 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; /// 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. @safe @nogc nothrow: @@ -219,6 +219,11 @@ 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; @@ -976,13 +981,13 @@ struct EngineState { @safe @nogc nothrow: void free() { - debug { - println("Resources that will be freed automatically:"); - println(" Text count: ", resources.texts.length != 0 ? resources.texts.length - 1 : 0); - println(" Texture count: ", resources.textures.length != 0 ? resources.textures.length - 1 : 0); - println(" Font count: ", resources.fonts.length != 0 ? resources.fonts.length - 1 : 0); - println(" Sound count: ", resources.sounds.length != 0 ? resources.sounds.length - 1 : 0); - } + // debug { + // println("Resources that will be freed automatically:"); + // println(" Text count: ", resources.texts.length != 0 ? resources.texts.length - 1 : 0); + // println(" Texture count: ", resources.textures.length != 0 ? resources.textures.length - 1 : 0); + // println(" Font count: ", resources.fonts.length != 0 ? resources.fonts.length - 1 : 0); + // println(" Sound count: ", resources.sounds.length != 0 ? resources.sounds.length - 1 : 0); + // } viewport.free(); resources.free(); tempText.free(); @@ -1858,6 +1863,8 @@ 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; @@ -2266,9 +2273,29 @@ void drawRune(FontId font, dchar rune, Vec2 position, DrawOptions options = Draw /// Draws the specified text with the given font at the given position using the provided draw options. @trusted void drawText(Font font, IStr text, Vec2 position, DrawOptions options = DrawOptions()) { + static linesBuffer = FixedList!(IStr, 128)(); + static linesWidthBuffer = FixedList!(short, 128)(); + if (font.isEmpty || text.length == 0) return; - // TODO: Make it work with negative scale values. - auto origin = Rect(measureTextSize(font, text)).origin(options.hook); + + // Get some info about the text. + linesBuffer.clear(); + linesWidthBuffer.clear(); + 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); + if (maxLineWidth < linesWidthBuffer[$ - 1]) maxLineWidth = linesWidthBuffer[$ - 1]; + lineStartIndex = cast(int) i + 1; + } + } + lineStartIndex = 0; + + // Prepare the the text for drawing. + auto textSize = measureTextSize(font, text); + auto origin = Rect(textSize).origin(options.hook); rl.rlPushMatrix(); if (isPixelSnapped || isPixelPerfect) { rl.rlTranslatef(floor(position.x), floor(position.y), 0.0f); @@ -2278,31 +2305,35 @@ void drawText(Font font, IStr text, Vec2 position, DrawOptions options = DrawOpt rl.rlRotatef(options.rotation, 0.0f, 0.0f, 1.0f); rl.rlScalef(options.scale.x, options.scale.y, 1.0f); rl.rlTranslatef(floor(-origin.x), floor(-origin.y), 0.0f); - auto textOffsetY = 0.0f; // Offset between lines (on linebreak '\n'). - auto textOffsetX = 0.0f; // Offset X to next character to draw. - auto i = 0; - while (i < text.length) { - // Get next codepoint from byte string and glyph index in font. - auto codepointByteCount = 0; - auto codepoint = rl.GetCodepointNext(&text[i], &codepointByteCount); - auto index = rl.GetGlyphIndex(font.data, codepoint); - if (codepoint == '\n') { - textOffsetY += font.lineSpacing; - textOffsetX = 0.0f; - } else { + // Draw the text. + auto textOffsetY = 0; // Offset between lines (on linebreak '\n'). + foreach (i, line; linesBuffer) { + auto textOffsetX = 0; // Offset X to next character to draw. + 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 codepointByteCount = 0; + auto codepoint = rl.GetCodepointNext(&line[codepointIndex], &codepointByteCount); + auto glyphIndex = rl.GetGlyphIndex(font.data, codepoint); if (codepoint != ' ' && codepoint != '\t') { auto runeOptions = DrawOptions(); runeOptions.color = options.color; - drawRune(font, codepoint, Vec2(textOffsetX, textOffsetY), runeOptions); + rl.DrawTextCodepoint(font.data, codepoint, rl.Vector2(textOffsetX, textOffsetY), font.size, options.color.toRl()); } - if (font.data.glyphs[index].advanceX == 0) { - textOffsetX += font.data.recs[index].width + font.runeSpacing; + if (font.data.glyphs[glyphIndex].advanceX) { + textOffsetX += font.data.glyphs[glyphIndex].advanceX + font.runeSpacing; } else { - textOffsetX += font.data.glyphs[index].advanceX + font.runeSpacing; + textOffsetX += cast(int) (font.data.recs[glyphIndex].width) + font.runeSpacing; } + // Move text bytes counter to next codepoint. + codepointIndex += codepointByteCount; } - // Move text bytes counter to next codepoint. - i += codepointByteCount; + textOffsetY += font.lineSpacing; } rl.rlPopMatrix(); } diff --git a/source/parin/scene.d b/source/parin/scene.d deleted file mode 100644 index 98c08bb..0000000 --- a/source/parin/scene.d +++ /dev/null @@ -1,83 +0,0 @@ -// --- -// Copyright 2024 Alexandros F. G. Kapretsos -// SPDX-License-Identifier: MIT -// Email: alexandroskapretsos@gmail.com -// Project: https://github.com/Kapendev/parin -// Version: v0.0.29 -// --- - -// NOTE(Kapendev): I am not a fan of this module and I would remove it, but maybe someone is using it. -// TODO: Update all the doc comments here. - -/// The `scene` module provides a simple scene manager. -module parin.scene; - -import stdc = joka.stdc; -import joka.types; - -@safe: - -struct Scene { - void delegate() @trusted ready; - bool delegate(float dt) @trusted update; - void delegate() @trusted finish; -} - -struct SceneManager { - Scene* scene; - Scene* nextScene; - Sz tag; - - @trusted: - - void enter(T)() { - if (nextScene) return; - auto temp = cast(T*) stdc.malloc(T.sizeof); - *temp = T(); - temp.prepare(); - nextScene = cast(Scene*) temp; - } - - bool update(float dt) { - if (nextScene) { - if (scene) { - if (scene.finish) scene.finish(); - stdc.free(scene); - } - scene = nextScene; - nextScene = null; - tag = (tag + 1) % Sz.max; - if (tag == 0) tag = 1; - if (scene.ready) scene.ready(); - if (scene.update) return scene.update(dt); - return true; - } - if (scene && scene.update) return scene.update(dt); - return true; - } - - void free() { - if (scene) { - if (scene.finish) scene.finish(); - stdc.free(scene); - scene = null; - } - if (nextScene) { - stdc.free(nextScene); - nextScene = null; - } - tag = 0; - } -} - -mixin template extendScene() { - Scene base; - - @trusted - void prepare() { - auto base = mixin("&", this.tupleof[0].stringof); - base.ready = cast(void delegate() @trusted) &this.ready; - base.update = cast(bool delegate(float dt) @trusted) &this.update; - base.finish = cast(void delegate() @trusted) &this.finish; - } -} diff --git a/source/parin/ui.d b/source/parin/ui.d index dadb3bc..73d920e 100644 --- a/source/parin/ui.d +++ b/source/parin/ui.d @@ -7,6 +7,7 @@ // --- // TODO: Clean maybe the UiState struct and prepareUi func. +// TODO: Think about overlapping UI items. // 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. @@ -67,7 +68,6 @@ struct UiState { Gamepad gamepadClickAction = Gamepad.a; bool isActOnPress; - Vec2 mousePressedPoint; Vec2 viewportPoint; Vec2 viewportSize; Vec2 viewportScale = Vec2(1); @@ -78,6 +78,7 @@ struct UiState { Vec2 layoutStartPointOffest; Vec2 layoutMaxItemSize; + Vec2 mousePressedPoint; Vec2 itemDragOffset; Vec2 itemPoint; Vec2 itemSize;