zcatgui/src/core/command.zig
R.Eugenio de56496803 feat: Añadir filledTriangle (rasterización scanline)
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>
2025-12-30 00:51:46 +01:00

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,
}
}