From 7d4761b878f43671024ce197f721827b5355a67e Mon Sep 17 00:00:00 2001 From: pajanowski Date: Mon, 12 Jan 2026 12:20:29 -0600 Subject: [PATCH] Add floating_window example (#300) --- build.zig | 5 + examples/gui/floating_window.zig | 167 +++++++++++++++++++++++++++++++ 2 files changed, 172 insertions(+) create mode 100644 examples/gui/floating_window.zig diff --git a/build.zig b/build.zig index 89d3e09..de3b8f3 100644 --- a/build.zig +++ b/build.zig @@ -185,6 +185,11 @@ pub fn build(b: *std.Build) !void { .path = "examples/gui/message_box.zig", .desc = "Demonstrates showing and hiding a message box", }, + .{ + .name = "floating_window", + .path = "examples/gui/floating_window.zig", + .desc = "Demonstrates a floating window", + }, .{ .name = "raymarching", .path = "examples/shaders/raymarching.zig", diff --git a/examples/gui/floating_window.zig b/examples/gui/floating_window.zig new file mode 100644 index 0000000..ab86f3e --- /dev/null +++ b/examples/gui/floating_window.zig @@ -0,0 +1,167 @@ +const std = @import("std"); +const rl = @import("raylib"); +const rg = @import("raygui"); + +const WINDOW_STATUS_BAR_HEIGHT = 24; +const WINDOW_CLOSE_BUTTON_SIZE = 18; +const CLOSE_TITLE_SIZE_DELTA_HALF = (WINDOW_STATUS_BAR_HEIGHT - WINDOW_CLOSE_BUTTON_SIZE) / 2; + +const DrawContentFn = *const fn(rl.Vector2, rl.Vector2) void; + +fn floatingWindow( + position: *rl.Vector2, + size: *rl.Vector2, + minimized: *bool, + moving: *bool, + resizing: *bool, + draw_content: DrawContentFn, + content_size: rl.Vector2, + scroll: *rl.Vector2, + title: []const u8 +) void { + var title_buf: [64]u8 = undefined; + const title_text = std.fmt.bufPrintZ(&title_buf, "{s}", .{ title }) catch ""; + const mouse_position = rl.getMousePosition(); + + const is_left_pressed = rl.isMouseButtonPressed(rl.MouseButton.left); + if(is_left_pressed and !(moving.*) and !(resizing.*)) { + + const title_collsion_rect = rl.Rectangle{.x = position.x, .y = position.y, .width = size.x - WINDOW_CLOSE_BUTTON_SIZE - CLOSE_TITLE_SIZE_DELTA_HALF, .height = WINDOW_STATUS_BAR_HEIGHT}; + const resize_collision_rect = rl.Rectangle{.x = position.x + size.x - 20, .y = position.y + size.y - 20, .width = 20, .height = 20}; + + _ = rl.drawRectangleLinesEx(title_collsion_rect, 15, rl.Color.red); + _ = rl.drawRectangleLinesEx(resize_collision_rect, 15, rl.Color.green); + if(rl.checkCollisionPointRec(mouse_position, title_collsion_rect)) { + moving.* = true; + } else if(!(minimized.*) and rl.checkCollisionPointRec(mouse_position, resize_collision_rect)) { + resizing.* = true; + } + } + + const screen_width = rl.getScreenWidth(); + const screen_width_f32 = @as(f32, @floatFromInt(screen_width)); + const screen_height = rl.getScreenHeight(); + const screen_height_f32 = @as(f32, @floatFromInt(screen_height)); + // window movement and resize update + if(moving.*) { + const mouse_delta = rl.getMouseDelta(); + position.x += mouse_delta.x; + position.y += mouse_delta.y; + + if(rl.isMouseButtonReleased(rl.MouseButton.left)) { + moving.* = false; + + if(position.x < 0) { + position.x = 0; + } else if(position.x > screen_width_f32 - size.x) { + position.x = screen_width_f32 - size.x; + } + if(position.y < 0) { + position.x = 0; + } else if(position.y > screen_height_f32) { + position.y = screen_height_f32 - WINDOW_STATUS_BAR_HEIGHT; + } + } + } else if(resizing.*) { + if (mouse_position.x > position.x) { + size.x = mouse_position.x - position.x; + } + if (mouse_position.y > position.y) { + size.y = mouse_position.y - position.y; + } + // clamp window size to an arbitrary minimum value and the window size as the maximum + const min_window_size = 100; + if(size.x < min_window_size) { + size.x = min_window_size; + } else if(size.x > screen_width_f32) { + size.x = screen_width_f32; + } + if(size.y < min_window_size) { + size.y = min_window_size; + } else if(size.y > screen_height_f32) { + size.y = screen_height_f32; + } + + if (rl.isMouseButtonReleased(rl.MouseButton.left)) { + resizing.* = false; + } + } + // window and content drawing with scissor and scroll area + if(minimized.*) { + _ = rg.statusBar(rl.Rectangle{ .x = position.x, .y = position.y, .width = size.x, .height = WINDOW_STATUS_BAR_HEIGHT}, title_text); + + if (rg.button(rl.Rectangle{ .x = position.x + size.x - WINDOW_CLOSE_BUTTON_SIZE - CLOSE_TITLE_SIZE_DELTA_HALF, + .y = position.y + CLOSE_TITLE_SIZE_DELTA_HALF, + .width = WINDOW_CLOSE_BUTTON_SIZE, + .height = WINDOW_CLOSE_BUTTON_SIZE}, + "#120#")) { + minimized.* = false; + } + + } else { + minimized.* = rg.windowBox(rl.Rectangle{ .x = position.x, .y = position.y, .width = size.x, .height = size.y}, title_text) > 0; + + // scissor and draw content within a scroll panel + var scissor: rl.Rectangle = undefined; + _ = rg.scrollPanel(rl.Rectangle{ .x = position.x, .y = position.y + WINDOW_STATUS_BAR_HEIGHT, .width = size.x, .height = size.y - WINDOW_STATUS_BAR_HEIGHT}, + null, + rl.Rectangle{ .x = position.x, .y = position.y, .width = content_size.x, .height = content_size.y }, + scroll, + &scissor); + + _ = rl.drawRectangleRec(scissor, rl.Color.gold); + + const require_scissor = size.x < content_size.x or size.y < content_size.y; + + if(require_scissor) { + rl.beginScissorMode(@intFromFloat(scissor.x), @intFromFloat(scissor.y), @intFromFloat(scissor.width), @intFromFloat(scissor.height)); + } + + draw_content(position.*, scroll.*); + + if(require_scissor) { + rl.endScissorMode(); + } + + // draw the resize button/icon + _ = rg.drawIcon(71, @intFromFloat(position.x + size.x - 20), @intFromFloat(position.y + size.y - 20), 1, rl.Color.gray); + + } +} + +fn drawContent(position: rl.Vector2, window_scroll: rl.Vector2) void { + _ = rg.button(rl.Rectangle{ .x = position.x + 20 + window_scroll.x, .y = position.y + 50 + window_scroll.y, .width = 100, .height = 25 }, "Button 1"); + _ = rg.button(rl.Rectangle{ .x = position.x + 20 + window_scroll.x, .y = position.y + 100 + window_scroll.y, .width = 100, .height = 25 }, "Button 2"); + _ = rg.button(rl.Rectangle{ .x = position.x + 20 + window_scroll.x, .y = position.y + 150 + window_scroll.y, .width = 100, .height = 25 }, "Button 3"); + _ = rg.label(rl.Rectangle{ .x = position.x + 20 + window_scroll.x, .y = position.y + 200 + window_scroll.y, .width = 250, .height = 25 }, "A Label"); + _ = rg.label(rl.Rectangle{ .x = position.x + 20 + window_scroll.x, .y = position.y + 250 + window_scroll.y, .width = 250, .height = 25 }, "Another Label"); + _ = rg.label(rl.Rectangle{ .x = position.x + 20 + window_scroll.x, .y = position.y + 300 + window_scroll.y, .width = 250, .height = 25 }, "Yet Another Label"); +} + +pub fn main() !void { + var window_position = rl.Vector2{ .x = 10, .y = 10}; + var window2_position = rl.Vector2{ .x = 250, .y = 10}; + var window_size = rl.Vector2{ .x = 200, .y = 400}; + var window2_size = rl.Vector2{ .x = 200, .y = 400}; + var minimized = false; + var minimized2 = false; + var moving = false; + var moving2 = false; + var resizing = false; + var resizing2 = false; + var scroll: rl.Vector2 = rl.Vector2{ .x = -1, .y = -1}; + var scroll2: rl.Vector2 = rl.Vector2{ .x = -1, .y = -1}; + + rl.initWindow(960, 560, "raygui - floating window example"); + rl.setTargetFPS(60); + + while(!rl.windowShouldClose()) { + rl.beginDrawing(); + defer rl.endDrawing(); + floatingWindow(&window_position, &window_size, &minimized, &moving, &resizing, &drawContent, rl.Vector2{ .x = 140, .y = 320 }, &scroll, "Movable & Scalable Window"); + floatingWindow(&window2_position, &window2_size, &minimized2, &moving2, &resizing2, &drawContent, rl.Vector2{ .x = 140, .y = 320 }, &scroll2, "Another window"); + rl.clearBackground(rl.Color.ray_white); + } + + rl.closeWindow(); +} \ No newline at end of file