Nueva primitiva de dibujo para gráficos 3D: - FilledTriangleCommand en command.zig - Algoritmo scanline en software.zig - Ordena vértices por Y, interpola bordes - Soporta clipping correctamente Base para logos 3D sólidos y widgets avanzados. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
499 lines
12 KiB
Zig
499 lines
12 KiB
Zig
//! 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,
|
|
}
|
|
}
|