New example: rectangle_bounds
This commit is contained in:
parent
5100377cde
commit
b7ac6957d3
1 changed files with 282 additions and 0 deletions
282
examples/text/rectangle_bounds/main.go
Normal file
282
examples/text/rectangle_bounds/main.go
Normal file
|
@ -0,0 +1,282 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [text] example - Rectangle bounds
|
||||
*
|
||||
* Example originally created with raylib 2.5, last time updated with raylib 4.0
|
||||
*
|
||||
* Example contributed by Vlad Adrian (@demizdor) and reviewed by Ramon Santamaria (@raysan5)
|
||||
*
|
||||
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
|
||||
* BSD-like license that allows static linking with closed source software
|
||||
*
|
||||
* Copyright (c) 2018-2024 Vlad Adrian (@demizdor) and Ramon Santamaria (@raysan5)
|
||||
*
|
||||
********************************************************************************************/
|
||||
package main
|
||||
|
||||
import (
|
||||
"unicode/utf8"
|
||||
"unsafe"
|
||||
|
||||
rl "github.com/gen2brain/raylib-go/raylib"
|
||||
)
|
||||
|
||||
const (
|
||||
screenWidth = 800
|
||||
screenHeight = 450
|
||||
MeasureState = 0
|
||||
DrawState = 1
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
rl.InitWindow(screenWidth, screenHeight, "raylib [text] example - draw text inside a rectangle")
|
||||
|
||||
text := `Text cannot escape this container ...word wrap also works when active so here's a long text for testing.
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Nec ullamcorper sit amet risus nullam eget felis eget.`
|
||||
|
||||
resizing, wordWrap := false, true
|
||||
|
||||
container := rl.Rectangle{X: 25.0, Y: 25.0, Width: screenWidth - 50.0, Height: screenHeight - 250.0}
|
||||
resizer := rl.Rectangle{X: container.X + container.Width - 17, Y: container.Y + container.Height - 17, Width: 14, Height: 14}
|
||||
|
||||
// Minimum width and height for the container rectangle
|
||||
minWidth := float32(60.0)
|
||||
minHeight := float32(60.0)
|
||||
maxWidth := screenWidth - float32(50.0)
|
||||
maxHeight := screenHeight - float32(160.0)
|
||||
|
||||
lastMouse := rl.Vector2{} // Stores last mouse coordinates
|
||||
borderColor := rl.Maroon // Container border color
|
||||
font := rl.GetFontDefault() // Get default system font
|
||||
|
||||
rl.SetTargetFPS(60) // Set our game to run at 60 frames-per-second
|
||||
|
||||
for !rl.WindowShouldClose() { // Detect window close button or ESC key
|
||||
// Update
|
||||
if rl.IsKeyPressed(rl.KeySpace) {
|
||||
wordWrap = !wordWrap
|
||||
}
|
||||
|
||||
mouse := rl.GetMousePosition()
|
||||
|
||||
// Check if the mouse is inside the container and toggle border color
|
||||
if rl.CheckCollisionPointRec(mouse, container) {
|
||||
borderColor = rl.Fade(rl.Maroon, 0.4)
|
||||
} else if !resizing {
|
||||
borderColor = rl.Maroon
|
||||
}
|
||||
|
||||
// Container resizing logic
|
||||
if resizing {
|
||||
if rl.IsMouseButtonReleased(rl.MouseButtonLeft) {
|
||||
resizing = false
|
||||
}
|
||||
|
||||
width := container.Width + (mouse.X - lastMouse.X)
|
||||
height := container.Height + (mouse.Y - lastMouse.Y)
|
||||
|
||||
container.Width = rl.Clamp(width, minWidth, maxWidth)
|
||||
container.Height = rl.Clamp(height, minHeight, maxHeight)
|
||||
} else {
|
||||
// Check if we're resizing
|
||||
if rl.IsMouseButtonDown(rl.MouseButtonLeft) && rl.CheckCollisionPointRec(mouse, resizer) {
|
||||
resizing = true
|
||||
}
|
||||
}
|
||||
|
||||
// Move resizer rectangle properly
|
||||
resizer.X = container.X + container.Width - 17
|
||||
resizer.Y = container.Y + container.Height - 17
|
||||
|
||||
lastMouse = mouse // Update mouse
|
||||
|
||||
// Draw
|
||||
rl.BeginDrawing()
|
||||
rl.ClearBackground(rl.RayWhite)
|
||||
|
||||
rl.DrawRectangleLinesEx(container, 3, borderColor) // Draw container border
|
||||
|
||||
// Draw text in container (add some padding)
|
||||
DrawTextBoxed(font, text, rl.Rectangle{X: container.X + 4, Y: container.Y + 4, Width: container.Width - 4,
|
||||
Height: container.Height - 4}, 20.0, 2.0, wordWrap, rl.Gray)
|
||||
|
||||
rl.DrawRectangleRec(resizer, borderColor) // Draw the resize box
|
||||
|
||||
// Draw bottom info
|
||||
rl.DrawRectangle(0, screenHeight-54, screenWidth, 54, rl.Gray)
|
||||
rl.DrawRectangleRec(rl.Rectangle{X: 382.0, Y: screenHeight - 34.0, Width: 12.0, Height: 12.0}, rl.Maroon)
|
||||
|
||||
rl.DrawText("Word Wrap: ", 313, screenHeight-115, 20, rl.Black)
|
||||
if wordWrap {
|
||||
rl.DrawText("ON", 447, screenHeight-115, 20, rl.Red)
|
||||
} else {
|
||||
rl.DrawText("OFF", 447, screenHeight-115, 20, rl.Black)
|
||||
}
|
||||
|
||||
rl.DrawText("Press [SPACE] to toggle word wrap", 218, screenHeight-86, 20, rl.Gray)
|
||||
rl.DrawText("Click hold & drag the to resize the container", 155, screenHeight-38, 20, rl.RayWhite)
|
||||
|
||||
rl.EndDrawing()
|
||||
}
|
||||
|
||||
// De-Initialization
|
||||
rl.CloseWindow() // Close window and OpenGL context
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------
|
||||
// Module functions definition
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
// DrawTextBoxed draws text using font inside rectangle limits
|
||||
func DrawTextBoxed(font rl.Font, text string, rec rl.Rectangle, fontSize, spacing float32, wordWrap bool, tint rl.Color) {
|
||||
DrawTextBoxedSelectable(font, text, rec, fontSize, spacing, wordWrap, tint, 0, 0, rl.White, rl.White)
|
||||
}
|
||||
|
||||
// DrawTextBoxedSelectable draws text using font inside rectangle limits with support for text selection
|
||||
func DrawTextBoxedSelectable(font rl.Font, text string, rec rl.Rectangle, fontSize, spacing float32,
|
||||
wordWrap bool, tint rl.Color, selectStart, selectLength int32, selectTint, selectBackTint rl.Color) {
|
||||
|
||||
length := int32(utf8.RuneCountInString(text)) // Total length in bytes of the text, scanned by codepoints in loop
|
||||
|
||||
// TextOffsetY : Offset between lines (on line break '\n')
|
||||
// TextOffsetX : Offset X to next character to draw
|
||||
var textOffsetY, textOffsetX float32
|
||||
|
||||
scaleFactor := fontSize / float32(font.BaseSize) // Character rectangle scaling factor
|
||||
|
||||
// Word/character wrapping mechanism variables
|
||||
state := DrawState
|
||||
if wordWrap {
|
||||
state = MeasureState
|
||||
}
|
||||
|
||||
// StartLine : Index where to begin drawing (where a line begins)
|
||||
// EndLine : Index where to stop drawing (where a line ends)
|
||||
// LastK : Holds last value of the character position
|
||||
var startLine, endLine, lastk int32 = -1, -1, -1
|
||||
|
||||
for i, k := int32(0), int32(0); i < length; i, k = i+1, k+1 {
|
||||
// Get next codepoint from byte string and glyph index in font
|
||||
codepoint, width := utf8.DecodeRuneInString(text[i:])
|
||||
codepointByteCount := int32(width)
|
||||
index := rl.GetGlyphIndex(font, codepoint)
|
||||
|
||||
// NOTE: Normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f)
|
||||
// but we need to draw all the bad bytes using the '?' symbol moving one byte
|
||||
if codepoint == 0x3f {
|
||||
codepointByteCount = 1
|
||||
}
|
||||
i += codepointByteCount - 1
|
||||
|
||||
var glyphWidth float32
|
||||
if codepoint != '\n' {
|
||||
chars := unsafe.Slice(font.Chars, font.CharsCount)
|
||||
if chars[index].AdvanceX == 0 {
|
||||
glyphWidth = unsafe.Slice(font.Recs, font.CharsCount)[index].Width * scaleFactor
|
||||
} else {
|
||||
glyphWidth = float32(chars[index].AdvanceX) * scaleFactor
|
||||
}
|
||||
|
||||
if i+1 < length {
|
||||
glyphWidth = glyphWidth + spacing
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: When wordWrap is ON we first measure how much of the text we can draw before going outside the rec container
|
||||
// We store this info in startLine and endLine, then we change states, draw the text between those two variables
|
||||
// and change states again and again recursively until the end of the text (or until we get outside the container).
|
||||
// When wordWrap is OFF we don't need the measure state so we go to the drawing state immediately
|
||||
// and begin drawing on the next line before we can get outside the container.
|
||||
if state == MeasureState {
|
||||
// TODO: There are multiple types of spaces in UNICODE, maybe it's a good idea to add support for more
|
||||
// Ref: http://jkorpela.fi/chars/spaces.html
|
||||
if (codepoint == ' ') || (codepoint == '\t') || (codepoint == '\n') {
|
||||
endLine = i
|
||||
}
|
||||
|
||||
if (textOffsetX + glyphWidth) > rec.Width {
|
||||
if endLine < 1 {
|
||||
endLine = i
|
||||
}
|
||||
|
||||
if i == endLine {
|
||||
endLine -= codepointByteCount
|
||||
}
|
||||
if (startLine + codepointByteCount) == endLine {
|
||||
endLine = i - codepointByteCount
|
||||
}
|
||||
|
||||
state = 1 - state // Toggle state between MeasureState and DrawState
|
||||
} else if (i + 1) == length {
|
||||
endLine = i
|
||||
state = 1 - state // Toggle state between MeasureState and DrawState
|
||||
} else if codepoint == '\n' {
|
||||
state = 1 - state // Toggle state between MeasureState and DrawState
|
||||
}
|
||||
|
||||
if state == DrawState {
|
||||
textOffsetX = 0
|
||||
i = startLine
|
||||
glyphWidth = 0
|
||||
|
||||
// Save character position when we switch states
|
||||
tmp := lastk
|
||||
lastk = k - 1
|
||||
k = tmp
|
||||
}
|
||||
} else {
|
||||
if codepoint == '\n' {
|
||||
if !wordWrap {
|
||||
textOffsetY += float32(font.BaseSize+font.BaseSize/2) * scaleFactor
|
||||
textOffsetX = 0
|
||||
}
|
||||
} else {
|
||||
if !wordWrap && ((textOffsetX + glyphWidth) > rec.Width) {
|
||||
textOffsetY += float32(font.BaseSize+font.BaseSize/2) * scaleFactor
|
||||
textOffsetX = 0
|
||||
}
|
||||
|
||||
// When text overflows rectangle height limit, just stop drawing
|
||||
if (textOffsetY + float32(font.BaseSize)*scaleFactor) > rec.Height {
|
||||
break
|
||||
}
|
||||
|
||||
// Draw selection background
|
||||
isGlyphSelected := false
|
||||
if (selectStart >= 0) && (k >= selectStart) && (k < (selectStart + selectLength)) {
|
||||
rl.DrawRectangleRec(rl.Rectangle{X: rec.X + textOffsetX - 1, Y: rec.Y + textOffsetY, Width: glyphWidth,
|
||||
Height: float32(font.BaseSize) * scaleFactor}, selectBackTint)
|
||||
isGlyphSelected = true
|
||||
}
|
||||
|
||||
// Draw current character glyph
|
||||
if (codepoint != ' ') && (codepoint != '\t') {
|
||||
col := tint
|
||||
if isGlyphSelected {
|
||||
col = selectTint
|
||||
}
|
||||
pos := rl.Vector2{X: rec.X + textOffsetX, Y: rec.Y + textOffsetY}
|
||||
rl.DrawTextEx(font, string(codepoint), pos, fontSize, 0, col)
|
||||
}
|
||||
}
|
||||
|
||||
if wordWrap && (i == endLine) {
|
||||
textOffsetY += float32(font.BaseSize+font.BaseSize/2) * scaleFactor
|
||||
textOffsetX = 0
|
||||
startLine = endLine
|
||||
endLine = -1
|
||||
glyphWidth = 0
|
||||
selectStart += lastk - k
|
||||
k = lastk
|
||||
|
||||
state = 1 - state // Toggle state between MeasureState and DrawState
|
||||
}
|
||||
}
|
||||
|
||||
if (textOffsetX != 0) || (codepoint != ' ') { // avoid leading spaces
|
||||
textOffsetX += glyphWidth
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue