//! DrawCommand - Rendering commands for the software rasterizer //! //! The immediate mode UI generates a list of DrawCommands each frame. //! The software rasterizer then processes these commands to produce pixels. //! //! This is the "command list" approach (like microui), not vertex buffers. const Style = @import("style.zig"); /// A single draw command pub const DrawCommand = union(enum) { /// Draw a filled rectangle rect: RectCommand, /// Draw a filled rounded rectangle (fancy mode) rounded_rect: RoundedRectCommand, /// Draw text text: TextCommand, /// Draw a line line: LineCommand, /// Draw a rectangle outline (border) rect_outline: RectOutlineCommand, /// Draw a rounded rectangle outline (fancy mode) rounded_rect_outline: RoundedRectOutlineCommand, /// Draw a multi-layer shadow (fancy mode) shadow: ShadowCommand, /// Draw a gradient-filled rectangle (fancy mode) gradient: GradientCommand, /// Draw a filled triangle (for 3D graphics) filled_triangle: FilledTriangleCommand, /// Begin clipping to a rectangle clip: ClipCommand, /// End clipping clip_end, /// No operation (placeholder) nop, }; /// Draw a filled rectangle pub const RectCommand = struct { x: i32, y: i32, w: u32, h: u32, color: Style.Color, }; /// Draw text at a position pub const TextCommand = struct { x: i32, y: i32, text: []const u8, color: Style.Color, /// null means default font font: ?*anyopaque = null, }; /// Draw a line between two points pub const LineCommand = struct { x1: i32, y1: i32, x2: i32, y2: i32, color: Style.Color, }; /// Draw a rectangle outline pub const RectOutlineCommand = struct { x: i32, y: i32, w: u32, h: u32, color: Style.Color, thickness: u32 = 1, }; /// Draw a filled rounded rectangle (fancy mode) pub const RoundedRectCommand = struct { x: i32, y: i32, w: u32, h: u32, color: Style.Color, radius: u8, aa: bool = true, }; /// Draw a rounded rectangle outline (fancy mode) pub const RoundedRectOutlineCommand = struct { x: i32, y: i32, w: u32, h: u32, color: Style.Color, radius: u8, thickness: u8 = 1, aa: bool = true, }; /// Draw a multi-layer shadow (fancy mode) /// The shadow is drawn as multiple expanding layers with decreasing alpha /// to create a soft blur effect without actual blur computation. pub const ShadowCommand = struct { x: i32, y: i32, w: u32, h: u32, /// Shadow color (alpha controls overall opacity) color: Style.Color = Style.Color.rgba(0, 0, 0, 100), /// Corner radius for rounded shadow radius: u8 = 6, /// Horizontal offset (positive = right) offset_x: i8 = 3, /// Vertical offset (positive = down) offset_y: i8 = 3, /// Blur radius - number of layers to draw (0 = hard shadow) blur: u8 = 6, /// Spread - expand shadow beyond element bounds spread: i8 = 0, }; /// Gradient direction pub const GradientDirection = enum { /// Top to bottom vertical, /// Left to right horizontal, /// Top-left to bottom-right diagonal_down, /// Bottom-left to top-right diagonal_up, }; /// Draw a gradient-filled rectangle (fancy mode) pub const GradientCommand = struct { x: i32, y: i32, w: u32, h: u32, /// Start color (top for vertical, left for horizontal) start_color: Style.Color, /// End color (bottom for vertical, right for horizontal) end_color: Style.Color, /// Gradient direction direction: GradientDirection = .vertical, /// Corner radius (0 = square) radius: u8 = 0, }; /// Draw a filled triangle (for 3D graphics, logos, etc.) pub const FilledTriangleCommand = struct { /// First vertex x1: i32, y1: i32, /// Second vertex x2: i32, y2: i32, /// Third vertex x3: i32, y3: i32, /// Fill color color: Style.Color, }; /// Begin clipping to a rectangle pub const ClipCommand = struct { x: i32, y: i32, w: u32, h: u32, }; // ============================================================================= // Helper constructors // ============================================================================= /// Create a rect command pub fn rect(x: i32, y: i32, w: u32, h: u32, color: Style.Color) DrawCommand { return .{ .rect = .{ .x = x, .y = y, .w = w, .h = h, .color = color, } }; } /// Create a text command pub fn text(x: i32, y: i32, str: []const u8, color: Style.Color) DrawCommand { return .{ .text = .{ .x = x, .y = y, .text = str, .color = color, } }; } /// Create a line command pub fn line(x1: i32, y1: i32, x2: i32, y2: i32, color: Style.Color) DrawCommand { return .{ .line = .{ .x1 = x1, .y1 = y1, .x2 = x2, .y2 = y2, .color = color, } }; } /// Create a rect outline command pub fn rectOutline(x: i32, y: i32, w: u32, h: u32, color: Style.Color) DrawCommand { return .{ .rect_outline = .{ .x = x, .y = y, .w = w, .h = h, .color = color, } }; } /// Create a clip command pub fn clip(x: i32, y: i32, w: u32, h: u32) DrawCommand { return .{ .clip = .{ .x = x, .y = y, .w = w, .h = h, } }; } /// Create a clip end command pub fn clipEnd() DrawCommand { return .clip_end; } /// Create a filled triangle command pub fn filledTriangle(x1: i32, y1: i32, x2: i32, y2: i32, x3: i32, y3: i32, color: Style.Color) DrawCommand { return .{ .filled_triangle = .{ .x1 = x1, .y1 = y1, .x2 = x2, .y2 = y2, .x3 = x3, .y3 = y3, .color = color, } }; } /// Create a rounded rect command (fancy mode) pub fn roundedRect(x: i32, y: i32, w: u32, h: u32, color: Style.Color, radius: u8) DrawCommand { return .{ .rounded_rect = .{ .x = x, .y = y, .w = w, .h = h, .color = color, .radius = radius, .aa = true, } }; } /// Create a rounded rect command with configurable AA pub fn roundedRectAA(x: i32, y: i32, w: u32, h: u32, color: Style.Color, radius: u8, aa: bool) DrawCommand { return .{ .rounded_rect = .{ .x = x, .y = y, .w = w, .h = h, .color = color, .radius = radius, .aa = aa, } }; } /// Create a rounded rect outline command (fancy mode) pub fn roundedRectOutline(x: i32, y: i32, w: u32, h: u32, color: Style.Color, radius: u8) DrawCommand { return .{ .rounded_rect_outline = .{ .x = x, .y = y, .w = w, .h = h, .color = color, .radius = radius, .thickness = 1, .aa = true, } }; } /// Create a focus ring command (visual focus indicator with AA) /// Draws a rounded outline outside the widget bounds with semi-transparent primary color. /// Use when a widget has keyboard focus to provide clear visual feedback. pub fn focusRing(x: i32, y: i32, w: u32, h: u32, radius: u8) DrawCommand { const offset: i32 = 2; // Draw outside widget bounds const focus_color = Style.Color.primary.withAlpha(180); // Semi-transparent const focus_radius = if (radius > 0) radius + 2 else 0; return .{ .rounded_rect_outline = .{ .x = x - offset, .y = y - offset, .w = w + @as(u32, @intCast(offset * 2)), .h = h + @as(u32, @intCast(offset * 2)), .color = focus_color, .radius = focus_radius, .thickness = 2, .aa = true, } }; } /// Create a focus ring with custom color pub fn focusRingColor(x: i32, y: i32, w: u32, h: u32, radius: u8, color: Style.Color) DrawCommand { const offset: i32 = 2; const focus_radius = if (radius > 0) radius + 2 else 0; return .{ .rounded_rect_outline = .{ .x = x - offset, .y = y - offset, .w = w + @as(u32, @intCast(offset * 2)), .h = h + @as(u32, @intCast(offset * 2)), .color = color, .radius = focus_radius, .thickness = 2, .aa = true, } }; } /// Create a soft shadow command (fancy mode) /// The shadow will be drawn as multiple layers creating a blur effect. pub fn shadow(x: i32, y: i32, w: u32, h: u32, radius: u8) DrawCommand { return .{ .shadow = .{ .x = x, .y = y, .w = w, .h = h, .radius = radius, .color = Style.Color.rgba(0, 0, 0, 80), .offset_x = 3, .offset_y = 3, .blur = 6, .spread = 0, } }; } /// Create a shadow with custom parameters pub fn shadowEx( x: i32, y: i32, w: u32, h: u32, radius: u8, color: Style.Color, offset_x: i8, offset_y: i8, blur: u8, spread: i8, ) DrawCommand { return .{ .shadow = .{ .x = x, .y = y, .w = w, .h = h, .radius = radius, .color = color, .offset_x = offset_x, .offset_y = offset_y, .blur = blur, .spread = spread, } }; } /// Create a subtle drop shadow (common preset) pub fn shadowDrop(x: i32, y: i32, w: u32, h: u32, radius: u8) DrawCommand { return .{ .shadow = .{ .x = x, .y = y, .w = w, .h = h, .radius = radius, .color = Style.Color.rgba(0, 0, 0, 60), .offset_x = 2, .offset_y = 4, .blur = 8, .spread = 0, } }; } /// Create a larger shadow for modals/floating elements pub fn shadowFloat(x: i32, y: i32, w: u32, h: u32, radius: u8) DrawCommand { return .{ .shadow = .{ .x = x, .y = y, .w = w, .h = h, .radius = radius, .color = Style.Color.rgba(0, 0, 0, 100), .offset_x = 0, .offset_y = 8, .blur = 16, .spread = 2, } }; } /// Create a vertical gradient (top to bottom) pub fn gradientV(x: i32, y: i32, w: u32, h: u32, start: Style.Color, end: Style.Color) DrawCommand { return .{ .gradient = .{ .x = x, .y = y, .w = w, .h = h, .start_color = start, .end_color = end, .direction = .vertical, .radius = 0, } }; } /// Create a horizontal gradient (left to right) pub fn gradientH(x: i32, y: i32, w: u32, h: u32, start: Style.Color, end: Style.Color) DrawCommand { return .{ .gradient = .{ .x = x, .y = y, .w = w, .h = h, .start_color = start, .end_color = end, .direction = .horizontal, .radius = 0, } }; } /// Create a rounded vertical gradient pub fn gradientVRounded(x: i32, y: i32, w: u32, h: u32, start: Style.Color, end: Style.Color, radius: u8) DrawCommand { return .{ .gradient = .{ .x = x, .y = y, .w = w, .h = h, .start_color = start, .end_color = end, .direction = .vertical, .radius = radius, } }; } /// Create a button-style gradient (subtle 3D effect) /// Lighter on top, darker on bottom pub fn gradientButton(x: i32, y: i32, w: u32, h: u32, base_color: Style.Color, radius: u8) DrawCommand { return .{ .gradient = .{ .x = x, .y = y, .w = w, .h = h, .start_color = base_color.lighten(15), .end_color = base_color.darken(10), .direction = .vertical, .radius = radius, } }; } /// Create a progress bar gradient (vibrant left-to-right) pub fn gradientProgress(x: i32, y: i32, w: u32, h: u32, base_color: Style.Color) DrawCommand { return .{ .gradient = .{ .x = x, .y = y, .w = w, .h = h, .start_color = base_color.lighten(20), .end_color = base_color.darken(15), .direction = .horizontal, .radius = 0, } }; } // ============================================================================= // Tests // ============================================================================= const std = @import("std"); test "DrawCommand creation" { const cmd = rect(10, 20, 100, 50, Style.Color.red); switch (cmd) { .rect => |r| { try std.testing.expectEqual(@as(i32, 10), r.x); try std.testing.expectEqual(@as(i32, 20), r.y); try std.testing.expectEqual(@as(u32, 100), r.w); try std.testing.expectEqual(@as(u32, 50), r.h); }, else => unreachable, } }