← ziglang.inraylib-zigGitHub

rlgl color wheel

★★★☆by Robin (@RobinsAviary)

There is no browser build of this example yet.

Is it the same as C?

//! raylib [shapes] example - rlgl color wheel
//!
//! Zig-API translation of examples/shapes/shapes_rlgl_color_wheel.c (raylib 6.2).
//! Example contributed by Robin (@RobinsAviary) and reviewed by Ramon Santamaria (@raysan5)
//! Example licensed under an unmodified zlib/libpng license.
//! Copyright (c) 2025 Robin (@RobinsAviary)

const rl = @import("raylib");
const gui = @import("raygui");
const gl = rl.gl;
const math = rl.math;

/// How the color wheel will be rendered
const RenderMode = enum { triangles, lines };

/// Position on a circle of `radius` around `center`, `angle` radians clockwise from the top.
fn pointOnWheel(center: rl.Vector2, angle: f32, radius: f32) rl.Vector2 {
    return math.vector2Add(.{ .x = @sin(angle) * radius, .y = -@cos(angle) * radius }, center);
}

/// Normalized (0..1) clockwise angle of `position` around `center`.
fn wheelAngle(center: rl.Vector2, position: rl.Vector2, radius: f32) f32 {
    return (math.vector2Angle(.{ .x = 0.0, .y = -radius }, math.vector2Subtract(center, position)) / rl.pi + 1.0) / 2.0;
}

pub fn main() void {
    // Initialization
    const screen_width = 800;
    const screen_height = 450;

    // The minimum/maximum points the circle can have
    const points_min = 3;
    const points_max = 256;

    // The current number of points and the radius of the circle
    var triangle_count: u32 = 64;
    var point_scale: f32 = 150.0;

    // Slider value, literally maps to value in HSV
    var value: f32 = 1.0;

    // The center of the screen
    const center: rl.Vector2 = .{ .x = screen_width / 2.0, .y = screen_height / 2.0 };
    // The location of the color wheel
    var circle_position = center;

    // The currently selected color
    var color: rl.Color = .{ .r = 255, .g = 255, .b = 255, .a = 255 };

    // Indicates if the slider is being clicked
    var slider_clicked = false;

    // Indicates if the current color going to be updated, as well as the handle position
    var setting_color = false;

    var render_mode: RenderMode = .triangles;

    // Enable anti-aliasing
    rl.setConfigFlags(.{ .msaa_4x_hint = true });
    rl.initWindow(screen_width, screen_height, "raylib [shapes] example - rlgl color wheel");
    defer rl.closeWindow(); // Close window and OpenGL context

    rl.setTargetFPS(60);

    // Main game loop
    while (!rl.windowShouldClose()) { // Detect window close button or ESC key
        // Update
        const wheel = @round(rl.getMouseWheelMove());
        triangle_count = @intFromFloat(math.clamp(@as(f32, @floatFromInt(triangle_count)) + wheel, points_min, points_max));

        const slider_rectangle: rl.Rectangle = .{ .x = 42.0, .y = 16.0 + 64.0 + 45.0, .width = 64.0, .height = 16.0 };
        const mouse_position = rl.getMousePosition();

        // Checks if the user is hovering over the value slider
        const slider_hover = mouse_position.x >= slider_rectangle.x and mouse_position.y >= slider_rectangle.y and
            mouse_position.x < slider_rectangle.x + slider_rectangle.width and mouse_position.y < slider_rectangle.y + slider_rectangle.height;

        // Copy color as hex
        if (rl.isKeyDown(.left_control) and rl.isKeyDown(.c)) {
            if (rl.isKeyPressed(.c)) {
                rl.setClipboardText(rl.textFormat("#{X:0>2}{X:0>2}{X:0>2}", .{ color.r, color.g, color.b }));
            }
        }

        // Scale up the color wheel, adjusting the handle visually
        if (rl.isKeyDown(.up)) {
            point_scale *= 1.025;

            if (point_scale > screen_height / 2.0) {
                point_scale = screen_height / 2.0;
            } else {
                circle_position = math.vector2Add(math.vector2Multiply(math.vector2Subtract(circle_position, center), .{ .x = 1.025, .y = 1.025 }), center);
            }
        }

        // Scale down the wheel, adjusting the handle visually
        if (rl.isKeyDown(.down)) {
            point_scale *= 0.975;

            if (point_scale < 32.0) {
                point_scale = 32.0;
            } else {
                circle_position = math.vector2Add(math.vector2Multiply(math.vector2Subtract(circle_position, center), .{ .x = 0.975, .y = 0.975 }), center);
            }

            const distance = math.vector2Distance(center, circle_position) / point_scale;
            const angle = wheelAngle(center, circle_position, point_scale);

            if (distance > 1.0) circle_position = pointOnWheel(center, angle * (rl.pi * 2.0), point_scale);
        }

        // Checks if the user clicked on the color wheel
        if (rl.isMouseButtonPressed(.left) and math.vector2Distance(rl.getMousePosition(), center) <= point_scale + 10.0) {
            setting_color = true;
        }

        // Update flag when mouse button is released
        if (rl.isMouseButtonReleased(.left)) setting_color = false;

        // Check if the user clicked/released the slider for the color's value
        if (slider_hover and rl.isMouseButtonPressed(.left)) slider_clicked = true;

        if (slider_clicked and rl.isMouseButtonReleased(.left)) slider_clicked = false;

        // Update render mode accordingly
        if (rl.isKeyPressed(.space)) render_mode = .lines;

        if (rl.isKeyReleased(.space)) render_mode = .triangles;

        // If the slider or the wheel was clicked, update the current color
        if (setting_color or slider_clicked) {
            if (setting_color) circle_position = rl.getMousePosition();

            const distance = math.vector2Distance(center, circle_position) / point_scale;

            const angle = wheelAngle(center, circle_position, point_scale);
            if (setting_color and distance > 1.0) circle_position = pointOnWheel(center, angle * (rl.pi * 2.0), point_scale);

            const angle360 = angle * 360.0;
            const value_actual = math.clamp(distance, 0.0, 1.0);
            const gray: u8 = @intFromFloat(value * 255.0);
            color = rl.colorLerp(.{ .r = gray, .g = gray, .b = gray, .a = 255 }, rl.colorFromHSV(angle360, math.clamp(distance, 0.0, 1.0), 1.0), value_actual);
        }

        // Draw
        rl.beginDrawing();
        defer rl.endDrawing();

        rl.clearBackground(.ray_white);

        // Begin rendering color wheel
        gl.begin(switch (render_mode) {
            .triangles => gl.triangles,
            .lines => gl.lines,
        });
        for (0..triangle_count) |i| {
            const angle_offset = (rl.pi * 2.0) / @as(f32, @floatFromInt(triangle_count));
            const angle = angle_offset * @as(f32, @floatFromInt(i));
            const angle_offset_calculated = (@as(f32, @floatFromInt(i)) + 1) * angle_offset;
            const scale: rl.Vector2 = .{ .x = point_scale, .y = point_scale };

            const offset = math.vector2Multiply(.{ .x = @sin(angle), .y = -@cos(angle) }, scale);
            const offset2 = math.vector2Multiply(.{ .x = @sin(angle_offset_calculated), .y = -@cos(angle_offset_calculated) }, scale);

            const position = math.vector2Add(center, offset);
            const position2 = math.vector2Add(center, offset2);

            const angle_non_radian = (angle / (2.0 * rl.pi)) * 360.0;
            const angle_non_radian_offset = (angle_offset / (2.0 * rl.pi)) * 360.0;

            const current_color = rl.colorFromHSV(angle_non_radian, 1.0, 1.0);
            const offset_color = rl.colorFromHSV(angle_non_radian + angle_non_radian_offset, 1.0, 1.0);

            // Input vertices differently depending on mode
            switch (render_mode) {
                .triangles => {
                    // RL_TRIANGLES expects three vertices per triangle
                    gl.color4ub(current_color.r, current_color.g, current_color.b, current_color.a);
                    gl.vertex2f(position.x, position.y);
                    gl.color4f(value, value, value, 1.0);
                    gl.vertex2f(center.x, center.y);
                    gl.color4ub(offset_color.r, offset_color.g, offset_color.b, offset_color.a);
                    gl.vertex2f(position2.x, position2.y);
                },
                .lines => {
                    // RL_LINES expects two vertices per line
                    const white: rl.Color = .white;
                    gl.color4ub(current_color.r, current_color.g, current_color.b, current_color.a);
                    gl.vertex2f(position.x, position.y);
                    gl.color4ub(white.r, white.g, white.b, white.a);
                    gl.vertex2f(center.x, center.y);

                    gl.vertex2f(center.x, center.y);
                    gl.color4ub(offset_color.r, offset_color.g, offset_color.b, offset_color.a);
                    gl.vertex2f(position2.x, position2.y);

                    gl.vertex2f(position2.x, position2.y);
                    gl.color4ub(current_color.r, current_color.g, current_color.b, current_color.a);
                    gl.vertex2f(position.x, position.y);
                },
            }
        }
        gl.end();

        // Make the handle slightly more visible overtop darker colors
        const handle_color: rl.Color = if (math.vector2Distance(center, circle_position) / point_scale <= 0.5 and value <= 0.5) .dark_gray else .black;

        // Draw the color handle
        rl.drawCircleLinesV(circle_position, 4.0, handle_color);

        // Draw the color in a preview, with a darkened outline.
        rl.drawRectangleV(.{ .x = 8.0, .y = 8.0 }, .{ .x = 64.0, .y = 64.0 }, color);
        rl.drawRectangleLinesEx(.{ .x = 8.0, .y = 8.0, .width = 64.0, .height = 64.0 }, 2.0, rl.colorLerp(color, .black, 0.5));

        // Draw current color as hex and decimal
        rl.drawText(rl.textFormat("#{X:0>2}{X:0>2}{X:0>2}\n({d}, {d}, {d})", .{ color.r, color.g, color.b, color.r, color.g, color.b }), 8, 8 + 64 + 8, 20, .dark_gray);

        // Update the visuals for the copying text
        const copying = rl.isKeyDown(.left_control) and rl.isKeyDown(.c);
        const copy_color: rl.Color = if (copying) .dark_green else .dark_gray;
        const offset: i32 = if (copying) 4 else 0;

        // Draw the copying text
        rl.drawText("press ctrl+c to copy!", 8, 425 - offset, 20, copy_color);

        // Display the number of rendered triangles
        rl.drawText(rl.textFormat("triangle count: {d}", .{triangle_count}), 8, 395, 20, .dark_gray);

        // Slider to change color's value
        _ = gui.sliderBar(slider_rectangle, "value: ", "", &value, 0.0, 1.0);

        // Draw FPS next to outlined color preview
        rl.drawFPS(64 + 16, 8);
    }
}
/*******************************************************************************************
*
*   raylib [shapes] example - rlgl color wheel
*
*   Example complexity rating: [★★★☆] 3/4
*
*   Example originally created with raylib 6.0, last time updated with raylib 6.0
*
*   Example contributed by Robin (@RobinsAviary) 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) 2025 Robin (@RobinsAviary)
*
********************************************************************************************/

#include "raylib.h"
#include "rlgl.h"
#include "raymath.h"
#include <stdlib.h>
#include <stdio.h>

#define RAYGUI_IMPLEMENTATION
#include "raygui.h"

//------------------------------------------------------------------------------------
// Program main entry point
//------------------------------------------------------------------------------------
int main(void)
{
    // Initialization
    //--------------------------------------------------------------------------------------
    const int screenWidth = 800;
    const int screenHeight = 450;

    // The minimum/maximum points the circle can have
    const unsigned int pointsMin = 3;
    const unsigned int pointsMax = 256;

    // The current number of points and the radius of the circle
    unsigned int triangleCount = 64;
    float pointScale = 150.0f;

    // Slider value, literally maps to value in HSV
    float value = 1.0f;

    // The center of the screen
    Vector2 center = { (float)screenWidth/2.0f, (float)screenHeight/2.0f };
    // The location of the color wheel
    Vector2 circlePosition = center;

    // The currently selected color
    Color color = { 255, 255, 255, 255 };

    // Indicates if the slider is being clicked
    bool sliderClicked = false;

    // Indicates if the current color going to be updated, as well as the handle position
    bool settingColor = false;

    // How the color wheel will be rendered
    unsigned int renderType = RL_TRIANGLES;

    // Enable anti-aliasing
    SetConfigFlags(FLAG_MSAA_4X_HINT);
    InitWindow(screenWidth, screenHeight, "raylib [shapes] example - rlgl color wheel");

    SetTargetFPS(60);
    //--------------------------------------------------------------------------------------

    // Main game loop
    while (!WindowShouldClose())    // Detect window close button or ESC key
    {
        // Update
        //----------------------------------------------------------------------------------
        float wheel = roundf(GetMouseWheelMove());
        triangleCount = (unsigned int)Clamp((float)triangleCount + wheel, (float)pointsMin, (float)pointsMax);

        Rectangle sliderRectangle = { 42.0f, 16.0f + 64.0f + 45.0f, 64.0f, 16.0f };
        Vector2 mousePosition = GetMousePosition();

        // Checks if the user is hovering over the value slider
        bool sliderHover = (mousePosition.x >= sliderRectangle.x && mousePosition.y >= sliderRectangle.y && mousePosition.x < sliderRectangle.x + sliderRectangle.width && mousePosition.y < sliderRectangle.y + sliderRectangle.height);

        // Copy color as hex
        if (IsKeyDown(KEY_LEFT_CONTROL) && IsKeyDown(KEY_C))
        {
            if (IsKeyPressed(KEY_C))
            {
                SetClipboardText(TextFormat("#%02X%02X%02X", color.r, color.g, color.b));
            }
        }

        // Scale up the color wheel, adjusting the handle visually
        if (IsKeyDown(KEY_UP))
        {
            pointScale *= 1.025f;

            if (pointScale > (float)screenHeight/2.0f)
            {
                pointScale = (float)screenHeight/2.0f;
            }
            else
            {
                circlePosition = Vector2Add(Vector2Multiply(Vector2Subtract(circlePosition, center), (Vector2){ 1.025f, 1.025f }), center);
            }
        }

        // Scale down the wheel, adjusting the handle visually
        if (IsKeyDown(KEY_DOWN))
        {
            pointScale *= 0.975f;

            if (pointScale < 32.0f)
            {
                pointScale = 32.0f;
            }
            else
            {
                circlePosition = Vector2Add(Vector2Multiply(Vector2Subtract(circlePosition, center), (Vector2){ 0.975f, 0.975f }), center);
            }

            float distance = Vector2Distance(center, circlePosition)/pointScale;
            float angle = ((Vector2Angle((Vector2){ 0.0f, -pointScale }, Vector2Subtract(center, circlePosition))/PI + 1.0f)/2.0f);

            if (distance > 1.0f)
            {
                circlePosition = Vector2Add((Vector2){ sinf(angle*(PI*2.0f))*pointScale, -cosf(angle*(PI*2.0f))*pointScale }, center);
            }
        }

        // Checks if the user clicked on the color wheel
        if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT) && Vector2Distance(GetMousePosition(), center) <= pointScale + 10.0f)
        {
            settingColor = true;
        }

        // Update flag when mouse button is released
        if (IsMouseButtonReleased(MOUSE_BUTTON_LEFT)) settingColor = false;

        // Check if the user clicked/released the slider for the color's value
        if (sliderHover && IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) sliderClicked = true;

        if (sliderClicked && IsMouseButtonReleased(MOUSE_BUTTON_LEFT)) sliderClicked = false;

        // Update render mode accordingly
        if (IsKeyPressed(KEY_SPACE)) renderType = RL_LINES;

        if (IsKeyReleased(KEY_SPACE)) renderType = RL_TRIANGLES;

        // If the slider or the wheel was clicked, update the current color
        if (settingColor || sliderClicked)
        {
            if (settingColor) circlePosition = GetMousePosition();

            float distance = Vector2Distance(center, circlePosition)/pointScale;

            float angle = ((Vector2Angle((Vector2){ 0.0f, -pointScale }, Vector2Subtract(center, circlePosition))/PI + 1.0f)/2.0f);
            if (settingColor && distance > 1.0f) circlePosition = Vector2Add((Vector2){ sinf(angle*(PI*2.0f))*pointScale, -cosf(angle*(PI* 2.0f))*pointScale }, center);

            float angle360 = angle*360.0f;
            float valueActual = Clamp(distance, 0.0f, 1.0f);
            color = ColorLerp((Color){ (int)(value*255.0f), (int)(value*255.0f), (int)(value*255.0f), 255 }, ColorFromHSV(angle360, Clamp(distance, 0.0f, 1.0f), 1.0f), valueActual);
        }
        //----------------------------------------------------------------------------------

        // Draw
        //----------------------------------------------------------------------------------
        BeginDrawing();

        ClearBackground(RAYWHITE);

        // Begin rendering color wheel
        rlBegin(renderType);
        for (unsigned int i = 0; i < triangleCount; i++)
        {
            float angleOffset = ((PI*2.0f)/(float)triangleCount);
            float angle = angleOffset*(float)i;
            float angleOffsetCalculated = ((float)i + 1)*angleOffset;
            Vector2 scale = (Vector2){ pointScale, pointScale };

            Vector2 offset = Vector2Multiply((Vector2){ sinf(angle), -cosf(angle) }, scale);
            Vector2 offset2 = Vector2Multiply((Vector2){ sinf(angleOffsetCalculated), -cosf(angleOffsetCalculated) }, scale);

            Vector2 position = Vector2Add(center, offset);
            Vector2 position2 = Vector2Add(center, offset2);

            float angleNonRadian = (angle/(2.0f*PI))*360.0f;
            float angleNonRadianOffset = (angleOffset/(2.0f*PI))*360.0f;

            Color currentColor = ColorFromHSV(angleNonRadian, 1.0f, 1.0f);
            Color offsetColor = ColorFromHSV(angleNonRadian + angleNonRadianOffset, 1.0f, 1.0f);

            // Input vertices differently depending on mode
            if (renderType == RL_TRIANGLES)
            {
                // RL_TRIANGLES expects three vertices per triangle
                rlColor4ub(currentColor.r, currentColor.g, currentColor.b, currentColor.a);
                rlVertex2f(position.x, position.y);
                rlColor4f(value, value, value, 1.0f);
                rlVertex2f(center.x, center.y);
                rlColor4ub(offsetColor.r, offsetColor.g, offsetColor.b, offsetColor.a);
                rlVertex2f(position2.x, position2.y);
            }
            else if (renderType == RL_LINES)
            {
                // RL_LINES expects two vertices per line
                rlColor4ub(currentColor.r, currentColor.g, currentColor.b, currentColor.a);
                rlVertex2f(position.x, position.y);
                rlColor4ub(WHITE.r, WHITE.g, WHITE.b, WHITE.a);
                rlVertex2f(center.x, center.y);

                rlVertex2f(center.x, center.y);
                rlColor4ub(offsetColor.r, offsetColor.g, offsetColor.b, offsetColor.a);
                rlVertex2f(position2.x, position2.y);

                rlVertex2f(position2.x, position2.y);
                rlColor4ub(currentColor.r, currentColor.g, currentColor.b, currentColor.a);
                rlVertex2f(position.x, position.y);
            }
        }
        rlEnd();

        // Make the handle slightly more visible overtop darker colors
        Color handleColor = BLACK;

        if (Vector2Distance(center, circlePosition)/pointScale <= 0.5f && value <= 0.5f)
        {
            handleColor = DARKGRAY;
        }

        // Draw the color handle
        DrawCircleLinesV(circlePosition, 4.0f, handleColor);

        // Draw the color in a preview, with a darkened outline.
        DrawRectangleV((Vector2){ 8.0f, 8.0f }, (Vector2){ 64.0f, 64.0f }, color);
        DrawRectangleLinesEx((Rectangle){ 8.0f, 8.0f, 64.0f, 64.0f }, 2.0f, ColorLerp(color, BLACK, 0.5f));

        // Draw current color as hex and decimal
        DrawText(TextFormat("#%02X%02X%02X\n(%d, %d, %d)", color.r, color.g, color.b, color.r, color.g, color.b), 8, 8 + 64 + 8, 20, DARKGRAY);

        // Update the visuals for the copying text
        Color copyColor = DARKGRAY;
        unsigned int offset = 0;
        if (IsKeyDown(KEY_LEFT_CONTROL) && IsKeyDown(KEY_C))
        {
            copyColor = DARKGREEN;
            offset = 4;
        }

        // Draw the copying text
        DrawText("press ctrl+c to copy!", 8, 425 - offset, 20, copyColor);

        // Display the number of rendered triangles
        DrawText(TextFormat("triangle count: %d", triangleCount), 8, 395, 20, DARKGRAY);

        // Slider to change color's value
        GuiSliderBar(sliderRectangle, "value: ", "", &value, 0.0f, 1.0f);

        // Draw FPS next to outlined color preview
        DrawFPS(64 + 16, 8);

        EndDrawing();
        //----------------------------------------------------------------------------------
    }

    // De-Initialization
    //--------------------------------------------------------------------------------------
    CloseWindow();        // Close window and OpenGL context
    //--------------------------------------------------------------------------------------

    return 0;
}

From raylib 6.2. The original on GitHub.