mirror of
https://github.com/Kapendev/parin.git
synced 2025-04-25 20:49:57 +03:00
Fixed ui bugs and made examples better.
This commit is contained in:
parent
381d99461b
commit
25acd69a84
6 changed files with 100 additions and 70 deletions
|
@ -1,4 +1,12 @@
|
|||
# Examples
|
||||
|
||||
This folder provides example projects to help you get started.
|
||||
|
||||
> [!NOTE]
|
||||
> For examples that use textures, the [atlas.png](atlas.png) file must be downloaded and saved in the project's assets folder.
|
||||
> If an example uses textures,
|
||||
> be sure to download the [atlas.png](atlas.png) file and place it in the project's assets folder.
|
||||
|
||||
## Example Categories
|
||||
- [Intro](intro): Basic examples to get familiar with Parin.
|
||||
- [Games](games): Examples focused on making simple games with Parin.
|
||||
- [UI](ui): Examples demonstrating how to use the Parin UI toolkit.
|
||||
|
|
|
@ -1,18 +1,17 @@
|
|||
/// This example shows how to create a simple game with Parin.
|
||||
/// This example shows how to create a simple collect-the-coins game with Parin.
|
||||
|
||||
import parin;
|
||||
|
||||
// The game variables.
|
||||
auto player = Rect(16, 16);
|
||||
auto playerSpeed = 120;
|
||||
auto coins = SparseList!Rect();
|
||||
auto coinSize = Vec2(8);
|
||||
auto maxCoinCount = 8;
|
||||
|
||||
void ready() {
|
||||
lockResolution(320, 180);
|
||||
|
||||
// Place the player and create the coins. Every coin will have a random starting position.
|
||||
// Place the player at the center of the window.
|
||||
player.position = resolution * Vec2(0.5);
|
||||
// Create the coins. Every coin will have a random starting position.
|
||||
auto coinSize = Vec2(8);
|
||||
foreach (i; 0 .. maxCoinCount) {
|
||||
auto minPosition = Vec2(0, 40);
|
||||
auto maxPosition = resolution - coinSize - minPosition;
|
||||
|
@ -27,24 +26,17 @@ void ready() {
|
|||
|
||||
bool update(float dt) {
|
||||
// Move the player.
|
||||
auto playerDirection = Vec2();
|
||||
if (Keyboard.left.isDown || 'a'.isDown) playerDirection.x = -1;
|
||||
if (Keyboard.right.isDown || 'd'.isDown) playerDirection.x = 1;
|
||||
if (Keyboard.up.isDown || 'w'.isDown) playerDirection.y = -1;
|
||||
if (Keyboard.down.isDown || 's'.isDown) playerDirection.y = 1;
|
||||
player.position += playerDirection * Vec2(playerSpeed * dt);
|
||||
|
||||
// Check if the player is touching some coins and remove those coins.
|
||||
auto playerDirection = Vec2(
|
||||
Keyboard.right.isDown - Keyboard.left.isDown,
|
||||
Keyboard.down.isDown - Keyboard.up.isDown,
|
||||
);
|
||||
player.position += playerDirection * Vec2(120 * dt);
|
||||
// Check if the player is touching coins and remove them.
|
||||
foreach (id; coins.ids) {
|
||||
if (coins[id].hasIntersection(player)) {
|
||||
coins.remove(id);
|
||||
}
|
||||
if (coins[id].hasIntersection(player)) coins.remove(id);
|
||||
}
|
||||
|
||||
// Draw the game.
|
||||
foreach (coin; coins.items) {
|
||||
drawRect(coin);
|
||||
}
|
||||
foreach (coin; coins.items) drawRect(coin);
|
||||
drawRect(player);
|
||||
if (coins.length == 0) {
|
||||
drawDebugText("You collected all the coins!", Vec2(8));
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
/// This example shows how to create a pong-like game with Parin.
|
||||
|
||||
import parin;
|
||||
|
||||
// The game variables.
|
||||
auto gameCounter = 0;
|
||||
|
||||
auto paddle1 = Rect(2, 25);
|
||||
auto paddle2 = Rect(2, 25);
|
||||
|
||||
auto ball = Rect(5, 5);
|
||||
auto ballDirection = Vec2(1, 1);
|
||||
auto ballSpeed = 120;
|
||||
|
@ -21,61 +19,52 @@ void ready() {
|
|||
ball.position = center;
|
||||
}
|
||||
|
||||
// The objects in this game are centered.
|
||||
// This means that rectangle data is divided into 2 parts, normal and centered.
|
||||
// A normal rectangle holds the position of an object.
|
||||
// A centered rectangle is used for collision checking and drawing.
|
||||
bool update(float dt) {
|
||||
// The objects in this game are centered.
|
||||
// This means that rectangle data is divided into 2 parts, normal and centered.
|
||||
// A normal rectangle holds the position of an object.
|
||||
// A centered rectangle is used for collision checking and drawing.
|
||||
|
||||
// Move the ball.
|
||||
ball.position += ballDirection * Vec2(ballSpeed * dt);
|
||||
// Check if the ball exited the screen from the left or right side.
|
||||
if (ball.centerArea.leftPoint.x < 0) {
|
||||
ball.position = resolution * Vec2(0.5);
|
||||
paddle1.position.y = resolutionHeight * 0.5;
|
||||
paddle2.position.y = resolutionHeight * 0.5;
|
||||
gameCounter = 0;
|
||||
} else if (ball.centerArea.rightPoint.x > resolutionWidth) {
|
||||
if (ball.centerArea.leftPoint.x < 0 || ball.centerArea.rightPoint.x > resolutionWidth) {
|
||||
ball.position = resolution * Vec2(0.5);
|
||||
paddle1.position.y = resolutionHeight * 0.5;
|
||||
paddle2.position.y = resolutionHeight * 0.5;
|
||||
gameCounter = 0;
|
||||
}
|
||||
// Check if the ball exited the screen from the top or bottom side.
|
||||
if (ball.centerArea.topPoint.y < 0) {
|
||||
if (ball.centerArea.topPoint.y < 0 || ball.centerArea.bottomPoint.y > resolutionHeight) {
|
||||
ballDirection.y *= -1;
|
||||
ball.position.y = ball.size.y * 0.5;
|
||||
} else if (ball.centerArea.bottomPoint.y > resolutionHeight) {
|
||||
ballDirection.y *= -1;
|
||||
ball.position.y = resolutionHeight - ball.size.y * 0.5;
|
||||
}
|
||||
|
||||
// Move paddle1.
|
||||
paddle1.position.y = clamp(paddle1.position.y + wasd.y * ballSpeed * dt, paddle1.size.y * 0.5f, resolutionHeight - paddle1.size.y * 0.5f);
|
||||
paddle1.position.y = clamp(
|
||||
paddle1.position.y + wasd.y * ballSpeed * dt,
|
||||
paddle1.size.y * 0.5f,
|
||||
resolutionHeight - paddle1.size.y * 0.5f
|
||||
);
|
||||
// Move paddle2.
|
||||
auto paddle2Target = ball.position.y;
|
||||
if (ballDirection.x < 1) {
|
||||
paddle2Target = paddle2.position.y;
|
||||
}
|
||||
paddle2.position.y = paddle2.position.y.moveTo(clamp(paddle2Target, paddle2.size.y * 0.5f, resolutionHeight - paddle2.size.y * 0.5f), ballSpeed * dt);
|
||||
auto paddle2Target = ballDirection.x < 1 ? paddle2.position.y : ball.position.y;
|
||||
paddle2.position.y = paddle2.position.y.moveTo(
|
||||
clamp(paddle2Target, paddle2.size.y * 0.5f, resolutionHeight - paddle2.size.y * 0.5f),
|
||||
ballSpeed * dt
|
||||
);
|
||||
|
||||
// Check for paddle and ball collisions.
|
||||
// Check for collisions.
|
||||
if (paddle1.centerArea.hasIntersection(ball.centerArea)) {
|
||||
ballDirection.x *= -1;
|
||||
ball.position.x = paddle1.centerArea.rightPoint.x + ball.size.x * 0.5;
|
||||
gameCounter += 1;
|
||||
}
|
||||
if (paddle2.centerArea.hasIntersection(ball.centerArea)) {
|
||||
ballDirection.x *= -1;
|
||||
ball.position.x = paddle2.centerArea.leftPoint.x - ball.size.x * 0.5;
|
||||
gameCounter += 1;
|
||||
}
|
||||
|
||||
// Draw the objects.
|
||||
// Draw the game.
|
||||
drawRect(ball.centerArea);
|
||||
drawRect(paddle1.centerArea);
|
||||
drawRect(paddle2.centerArea);
|
||||
// Draw the counter.
|
||||
auto textOptions = DrawOptions(Hook.center);
|
||||
textOptions.scale = Vec2(2);
|
||||
drawDebugText("{}".format(gameCounter), Vec2(resolutionWidth * 0.5, 16), textOptions);
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
/// This example shows how to use the drag handle.
|
||||
|
||||
// TODO: There is a small bug with overlapping UI items. Fix it.
|
||||
|
||||
import parin;
|
||||
|
||||
auto handlePosition = Vec2(120, 60);
|
||||
|
@ -18,7 +16,7 @@ bool update(float dt) {
|
|||
if (handleOptions.dragLimit) handleOptions.dragLimit = UiDragLimit.none;
|
||||
else handleOptions.dragLimit = UiDragLimit.viewport;
|
||||
}
|
||||
// Create the drag handle and return true if it is dragged.
|
||||
// Create the drag handle and print if it is dragged.
|
||||
if (uiDragHandle(Vec2(60), handlePosition, handleOptions)) {
|
||||
println(handlePosition);
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ void ready() {
|
|||
bool update(float dt) {
|
||||
// Set the starting point for subsequent UI items.
|
||||
setUiStartPoint(Vec2(8));
|
||||
// Create a button and return true if it is clicked.
|
||||
// Create a button and print if it is clicked.
|
||||
if (uiButton(Vec2(80, 30), buttonText)) {
|
||||
println(buttonText);
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
// Version: v0.0.29
|
||||
// ---
|
||||
|
||||
// TODO: Try to fix the ui item overlaping bug maybe.
|
||||
// TODO: Add way to get item point for some stuff. This is nice when making lists.
|
||||
// TODO: Test the ui code and think how to make it better while working on real stuff.
|
||||
// TODO: Test the resource loading code.
|
||||
// TODO: Think about the sound API.
|
||||
|
@ -877,13 +879,16 @@ struct UiState {
|
|||
Gamepad gamepadClickAction = Gamepad.a;
|
||||
bool isActOnPress;
|
||||
|
||||
Vec2 mousePressedPoint;
|
||||
Vec2 viewportPoint;
|
||||
Vec2 viewportSize;
|
||||
Vec2 viewportScale = Vec2(1);
|
||||
Vec2 startPoint;
|
||||
Vec2 startPointOffest;
|
||||
short margin;
|
||||
Layout layout;
|
||||
Vec2 layoutStartPoint;
|
||||
Vec2 layoutStartPointOffest;
|
||||
Vec2 layoutMaxItemSize;
|
||||
|
||||
Vec2 itemDragOffset;
|
||||
Vec2 itemPoint;
|
||||
|
@ -2414,15 +2419,21 @@ void prepareUi() {
|
|||
uiState.viewportSize = resolution;
|
||||
uiState.viewportScale = Vec2(1.0f);
|
||||
uiState.startPoint = Vec2();
|
||||
uiState.startPointOffest = Vec2();
|
||||
uiState.margin = 0;
|
||||
uiState.layout = Layout.v;
|
||||
uiState.layoutStartPoint = Vec2();
|
||||
uiState.layoutStartPointOffest = Vec2();
|
||||
uiState.layoutMaxItemSize = Vec2();
|
||||
uiState.itemPoint = Vec2();
|
||||
uiState.itemSize = Vec2();
|
||||
uiState.itemId = 0;
|
||||
uiState.hotItemId = 0;
|
||||
uiState.activeItemId = 0;
|
||||
uiState.clickedItemId = 0;
|
||||
|
||||
if (uiState.mouseClickAction.isPressed) {
|
||||
uiState.mousePressedPoint = uiMouse;
|
||||
}
|
||||
}
|
||||
|
||||
Vec2 uiMouse() {
|
||||
|
@ -2467,7 +2478,9 @@ Vec2 uiStartPoint() {
|
|||
void setUiStartPoint(Vec2 value) {
|
||||
uiState.itemSize = Vec2();
|
||||
uiState.startPoint = value;
|
||||
uiState.startPointOffest = Vec2();
|
||||
uiState.layoutStartPoint = value;
|
||||
uiState.layoutStartPointOffest = Vec2();
|
||||
uiState.layoutMaxItemSize = Vec2();
|
||||
}
|
||||
|
||||
short uiMargin() {
|
||||
|
@ -2479,15 +2492,26 @@ void setUiMargin(short value) {
|
|||
}
|
||||
|
||||
void useUiLayout(Layout value) {
|
||||
if (uiState.startPointOffest) {
|
||||
if (uiState.layoutStartPointOffest) {
|
||||
final switch (value) {
|
||||
case Layout.v:
|
||||
if (uiState.layout == value) uiState.startPointOffest.x += uiState.itemSize.x + uiState.margin;
|
||||
uiState.startPointOffest.y = 0;
|
||||
if (uiState.layoutStartPointOffest.x > uiState.layoutMaxItemSize.x) {
|
||||
uiState.layoutStartPoint.x = uiState.layoutStartPoint.x + uiState.layoutStartPointOffest.x + uiState.margin;
|
||||
} else {
|
||||
uiState.layoutStartPoint.x += uiState.layoutMaxItemSize.x + uiState.margin;
|
||||
}
|
||||
uiState.layoutStartPointOffest = Vec2();
|
||||
uiState.layoutMaxItemSize.x = 0.0f;
|
||||
break;
|
||||
case Layout.h:
|
||||
uiState.startPointOffest.x = 0;
|
||||
if (uiState.layout == value) uiState.startPointOffest.y += uiState.itemSize.y + uiState.margin;
|
||||
uiState.layoutStartPoint.x = uiState.startPoint.x;
|
||||
if (uiState.layoutStartPointOffest.y > uiState.layoutMaxItemSize.y) {
|
||||
uiState.layoutStartPoint.y = uiState.layoutStartPoint.y + uiState.layoutStartPointOffest.y + uiState.margin;
|
||||
} else {
|
||||
uiState.layoutStartPoint.y += uiState.layoutMaxItemSize.y + uiState.margin;
|
||||
}
|
||||
uiState.layoutStartPointOffest = Vec2();
|
||||
uiState.layoutMaxItemSize.y = 0.0f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -2583,9 +2607,11 @@ void updateUiState(Vec2 itemPoint, Vec2 itemSize, bool isHot, bool isActive, boo
|
|||
uiState.itemPoint = itemPoint;
|
||||
uiState.itemSize = itemSize;
|
||||
uiState.itemId += 1;
|
||||
if (itemSize.x > uiState.layoutMaxItemSize.x) uiState.layoutMaxItemSize.x = itemSize.x;
|
||||
if (itemSize.y > uiState.layoutMaxItemSize.y) uiState.layoutMaxItemSize.y = itemSize.y;
|
||||
final switch (uiState.layout) {
|
||||
case Layout.v: uiState.startPointOffest.y += uiState.itemSize.y + uiState.margin; break;
|
||||
case Layout.h: uiState.startPointOffest.x += uiState.itemSize.x + uiState.margin; break;
|
||||
case Layout.v: uiState.layoutStartPointOffest.y += uiState.itemSize.y + uiState.margin; break;
|
||||
case Layout.h: uiState.layoutStartPointOffest.x += uiState.itemSize.x + uiState.margin; break;
|
||||
}
|
||||
if (isHot) uiState.hotItemId = uiState.itemId;
|
||||
if (isActive) {
|
||||
|
@ -2606,11 +2632,22 @@ bool updateUiButton(Vec2 size, IStr text, UiButtonOptions options = UiButtonOpti
|
|||
if (options.font.isEmpty) options.font = engineFont;
|
||||
auto m = uiMouse;
|
||||
auto id = uiState.itemId + 1;
|
||||
auto area = Rect(uiState.startPoint + uiState.startPointOffest, size);
|
||||
auto area = Rect(uiState.layoutStartPoint + uiState.layoutStartPointOffest, size);
|
||||
// auto isHot = area.hasPoint(uiMouse)
|
||||
auto isHot = m.x >= area.position.x && m.x < area.position.x + area.size.x && m.y >= area.position.y && m.y < area.position.y + area.size.y;
|
||||
auto isActive = isHot && uiState.mouseClickAction.isDown;
|
||||
auto isClicked = isHot && (uiState.isActOnPress ? uiState.mouseClickAction.isPressed : uiState.mouseClickAction.isReleased);
|
||||
auto isClicked = isHot;
|
||||
if (uiState.isActOnPress) {
|
||||
isClicked = isClicked && uiState.mouseClickAction.isPressed;
|
||||
} else {
|
||||
auto isHotFromMousePressedPoint =
|
||||
uiState.mousePressedPoint.x >= area.position.x &&
|
||||
uiState.mousePressedPoint.x < area.position.x + area.size.x &&
|
||||
uiState.mousePressedPoint.y >= area.position.y &&
|
||||
uiState.mousePressedPoint.y < area.position.y + area.size.y;
|
||||
isClicked = isClicked && isHotFromMousePressedPoint && uiState.mouseClickAction.isReleased;
|
||||
}
|
||||
|
||||
if (options.isDisabled) {
|
||||
isHot = false;
|
||||
isActive = false;
|
||||
|
@ -2658,6 +2695,7 @@ bool uiButton(Vec2 size, IStr text, UiButtonOptions options = UiButtonOptions())
|
|||
bool uiDragHandle(Vec2 size, ref Vec2 point, UiButtonOptions options = UiButtonOptions()) {
|
||||
auto dragLimitX = Vec2(-100000.0f, 100000.0f);
|
||||
auto dragLimitY = Vec2(-100000.0f, 100000.0f);
|
||||
// NOTE: There is a potential bug here when size is bigger than the limit/viewport. I will ignore it for now.
|
||||
final switch (options.dragLimit) {
|
||||
case UiDragLimit.none: break;
|
||||
case UiDragLimit.viewport:
|
||||
|
@ -2665,10 +2703,12 @@ bool uiDragHandle(Vec2 size, ref Vec2 point, UiButtonOptions options = UiButtonO
|
|||
dragLimitY = Vec2(0.0f, uiState.viewportSize.y);
|
||||
break;
|
||||
case UiDragLimit.viewportAndX:
|
||||
point.y = clamp(point.y, 0.0f, uiState.viewportSize.y - size.y);
|
||||
dragLimitX = Vec2(0.0f, uiState.viewportSize.x);
|
||||
dragLimitY = Vec2(point.y, point.y + size.y);
|
||||
break;
|
||||
case UiDragLimit.viewportAndY:
|
||||
point.x = clamp(point.x, 0.0f, uiState.viewportSize.x - size.x);
|
||||
dragLimitX = Vec2(point.x, point.x + size.x);
|
||||
dragLimitY = Vec2(0.0f, uiState.viewportSize.y);
|
||||
break;
|
||||
|
@ -2677,10 +2717,12 @@ bool uiDragHandle(Vec2 size, ref Vec2 point, UiButtonOptions options = UiButtonO
|
|||
dragLimitY = options.dragLimitY;
|
||||
break;
|
||||
case UiDragLimit.customAndX:
|
||||
point.y = clamp(point.y, 0.0f, options.dragLimitY.y - size.y);
|
||||
dragLimitX = options.dragLimitX;
|
||||
dragLimitY = Vec2(point.y, point.y + size.y);
|
||||
break;
|
||||
case UiDragLimit.customAndY:
|
||||
point.x = clamp(point.x, 0.0f, options.dragLimitX.y - size.x);
|
||||
dragLimitX = Vec2(point.x, point.x + size.x);
|
||||
dragLimitY = options.dragLimitY;
|
||||
break;
|
||||
|
@ -2708,7 +2750,8 @@ bool uiDragHandle(Vec2 size, ref Vec2 point, UiButtonOptions options = UiButtonO
|
|||
}
|
||||
|
||||
void uiTexture(Texture texture, UiButtonOptions options = UiButtonOptions()) {
|
||||
auto point = uiState.startPoint + uiState.startPointOffest;
|
||||
auto point = uiState.layoutStartPoint + uiState.layoutStartPointOffest;
|
||||
drawRect(Rect(point, texture.size), black);
|
||||
drawTexture(texture, point);
|
||||
updateUiState(point, texture.size, false, false, false);
|
||||
}
|
||||
|
@ -2719,7 +2762,7 @@ void uiTexture(TextureId texture, UiButtonOptions options = UiButtonOptions()) {
|
|||
|
||||
void uiText(IStr text, UiButtonOptions options = UiButtonOptions()) {
|
||||
if (options.font.isEmpty) options.font = engineFont;
|
||||
auto point = uiState.startPoint + uiState.startPointOffest;
|
||||
auto point = uiState.layoutStartPoint + uiState.layoutStartPointOffest;
|
||||
auto size = measureTextSize(options.font, text);
|
||||
drawText(options.font, text, point);
|
||||
updateUiState(point, size, false, false, false);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue