|
const std = @import("std"); |
|
|
|
const c = @cImport({ |
|
@cInclude("SDL3/SDL.h"); |
|
@cInclude("SDL3_ttf/SDL_ttf.h"); |
|
}); |
|
|
|
// ====================== FAKE WINDOW PANEL MODULE ====================== |
|
const FakeWindow = struct { |
|
x: f32 = 150, |
|
y: f32 = 120, |
|
width: f32 = 500, |
|
height: f32 = 320, |
|
|
|
is_dragging: bool = false, |
|
drag_offset_x: f32 = 0, |
|
drag_offset_y: f32 = 0, |
|
close_hovered: bool = false, |
|
|
|
const Self = @This(); |
|
|
|
pub fn handleEvent(self: *Self, event: *c.SDL_Event) bool { |
|
switch (event.type) { |
|
// c.SDL_EVENT_QUIT => return true, |
|
|
|
c.SDL_EVENT_MOUSE_BUTTON_DOWN => { |
|
if (event.button.button != c.SDL_BUTTON_LEFT) return false; |
|
|
|
const mx: f32 = event.button.x; |
|
const my: f32 = event.button.y; |
|
|
|
const title_bar = c.SDL_FRect{ .x = self.x, .y = self.y, .w = self.width, .h = 42 }; |
|
|
|
// Close button |
|
const close_btn = c.SDL_FRect{ .x = self.x + self.width - 50, .y = self.y + 8, .w = 36, .h = 28 }; |
|
if (mx >= close_btn.x and mx <= close_btn.x + close_btn.w and |
|
my >= close_btn.y and my <= close_btn.y + close_btn.h) |
|
{ |
|
return true; // Request close |
|
} |
|
|
|
// Start dragging on title bar |
|
if (mx >= title_bar.x and mx <= title_bar.x + title_bar.w and |
|
my >= title_bar.y and my <= title_bar.y + title_bar.h) |
|
{ |
|
if (!(mx >= close_btn.x and mx <= close_btn.x + close_btn.w)) { |
|
self.is_dragging = true; |
|
self.drag_offset_x = mx - self.x; |
|
self.drag_offset_y = my - self.y; |
|
} |
|
} |
|
}, |
|
|
|
c.SDL_EVENT_MOUSE_BUTTON_UP => { |
|
if (event.button.button == c.SDL_BUTTON_LEFT) { |
|
self.is_dragging = false; |
|
} |
|
}, |
|
|
|
c.SDL_EVENT_MOUSE_MOTION => { |
|
const mx: f32 = event.motion.x; |
|
const my: f32 = event.motion.y; |
|
|
|
if (self.is_dragging) { |
|
self.x = mx - self.drag_offset_x; |
|
self.y = my - self.drag_offset_y; |
|
} else { |
|
const close_btn = c.SDL_FRect{ .x = self.x + self.width - 50, .y = self.y + 8, .w = 36, .h = 28 }; |
|
self.close_hovered = (mx >= close_btn.x and mx <= close_btn.x + close_btn.w and |
|
my >= close_btn.y and my <= close_btn.y + close_btn.h); |
|
} |
|
}, |
|
else => {}, |
|
} |
|
return false; |
|
} |
|
|
|
pub fn render(self: *const Self, renderer: ?*c.SDL_Renderer, font: ?*c.TTF_Font, close_font: ?*c.TTF_Font) void { |
|
const panel = c.SDL_FRect{ .x = self.x, .y = self.y, .w = self.width, .h = self.height }; |
|
|
|
// Shadow |
|
const shadow = c.SDL_FRect{ .x = self.x + 10, .y = self.y + 10, .w = self.width, .h = self.height }; |
|
_ = c.SDL_SetRenderDrawColor(renderer, 0, 0, 0, 100); |
|
_ = c.SDL_RenderFillRect(renderer, &shadow); |
|
|
|
// Panel body |
|
_ = c.SDL_SetRenderDrawColor(renderer, 45, 55, 70, 255); |
|
_ = c.SDL_RenderFillRect(renderer, &panel); |
|
|
|
// Border |
|
_ = c.SDL_SetRenderDrawColor(renderer, 100, 180, 255, 255); |
|
_ = c.SDL_RenderRect(renderer, &panel); |
|
|
|
// Title bar |
|
const title_bar = c.SDL_FRect{ .x = self.x, .y = self.y, .w = self.width, .h = 42 }; |
|
_ = c.SDL_SetRenderDrawColor(renderer, 30, 100, 180, 255); |
|
_ = c.SDL_RenderFillRect(renderer, &title_bar); |
|
|
|
// Title bar separator |
|
_ = c.SDL_SetRenderDrawColor(renderer, 80, 160, 255, 255); |
|
_ = c.SDL_RenderLine(renderer, title_bar.x, title_bar.y + title_bar.h - 1, title_bar.x + title_bar.w, title_bar.y + title_bar.h - 1); |
|
|
|
// Close Button |
|
const close_btn = c.SDL_FRect{ .x = self.x + self.width - 50, .y = self.y + 8, .w = 36, .h = 28 }; |
|
if (self.close_hovered) { |
|
_ = c.SDL_SetRenderDrawColor(renderer, 220, 50, 50, 255); |
|
} else { |
|
_ = c.SDL_SetRenderDrawColor(renderer, 200, 60, 60, 255); |
|
} |
|
_ = c.SDL_RenderFillRect(renderer, &close_btn); |
|
|
|
if (self.close_hovered) { |
|
_ = c.SDL_SetRenderDrawColor(renderer, 255, 120, 120, 255); |
|
} else { |
|
_ = c.SDL_SetRenderDrawColor(renderer, 255, 100, 100, 255); |
|
} |
|
_ = c.SDL_RenderRect(renderer, &close_btn); |
|
|
|
// × symbol |
|
if (close_font) |f| { |
|
const surf = c.TTF_RenderText_Blended(f, "×", 0, c.SDL_Color{ .r = 255, .g = 255, .b = 255, .a = 255 }); |
|
if (surf) |s| { |
|
defer c.SDL_DestroySurface(s); |
|
if (c.SDL_CreateTextureFromSurface(renderer, s)) |tex| { |
|
defer c.SDL_DestroyTexture(tex); |
|
const rect = c.SDL_FRect{ |
|
.x = close_btn.x + (close_btn.w - @as(f32, @floatFromInt(s.*.w))) / 2, |
|
.y = close_btn.y + (close_btn.h - @as(f32, @floatFromInt(s.*.h))) / 2 - 2, |
|
.w = @floatFromInt(s.*.w), |
|
.h = @floatFromInt(s.*.h), |
|
}; |
|
_ = c.SDL_RenderTexture(renderer, tex, null, &rect); |
|
} |
|
} |
|
} |
|
|
|
// Title text |
|
if (font) |f| { |
|
const surf = c.TTF_RenderText_Blended(f, "Panel Title", 0, c.SDL_Color{ .r = 255, .g = 255, .b = 255, .a = 255 }); |
|
if (surf) |s| { |
|
defer c.SDL_DestroySurface(s); |
|
if (c.SDL_CreateTextureFromSurface(renderer, s)) |tex| { |
|
defer c.SDL_DestroyTexture(tex); |
|
const rect = c.SDL_FRect{ |
|
.x = self.x + 16, |
|
.y = self.y + 9, |
|
.w = @floatFromInt(s.*.w), |
|
.h = @floatFromInt(s.*.h), |
|
}; |
|
_ = c.SDL_RenderTexture(renderer, tex, null, &rect); |
|
} |
|
} |
|
} |
|
} |
|
}; |
|
|
|
// ========================== MAIN ========================== |
|
pub fn main() !void { |
|
if (!c.SDL_Init(c.SDL_INIT_VIDEO)) return error.SDLInitFailed; |
|
defer c.SDL_Quit(); |
|
|
|
if (!c.TTF_Init()) return error.TTFInitFailed; |
|
defer c.TTF_Quit(); |
|
|
|
var window: ?*c.SDL_Window = null; |
|
var renderer: ?*c.SDL_Renderer = null; |
|
if (!c.SDL_CreateWindowAndRenderer("Zig Modular UI Demo", 800, 600, 0, &window, &renderer)) { |
|
return error.WindowCreationFailed; |
|
} |
|
defer c.SDL_DestroyRenderer(renderer); |
|
defer c.SDL_DestroyWindow(window); |
|
|
|
const font = c.TTF_OpenFont("Kenney Pixel.ttf", 32); |
|
const close_font = c.TTF_OpenFont("Kenney Pixel.ttf", 28); |
|
defer if (font != null) c.TTF_CloseFont(font); |
|
defer if (close_font != null) c.TTF_CloseFont(close_font); |
|
|
|
// Main content text |
|
const content_surf = c.TTF_RenderText_Blended(font, "Hello from Zig & SDL3!", 0, c.SDL_Color{ .r = 255, .g = 255, .b = 255, .a = 255 }); |
|
defer if (content_surf != null) c.SDL_DestroySurface(content_surf); |
|
|
|
const content_tex = if (content_surf != null) c.SDL_CreateTextureFromSurface(renderer, content_surf) else null; |
|
defer if (content_tex != null) c.SDL_DestroyTexture(content_tex); |
|
|
|
const text_w: f32 = if (content_surf != null) @floatFromInt(content_surf.?.*.w) else 0; |
|
const text_h: f32 = if (content_surf != null) @floatFromInt(content_surf.?.*.h) else 0; |
|
|
|
var panel = FakeWindow{}; |
|
|
|
var quit = false; |
|
var event: c.SDL_Event = undefined; |
|
|
|
while (!quit) { |
|
while (c.SDL_PollEvent(&event)) { |
|
// Escape key and Main Window X handling |
|
if (event.type == c.SDL_EVENT_QUIT or |
|
(event.type == c.SDL_EVENT_KEY_DOWN and event.key.key == c.SDLK_ESCAPE)) |
|
{ |
|
quit = true; |
|
} |
|
|
|
if (panel.handleEvent(&event)) { |
|
quit = true; |
|
} |
|
} |
|
|
|
_ = c.SDL_SetRenderDrawColor(renderer, 15, 20, 30, 255); |
|
_ = c.SDL_RenderClear(renderer); |
|
|
|
panel.render(renderer, font, close_font); |
|
|
|
// Content inside panel |
|
if (content_tex) |tex| { |
|
const content_rect = c.SDL_FRect{ |
|
.x = panel.x + (panel.width - text_w) / 2, |
|
.y = panel.y + 90, |
|
.w = text_w, |
|
.h = text_h, |
|
}; |
|
_ = c.SDL_RenderTexture(renderer, tex, null, &content_rect); |
|
} |
|
|
|
_ = c.SDL_RenderPresent(renderer); |
|
} |
|
} |