edit: added 2 lines to logo.odin for smoother transition
I wanted to have an animated logo that could be reused and modified relatively easily for all future projects. My goal was to allow some configurability while also scaling things to prevent too much math work down the road. I feel that I’ve reached a confortable compromise for my needs. Hopefully others can find this useful.
There are two files. A logo.odin file meant to be used as “drop-in” code, and a main.odin file to demo the scaling and “configurability” of the glyphs. The glyphs are their own contained procedures, which allow for any future whims on what I might want a glyph to look like. I decided to allow the font size to be defined and not automatically scaled with the intention that I may want to use a custom font, and the scaling for that could be different. The main.odin file shows the current method for scaling the default font. The demo only tests sizes 64 to 256, with an increment/decrement of 16.
Keyboard Controls:
R - resets the animation and starts it over
UP - increase size by 16
DOWN - decrease size by 16
files: logo.odin and main.odin - place both in same folder to compile the demo
logo.odin
package logo
import rl "vendor:raylib"
LogoGlyph :: #type proc (l: Logo)
Logo :: struct {
using xys: struct {x, y, s: f32 }, //square x, y, s (size) - logo.s/16 is used to scale width of animated lines
text: cstring, //6 chars or less is best for default font
font: rl.Font, //leave blank for default font
fs: f32, //font size: for default font suggest 70 for s (size) 256, 30 for 128 etc.
bg: rl.Color, //background color
fg: rl.Color, //foreground color
state: i32, //starts at 0 - set state to 0 before redrawing
glyph: LogoGlyph, //glyph proc drawn at end - make relative to logo dimensions - leave as {} if not using
c: struct { f, t, b: f32 }, //counters: frame, top(and left), bottom(and right), leave 0
}
resetLogo :: proc(logo: ^Logo) { logo.state = 0 }
drawLogo :: proc (logo: ^Logo) { using rl
anime: [8]proc (logo: Logo) = {
proc(logo: Logo) { DrawRectangleRec({logo.x, logo.y, logo.s, logo.s}, logo.bg) }, //background
proc(logo: Logo) { DrawRectangleRec({logo.x, logo.y, logo.s/16, logo.s/16}, logo.fg) }, //small blinking box
proc(logo: Logo) { DrawRectangleRec({logo.x, logo.y, logo.c.t, logo.s/16}, logo.fg) }, //top
proc(logo: Logo) { DrawRectangleRec({logo.x, logo.y, logo.s/16, logo.c.t}, logo.fg) }, //left
proc(logo: Logo) { DrawRectangleRec({logo.x + logo.s - (logo.s/16), logo.y, logo.s/16, logo.c.b}, logo.fg) }, //right
proc(logo: Logo) { DrawRectangleRec({logo.x, logo.y + logo.s - (logo.s/16), logo.c.b , logo.s/16}, logo.fg) }, //bottom
proc(logo: Logo) { DrawTextEx(logo.font, sub(logo), {logo.x, logo.y} + logo.s - txt(logo) - (2 * logo.s/16), logo.fs, 4, logo.fg) },
proc(logo: Logo) { if logo.glyph != {} { logo.glyph(logo) } }
}
sub :: proc(logo: Logo) -> cstring { return TextSubtext(logo.text, 0, (i32(logo.c.f)/12) + 1) } // every 12 frames, one more letter
txt :: proc(logo: Logo) -> rl.Vector2 { return MeasureTextEx(logo.font, logo.text, logo.fs, 4) }
switch logo.state {
case 0:
if logo.font == {} { logo.font = GetFontDefault() }
logo.c = {0, 0, 0}
logo.state = 1
case 1: //small box blinking
anime[0](logo^)
if i32(logo.c.f) % 30 >= 15 { anime[1]((logo^)) } //blink square on/off every 15 frames
logo.c.f +=1
if logo.c.f >= 120 { logo.state = 2; logo.c.f = 0 } //reset frame counter for text case
case 2: //top and left bars growing
if logo.c.t == 0 { logo.c.t = logo.s/16 } //kickstart at 0 for smoother transition
for i in 0..=3 { anime[i](logo^) }
logo.c.t += logo.s/64
if logo.c.t >= logo.s { logo.state = 3; logo.c.f = 0 } //reset frame counter incase state 1 was manually skipped
case 3: //bottom and right bars growing
if logo.c.b == 0 { logo.c.b = logo.s/16 } //kickstart at 0 for smoother transition
for i in 0..=5 { anime[i](logo^) }
logo.c.b += logo.s/64
if logo.c.b >= logo.s { logo.state = 4; logo.c.f = 0 } //reset frame counter incase state 1 was manually skipped
case 4: //letters appearing (one by one)
for i in 0..=6 { anime[i](logo^) }
logo.c.f += 1
if i32(logo.c.f) >= (12 * i32(len(logo.text))) { logo.state = 5 }
case 5: //everything
for i in 0..=7 { anime[i](logo^) }
}
}
main.odin - this is to show what is possible and my inentions for future use
package logo
import rl "vendor:raylib"
import "core:math"
round :: math.round
ol_glyph :LogoGlyph: proc(logo: Logo) { using rl
cos_sin :: proc(a: f32) -> [2]f32 { return {math.cos(DEG2RAD*a), math.sin(DEG2RAD*a)} }
vec: [4]Vector2
thick: f32 = clamp(round(logo.s/64), 1, 4)
radius: f32 = logo.s/8
deg: Vector4 = {140, 270, -70, 120}
center: Vector2 = {logo.x, logo.y} + {logo.s, logo.s/64} + {-4, 4} * logo.s/16
for &v, i in vec { v = radius * cos_sin(deg[i]) + center}
segments: i32 = IsWindowState({.MSAA_4X_HINT}) ? 60 : 360 //fewer segments if MSAA enabled - let it do the work
DrawRing(center, radius - thick, radius, deg[0], deg[1], segments, WHITE)
DrawRing(center, radius - thick, radius, deg[2], deg[3], segments, WHITE)
DrawLineEx(vec[0], vec[1], thick, WHITE)
DrawLineEx(vec[2], vec[3], thick, WHITE)
}
rl_glyph :LogoGlyph: proc(logo: Logo) {
rl.DrawTriangle(
{logo.x, logo.y} + {logo.s, 0} + {-2, 2} * logo.s/16,
{logo.x, logo.y} + {logo.s, 0} + {-6, 2} * logo.s/16,
{logo.x, logo.y} + {logo.s, 0} + {-2, 6} * logo.s/16, rl.RED)
}
gl_glyph :LogoGlyph: proc(logo: Logo) {
psize: f32 = clamp(round(logo.s/64), 1, 4)
scale: f32 = ((psize*16)/16)*16
wspace: [2]f32 = {1,2}
rl.GuiDrawIcon(
.ICON_WINDOW,
i32(logo.x + logo.s - (2 * logo.s/16) - scale + wspace.x),
i32(logo.y + (2 * logo.s/16) - wspace.y),
i32(psize), logo.fg)
}
xl_glyph :LogoGlyph: proc(logo: Logo) {
psize: f32 = clamp(round(logo.s/64), 1, 4)
scale: f32 = ((psize*16)/16)*16
wspace: [2]f32 = {2,1}
rl.GuiDrawIcon(
.ICON_DEMON,
i32(logo.x + logo.s - (2 * logo.s/16) - scale + wspace.x),
i32(logo.y + (2 * logo.s/16) - wspace.y),
i32(psize), logo.fg)
}
main :: proc() { using rl
InitWindow(1088, 336, "raylib logo anime")
SetTargetFPS(60)
dim: f32 = 256
dimtext: struct {text: cstring, using xy: [2]f32}
ol_logo: Logo = {text = "ODIN", bg = rl.BLUE, fs = 70, fg = rl.RAYWHITE, glyph = ol_glyph}
rl_logo: Logo = {text = "raylib", bg = rl.RAYWHITE, fs = 70, fg = rl.BLACK, glyph = rl_glyph}
gl_logo: Logo = {text = "raygui", bg = rl.LIGHTGRAY, fs = 70, fg = rl.GRAY, glyph = gl_glyph}
xl_logo: Logo = {text = "xuul", bg = rl.RED, fs = 70, fg = rl.BLACK, glyph = xl_glyph}
defer CloseWindow()
for !WindowShouldClose() {
//mouse control
mwmove := GetMouseWheelMove()
if mwmove != 0 {
dim = clamp(dim + (mwmove * 16), 64, 256)
resetLogo(&ol_logo); resetLogo(&rl_logo); resetLogo(&gl_logo); resetLogo(&xl_logo)
}
//keyboard control
for key := GetKeyPressed(); key > .KEY_NULL; key = GetKeyPressed() {
#partial switch key {
case .R:
resetLogo(&ol_logo); resetLogo(&rl_logo); resetLogo(&gl_logo); resetLogo(&xl_logo)
case .UP:
dim = dim + 16 > 256 ? 256 : dim + 16
resetLogo(&ol_logo); resetLogo(&rl_logo); resetLogo(&gl_logo); resetLogo(&xl_logo)
case .DOWN:
dim = dim - 64 < 64 ? 64 : dim - 16
resetLogo(&ol_logo); resetLogo(&rl_logo); resetLogo(&gl_logo); resetLogo(&xl_logo)
}
}
//set new size from key presses for demo here also scale font size based on dim
ol_logo.xys = {8 + 0 * 16 + 0 * dim, 8, dim}; ol_logo.fs = round(dim/32*10-10)
rl_logo.xys = {8 + 1 * 16 + 1 * dim, 8, dim}; rl_logo.fs = round(dim/32*10-10)
gl_logo.xys = {8 + 2 * 16 + 2 * dim, 8, dim}; gl_logo.fs = round(dim/32*10-10)
xl_logo.xys = {8 + 3 * 16 + 3 * dim, 8, dim}; xl_logo.fs = round(dim/32*10-10)
BeginDrawing(); defer EndDrawing()
ClearBackground(BLACK)
//metrics for demo
dimtext.text = TextFormat("size: %f x %f text: %f", dim, dim, round(dim/32*10-10))
dimtext.xy = MeasureTextEx(GetFontDefault(),dimtext.text, 50, 4)
DrawTextEx(GetFontDefault(),dimtext.text, {(8*8+4*256)/2-dimtext.x/2, 279},50,4,DARKGRAY)
//extra border to demo the logos - not needed but i think looks nice
DrawRectangleLinesEx({0 * (ol_logo.s + 16), 0, ol_logo.s + 16, ol_logo.s + 16}, 8, ol_logo.bg)
DrawRectangleLinesEx({1 * (rl_logo.s + 16), 0, rl_logo.s + 16, rl_logo.s + 16}, 8, rl_logo.bg)
DrawRectangleLinesEx({2 * (gl_logo.s + 16), 0, gl_logo.s + 16, gl_logo.s + 16}, 8, gl_logo.bg)
DrawRectangleLinesEx({3 * (xl_logo.s + 16), 0, xl_logo.s + 16, xl_logo.s + 16}, 8, xl_logo.bg)
//draw each logo
drawLogo(&ol_logo) //stagger the start of each after this one
drawLogo(&rl_logo); if ol_logo.state <= 2 { rl_logo.state = 1} else if rl_logo.state == 1 { rl_logo.state = 2 }
drawLogo(&gl_logo); if rl_logo.state <= 2 { gl_logo.state = 1} else if gl_logo.state == 1 { gl_logo.state = 2 }
drawLogo(&xl_logo); if gl_logo.state <= 2 { xl_logo.state = 1} else if xl_logo.state == 1 { xl_logo.state = 2 }
}
}