feat: Paridad Visual DVUI Fase 3 - Sombras y Gradientes
Nuevas capacidades de rendering: - ShadowCommand: sombras multi-capa con blur simulado - Helpers: shadow(), shadowDrop(), shadowFloat() - Quadratic alpha falloff para bordes suaves - GradientCommand: gradientes suaves pixel a pixel - Direcciones: vertical, horizontal, diagonal - Helpers: gradientV/H(), gradientButton(), gradientProgress() - Soporte esquinas redondeadas Widgets actualizados: - Panel/Modal: sombras en fancy mode - Select/Menu: dropdown con sombra + rounded corners - Tooltip/Toast: sombra sutil + rounded corners - Button: gradiente 3D (lighten top, darken bottom) - Progress: gradientes suaves vs 4 bandas IMPORTANTE: Compila y pasa tests (370/370) pero NO probado visualmente 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
83049a99be
commit
2dccddeab0
10 changed files with 568 additions and 92 deletions
|
|
@ -27,6 +27,12 @@ pub const DrawCommand = union(enum) {
|
|||
/// 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,
|
||||
|
||||
/// Begin clipping to a rectangle
|
||||
clip: ClipCommand,
|
||||
|
||||
|
|
@ -98,6 +104,56 @@ pub const RoundedRectOutlineCommand = struct {
|
|||
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,
|
||||
};
|
||||
|
||||
/// Begin clipping to a rectangle
|
||||
pub const ClipCommand = struct {
|
||||
x: i32,
|
||||
|
|
@ -245,6 +301,153 @@ pub fn focusRingColor(x: i32, y: i32, w: u32, h: u32, radius: u8, color: Style.C
|
|||
} };
|
||||
}
|
||||
|
||||
/// 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
|
||||
// =============================================================================
|
||||
|
|
|
|||
|
|
@ -91,6 +91,8 @@ pub const SoftwareRenderer = struct {
|
|||
.line => |l| self.drawLine(l),
|
||||
.rect_outline => |r| self.drawRectOutline(r),
|
||||
.rounded_rect_outline => |r| self.drawRoundedRectOutline(r),
|
||||
.shadow => |s| self.drawShadow(s),
|
||||
.gradient => |g| self.drawGradient(g),
|
||||
.clip => |c| self.pushClip(c),
|
||||
.clip_end => self.popClip(),
|
||||
.nop => {},
|
||||
|
|
@ -300,6 +302,181 @@ pub const SoftwareRenderer = struct {
|
|||
self.framebuffer.drawRoundedRect(r.x, r.y, r.w, r.h, r.color, r.radius, r.thickness, r.aa);
|
||||
}
|
||||
|
||||
/// Draw a multi-layer shadow to simulate blur effect
|
||||
/// Draws expanding layers with decreasing alpha, creating soft edges
|
||||
fn drawShadow(self: *Self, s: Command.ShadowCommand) void {
|
||||
if (s.blur == 0) {
|
||||
// Hard shadow - single solid rect
|
||||
const shadow_x = s.x + @as(i32, s.offset_x) - @as(i32, s.spread);
|
||||
const shadow_y = s.y + @as(i32, s.offset_y) - @as(i32, s.spread);
|
||||
const shadow_w = s.w +| @as(u32, @intCast(@abs(s.spread) * 2));
|
||||
const shadow_h = s.h +| @as(u32, @intCast(@abs(s.spread) * 2));
|
||||
|
||||
if (s.radius > 0) {
|
||||
self.framebuffer.fillRoundedRect(shadow_x, shadow_y, shadow_w, shadow_h, s.color, s.radius, false);
|
||||
} else {
|
||||
self.framebuffer.fillRect(shadow_x, shadow_y, shadow_w, shadow_h, s.color);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Soft shadow - draw multiple expanding layers with decreasing alpha
|
||||
// Each layer is larger and more transparent, creating a blur effect
|
||||
const layers: u8 = s.blur;
|
||||
const base_alpha = s.color.a;
|
||||
|
||||
// Calculate base shadow position
|
||||
const base_x = s.x + @as(i32, s.offset_x) - @as(i32, s.spread);
|
||||
const base_y = s.y + @as(i32, s.offset_y) - @as(i32, s.spread);
|
||||
const base_w = s.w +| @as(u32, @intCast(@abs(s.spread) * 2));
|
||||
const base_h = s.h +| @as(u32, @intCast(@abs(s.spread) * 2));
|
||||
|
||||
// Draw from outermost (most transparent) to innermost (most opaque)
|
||||
var layer: u8 = layers;
|
||||
while (layer > 0) {
|
||||
layer -= 1;
|
||||
|
||||
// Calculate alpha for this layer (quadratic falloff for softer edges)
|
||||
const t = @as(f32, @floatFromInt(layer)) / @as(f32, @floatFromInt(layers));
|
||||
const alpha_factor = (1.0 - t) * (1.0 - t); // Quadratic falloff
|
||||
const layer_alpha = @as(u8, @intFromFloat(@as(f32, @floatFromInt(base_alpha)) * alpha_factor * 0.5));
|
||||
|
||||
if (layer_alpha == 0) continue;
|
||||
|
||||
// Expand layer outward
|
||||
const expand = @as(i32, @intCast(layers - layer));
|
||||
const layer_x = base_x - expand;
|
||||
const layer_y = base_y - expand;
|
||||
const layer_w = base_w +| @as(u32, @intCast(expand * 2));
|
||||
const layer_h = base_h +| @as(u32, @intCast(expand * 2));
|
||||
const layer_radius = if (s.radius > 0) s.radius +| @as(u8, @intCast(@min(255 - s.radius, expand))) else 0;
|
||||
|
||||
const layer_color = Color.rgba(s.color.r, s.color.g, s.color.b, layer_alpha);
|
||||
|
||||
if (layer_radius > 0) {
|
||||
self.framebuffer.fillRoundedRect(layer_x, layer_y, layer_w, layer_h, layer_color, layer_radius, false);
|
||||
} else {
|
||||
self.framebuffer.fillRect(layer_x, layer_y, layer_w, layer_h, layer_color);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw core shadow (innermost, full opacity relative to input)
|
||||
const core_alpha = @as(u8, @intFromFloat(@as(f32, @floatFromInt(base_alpha)) * 0.7));
|
||||
if (core_alpha > 0) {
|
||||
const core_color = Color.rgba(s.color.r, s.color.g, s.color.b, core_alpha);
|
||||
if (s.radius > 0) {
|
||||
self.framebuffer.fillRoundedRect(base_x, base_y, base_w, base_h, core_color, s.radius, false);
|
||||
} else {
|
||||
self.framebuffer.fillRect(base_x, base_y, base_w, base_h, core_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw a gradient-filled rectangle
|
||||
fn drawGradient(self: *Self, g: Command.GradientCommand) void {
|
||||
if (g.w == 0 or g.h == 0) return;
|
||||
|
||||
const clip = self.getClip();
|
||||
|
||||
switch (g.direction) {
|
||||
.vertical => {
|
||||
// Draw horizontal bands from top to bottom
|
||||
var y: u32 = 0;
|
||||
while (y < g.h) : (y += 1) {
|
||||
const t = @as(f32, @floatFromInt(y)) / @as(f32, @floatFromInt(g.h -| 1));
|
||||
const color = interpolateColor(g.start_color, g.end_color, t);
|
||||
|
||||
const line_y = g.y + @as(i32, @intCast(y));
|
||||
const line_rect = Rect.init(g.x, line_y, g.w, 1).intersection(clip);
|
||||
|
||||
if (!line_rect.isEmpty()) {
|
||||
if (g.radius > 0 and (y < g.radius or y >= g.h - g.radius)) {
|
||||
// For rounded corners, calculate horizontal inset
|
||||
const corner_y = if (y < g.radius) g.radius - y else y - (g.h - g.radius);
|
||||
const r = @as(f32, @floatFromInt(g.radius));
|
||||
const cy = @as(f32, @floatFromInt(corner_y));
|
||||
const inset_f = r - @sqrt(r * r - cy * cy);
|
||||
const inset = @as(u32, @intFromFloat(@max(0, inset_f)));
|
||||
|
||||
if (g.w > inset * 2) {
|
||||
const inner_x = g.x + @as(i32, @intCast(inset));
|
||||
const inner_w = g.w - inset * 2;
|
||||
const inner_rect = Rect.init(inner_x, line_y, inner_w, 1).intersection(clip);
|
||||
if (!inner_rect.isEmpty()) {
|
||||
self.framebuffer.fillRect(inner_rect.x, inner_rect.y, inner_rect.w, inner_rect.h, color);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.framebuffer.fillRect(line_rect.x, line_rect.y, line_rect.w, line_rect.h, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
.horizontal => {
|
||||
// Draw vertical bands from left to right
|
||||
var x: u32 = 0;
|
||||
while (x < g.w) : (x += 1) {
|
||||
const t = @as(f32, @floatFromInt(x)) / @as(f32, @floatFromInt(g.w -| 1));
|
||||
const color = interpolateColor(g.start_color, g.end_color, t);
|
||||
|
||||
const line_x = g.x + @as(i32, @intCast(x));
|
||||
const line_rect = Rect.init(line_x, g.y, 1, g.h).intersection(clip);
|
||||
|
||||
if (!line_rect.isEmpty()) {
|
||||
if (g.radius > 0 and (x < g.radius or x >= g.w - g.radius)) {
|
||||
// For rounded corners, calculate vertical inset
|
||||
const corner_x = if (x < g.radius) g.radius - x else x - (g.w - g.radius);
|
||||
const r = @as(f32, @floatFromInt(g.radius));
|
||||
const cx = @as(f32, @floatFromInt(corner_x));
|
||||
const inset_f = r - @sqrt(r * r - cx * cx);
|
||||
const inset = @as(u32, @intFromFloat(@max(0, inset_f)));
|
||||
|
||||
if (g.h > inset * 2) {
|
||||
const inner_y = g.y + @as(i32, @intCast(inset));
|
||||
const inner_h = g.h - inset * 2;
|
||||
const inner_rect = Rect.init(line_x, inner_y, 1, inner_h).intersection(clip);
|
||||
if (!inner_rect.isEmpty()) {
|
||||
self.framebuffer.fillRect(inner_rect.x, inner_rect.y, inner_rect.w, inner_rect.h, color);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.framebuffer.fillRect(line_rect.x, line_rect.y, line_rect.w, line_rect.h, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
.diagonal_down, .diagonal_up => {
|
||||
// Draw pixel by pixel for diagonal gradients
|
||||
const max_dist = @as(f32, @floatFromInt(g.w + g.h -| 2));
|
||||
|
||||
var y: u32 = 0;
|
||||
while (y < g.h) : (y += 1) {
|
||||
var x: u32 = 0;
|
||||
while (x < g.w) : (x += 1) {
|
||||
const px = g.x + @as(i32, @intCast(x));
|
||||
const py = g.y + @as(i32, @intCast(y));
|
||||
|
||||
if (!clip.contains(px, py)) continue;
|
||||
|
||||
// Check rounded corners
|
||||
if (g.radius > 0) {
|
||||
if (!isInsideRoundedCorner(x, y, g.w, g.h, g.radius)) continue;
|
||||
}
|
||||
|
||||
const dist: f32 = if (g.direction == .diagonal_down)
|
||||
@as(f32, @floatFromInt(x + y))
|
||||
else
|
||||
@as(f32, @floatFromInt(x + (g.h -| 1 - y)));
|
||||
|
||||
const t = dist / max_dist;
|
||||
const color = interpolateColor(g.start_color, g.end_color, t);
|
||||
self.framebuffer.setPixel(px, py, color);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn pushClip(self: *Self, c: Command.ClipCommand) void {
|
||||
if (self.clip_depth >= self.clip_stack.len) return;
|
||||
|
||||
|
|
@ -349,6 +526,23 @@ fn commandBounds(cmd: DrawCommand) ?Rect {
|
|||
},
|
||||
.rect_outline => |r| Rect.init(r.x, r.y, r.w, r.h),
|
||||
.rounded_rect_outline => |r| Rect.init(r.x, r.y, r.w, r.h),
|
||||
.shadow => |s| blk: {
|
||||
// Shadow bounds include offset, blur, and spread expansion
|
||||
const blur_expand = @as(i32, s.blur);
|
||||
const spread_expand = @as(i32, @abs(s.spread));
|
||||
const total_expand = blur_expand + spread_expand;
|
||||
const min_offset_x = @min(0, @as(i32, s.offset_x));
|
||||
const min_offset_y = @min(0, @as(i32, s.offset_y));
|
||||
const max_offset_x = @max(0, @as(i32, s.offset_x));
|
||||
const max_offset_y = @max(0, @as(i32, s.offset_y));
|
||||
break :blk Rect.init(
|
||||
s.x + min_offset_x - total_expand,
|
||||
s.y + min_offset_y - total_expand,
|
||||
s.w +| @as(u32, @intCast(max_offset_x - min_offset_x + total_expand * 2)),
|
||||
s.h +| @as(u32, @intCast(max_offset_y - min_offset_y + total_expand * 2)),
|
||||
);
|
||||
},
|
||||
.gradient => |g| Rect.init(g.x, g.y, g.w, g.h),
|
||||
.clip, .clip_end, .nop => null,
|
||||
};
|
||||
}
|
||||
|
|
@ -363,6 +557,63 @@ fn rectsIntersect(a: Rect, b: Rect) bool {
|
|||
return a.x < b_right and a_right > b.x and a.y < b_bottom and a_bottom > b.y;
|
||||
}
|
||||
|
||||
/// Interpolate between two colors
|
||||
fn interpolateColor(a: Color, b: Color, t: f32) Color {
|
||||
const t_clamped = @max(0.0, @min(1.0, t));
|
||||
const inv_t = 1.0 - t_clamped;
|
||||
return Color.rgba(
|
||||
@intFromFloat(@as(f32, @floatFromInt(a.r)) * inv_t + @as(f32, @floatFromInt(b.r)) * t_clamped),
|
||||
@intFromFloat(@as(f32, @floatFromInt(a.g)) * inv_t + @as(f32, @floatFromInt(b.g)) * t_clamped),
|
||||
@intFromFloat(@as(f32, @floatFromInt(a.b)) * inv_t + @as(f32, @floatFromInt(b.b)) * t_clamped),
|
||||
@intFromFloat(@as(f32, @floatFromInt(a.a)) * inv_t + @as(f32, @floatFromInt(b.a)) * t_clamped),
|
||||
);
|
||||
}
|
||||
|
||||
/// Check if a point is inside the rounded corner area of a rectangle
|
||||
fn isInsideRoundedCorner(x: u32, y: u32, w: u32, h: u32, radius: u8) bool {
|
||||
const r = @as(u32, radius);
|
||||
|
||||
// Check if in corner region
|
||||
const in_left_corner = x < r;
|
||||
const in_right_corner = x >= w - r;
|
||||
const in_top_corner = y < r;
|
||||
const in_bottom_corner = y >= h - r;
|
||||
|
||||
// If not in any corner region, definitely inside
|
||||
if (!in_left_corner and !in_right_corner and !in_top_corner and !in_bottom_corner) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check each corner
|
||||
if (in_left_corner and in_top_corner) {
|
||||
// Top-left corner
|
||||
const dx = @as(f32, @floatFromInt(r - x));
|
||||
const dy = @as(f32, @floatFromInt(r - y));
|
||||
return dx * dx + dy * dy <= @as(f32, @floatFromInt(r * r));
|
||||
}
|
||||
if (in_right_corner and in_top_corner) {
|
||||
// Top-right corner
|
||||
const dx = @as(f32, @floatFromInt(x - (w - r - 1)));
|
||||
const dy = @as(f32, @floatFromInt(r - y));
|
||||
return dx * dx + dy * dy <= @as(f32, @floatFromInt(r * r));
|
||||
}
|
||||
if (in_left_corner and in_bottom_corner) {
|
||||
// Bottom-left corner
|
||||
const dx = @as(f32, @floatFromInt(r - x));
|
||||
const dy = @as(f32, @floatFromInt(y - (h - r - 1)));
|
||||
return dx * dx + dy * dy <= @as(f32, @floatFromInt(r * r));
|
||||
}
|
||||
if (in_right_corner and in_bottom_corner) {
|
||||
// Bottom-right corner
|
||||
const dx = @as(f32, @floatFromInt(x - (w - r - 1)));
|
||||
const dy = @as(f32, @floatFromInt(y - (h - r - 1)));
|
||||
return dx * dx + dy * dy <= @as(f32, @floatFromInt(r * r));
|
||||
}
|
||||
|
||||
// In an edge region but not a corner
|
||||
return true;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Tests
|
||||
// =============================================================================
|
||||
|
|
|
|||
|
|
@ -96,8 +96,8 @@ pub fn buttonRect(ctx: *Context, bounds: Layout.Rect, text: []const u8, config:
|
|||
|
||||
// Draw background and border based on render mode
|
||||
if (Style.isFancy() and config.corner_radius > 0) {
|
||||
// Fancy mode: rounded corners with AA
|
||||
ctx.pushCommand(Command.roundedRect(bounds.x, bounds.y, bounds.w, bounds.h, bg_color, config.corner_radius));
|
||||
// Fancy mode: rounded corners with subtle gradient for 3D effect
|
||||
ctx.pushCommand(Command.gradientButton(bounds.x, bounds.y, bounds.w, bounds.h, bg_color, config.corner_radius));
|
||||
ctx.pushCommand(Command.roundedRectOutline(bounds.x, bounds.y, bounds.w, bounds.h, theme.border, config.corner_radius));
|
||||
} else {
|
||||
// Simple mode: square corners
|
||||
|
|
@ -185,7 +185,8 @@ pub fn buttonStatefulRect(
|
|||
|
||||
// Draw background and border based on render mode
|
||||
if (Style.isFancy() and config.corner_radius > 0) {
|
||||
ctx.pushCommand(Command.roundedRect(bounds.x, bounds.y, bounds.w, bounds.h, bg_color, config.corner_radius));
|
||||
// Fancy mode: rounded corners with subtle gradient for 3D effect
|
||||
ctx.pushCommand(Command.gradientButton(bounds.x, bounds.y, bounds.w, bounds.h, bg_color, config.corner_radius));
|
||||
ctx.pushCommand(Command.roundedRectOutline(bounds.x, bounds.y, bounds.w, bounds.h, theme.border, config.corner_radius));
|
||||
} else {
|
||||
ctx.pushCommand(Command.rect(bounds.x, bounds.y, bounds.w, bounds.h, bg_color));
|
||||
|
|
|
|||
|
|
@ -251,9 +251,23 @@ pub fn menuEx(
|
|||
// Menu bounds
|
||||
const menu_rect = Layout.Rect.init(pos_x, pos_y, menu_width, menu_height);
|
||||
|
||||
// Check render mode for fancy features
|
||||
const corner_radius: u8 = 4;
|
||||
const fancy = Style.isFancy();
|
||||
|
||||
// Draw shadow first (behind menu) in fancy mode
|
||||
if (fancy) {
|
||||
ctx.pushCommand(Command.shadowDrop(menu_rect.x, menu_rect.y, menu_rect.w, menu_rect.h, corner_radius));
|
||||
}
|
||||
|
||||
// Draw background
|
||||
if (fancy) {
|
||||
ctx.pushCommand(Command.roundedRect(menu_rect.x, menu_rect.y, menu_rect.w, menu_rect.h, colors.background, corner_radius));
|
||||
ctx.pushCommand(Command.roundedRectOutline(menu_rect.x, menu_rect.y, menu_rect.w, menu_rect.h, colors.border, corner_radius));
|
||||
} else {
|
||||
ctx.pushCommand(Command.rect(menu_rect.x, menu_rect.y, menu_rect.w, menu_rect.h, colors.background));
|
||||
ctx.pushCommand(Command.rectOutline(menu_rect.x, menu_rect.y, menu_rect.w, menu_rect.h, colors.border));
|
||||
}
|
||||
|
||||
// Draw items
|
||||
var item_y = pos_y + @as(i32, @intCast(config.padding_v));
|
||||
|
|
|
|||
|
|
@ -198,15 +198,7 @@ pub fn modalEx(
|
|||
|
||||
// Draw shadow first (behind dialog) in fancy mode
|
||||
if (fancy and config.show_shadow) {
|
||||
const shadow_offset: i32 = 6;
|
||||
ctx.pushCommand(Command.roundedRect(
|
||||
dialog_x + shadow_offset,
|
||||
dialog_y + shadow_offset,
|
||||
dialog_w,
|
||||
dialog_h,
|
||||
colors.shadow,
|
||||
config.corner_radius,
|
||||
));
|
||||
ctx.pushCommand(Command.shadowFloat(dialog_x, dialog_y, dialog_w, dialog_h, config.corner_radius));
|
||||
}
|
||||
|
||||
// Draw dialog border and background based on render mode
|
||||
|
|
|
|||
|
|
@ -114,15 +114,7 @@ pub fn panelRect(
|
|||
|
||||
// Draw shadow first (behind panel) in fancy mode
|
||||
if (fancy and config.show_shadow) {
|
||||
const shadow_offset: i32 = 4;
|
||||
ctx.pushCommand(Command.roundedRect(
|
||||
bounds.x + shadow_offset,
|
||||
bounds.y + shadow_offset,
|
||||
bounds.w,
|
||||
bounds.h,
|
||||
colors.shadow,
|
||||
config.corner_radius,
|
||||
));
|
||||
ctx.pushCommand(Command.shadowDrop(bounds.x, bounds.y, bounds.w, bounds.h, config.corner_radius));
|
||||
}
|
||||
|
||||
// Draw outer border
|
||||
|
|
|
|||
|
|
@ -472,43 +472,14 @@ fn drawStripedFill(ctx: *Context, bounds: Rect, fill_color: Color, animated: boo
|
|||
fn drawGradientFill(ctx: *Context, bounds: Rect, base_color: Color, vertical: bool) void {
|
||||
if (bounds.w == 0 or bounds.h == 0) return;
|
||||
|
||||
// Simple gradient approximation with 4 bands
|
||||
const bands: u32 = 4;
|
||||
const steps = if (vertical) bounds.h / bands else bounds.w / bands;
|
||||
|
||||
var i: u32 = 0;
|
||||
while (i < bands) : (i += 1) {
|
||||
const t: f32 = @as(f32, @floatFromInt(i)) / @as(f32, @floatFromInt(bands));
|
||||
const brightness: u8 = @intFromFloat(t * 40);
|
||||
|
||||
const band_color = Color.rgba(
|
||||
base_color.r -| brightness,
|
||||
base_color.g -| brightness,
|
||||
base_color.b -| brightness,
|
||||
base_color.a,
|
||||
);
|
||||
// Use proper gradient command for smooth color transition
|
||||
const start_color = base_color.lighten(25);
|
||||
const end_color = base_color.darken(15);
|
||||
|
||||
if (vertical) {
|
||||
ctx.pushCommand(.{
|
||||
.rect = .{
|
||||
.x = bounds.x,
|
||||
.y = bounds.y + @as(i32, @intCast(i * steps)),
|
||||
.w = bounds.w,
|
||||
.h = steps,
|
||||
.color = band_color,
|
||||
},
|
||||
});
|
||||
ctx.pushCommand(Command.gradientV(bounds.x, bounds.y, bounds.w, bounds.h, start_color, end_color));
|
||||
} else {
|
||||
ctx.pushCommand(.{
|
||||
.rect = .{
|
||||
.x = bounds.x + @as(i32, @intCast(i * steps)),
|
||||
.y = bounds.y,
|
||||
.w = steps,
|
||||
.h = bounds.h,
|
||||
.color = band_color,
|
||||
},
|
||||
});
|
||||
}
|
||||
ctx.pushCommand(Command.gradientH(bounds.x, bounds.y, bounds.w, bounds.h, start_color, end_color));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -196,7 +196,33 @@ pub fn selectRect(
|
|||
const dropdown_h = visible_items * config.item_height;
|
||||
const dropdown_y = bounds.y + @as(i32, @intCast(bounds.h));
|
||||
|
||||
// Check render mode for fancy features
|
||||
const fancy = Style.isFancy() and config.corner_radius > 0;
|
||||
|
||||
// Draw dropdown shadow first (behind dropdown) in fancy mode
|
||||
if (fancy) {
|
||||
ctx.pushCommand(Command.shadowDrop(bounds.x, dropdown_y, bounds.w, @intCast(dropdown_h), config.corner_radius));
|
||||
}
|
||||
|
||||
// Dropdown background
|
||||
if (fancy) {
|
||||
ctx.pushCommand(Command.roundedRect(
|
||||
bounds.x,
|
||||
dropdown_y,
|
||||
bounds.w,
|
||||
@intCast(dropdown_h),
|
||||
theme.background.lighten(5),
|
||||
config.corner_radius,
|
||||
));
|
||||
ctx.pushCommand(Command.roundedRectOutline(
|
||||
bounds.x,
|
||||
dropdown_y,
|
||||
bounds.w,
|
||||
@intCast(dropdown_h),
|
||||
theme.border,
|
||||
config.corner_radius,
|
||||
));
|
||||
} else {
|
||||
ctx.pushCommand(Command.rect(
|
||||
bounds.x,
|
||||
dropdown_y,
|
||||
|
|
@ -204,7 +230,6 @@ pub fn selectRect(
|
|||
@intCast(dropdown_h),
|
||||
theme.background.lighten(5),
|
||||
));
|
||||
|
||||
ctx.pushCommand(Command.rectOutline(
|
||||
bounds.x,
|
||||
dropdown_y,
|
||||
|
|
@ -212,6 +237,7 @@ pub fn selectRect(
|
|||
@intCast(dropdown_h),
|
||||
theme.border,
|
||||
));
|
||||
}
|
||||
|
||||
// Draw visible items
|
||||
var item_y = dropdown_y;
|
||||
|
|
|
|||
|
|
@ -477,7 +477,19 @@ fn renderToast(ctx: *Context, toast: *const Toast, x: i32, y: i32, config: Confi
|
|||
// Calculate height based on text (simplified - assume single line for now)
|
||||
const height: u32 = 56;
|
||||
|
||||
// Check render mode for fancy features
|
||||
const corner_radius: u8 = 6;
|
||||
const fancy = Style.isFancy();
|
||||
|
||||
// Draw shadow first (behind toast) in fancy mode
|
||||
if (fancy) {
|
||||
ctx.pushCommand(Command.shadow(x, y, width, height, corner_radius));
|
||||
}
|
||||
|
||||
// Draw background
|
||||
if (fancy) {
|
||||
ctx.pushCommand(Command.roundedRect(x, y, width, height, colors.background, corner_radius));
|
||||
} else {
|
||||
ctx.pushCommand(.{
|
||||
.rect = .{
|
||||
.x = x,
|
||||
|
|
@ -487,6 +499,7 @@ fn renderToast(ctx: *Context, toast: *const Toast, x: i32, y: i32, config: Confi
|
|||
.color = colors.background,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Draw left accent bar
|
||||
ctx.pushCommand(.{
|
||||
|
|
|
|||
|
|
@ -206,7 +206,19 @@ pub fn showEx(ctx: *Context, state: *State, text: []const u8, config: Config) Re
|
|||
const tooltip_rect = calculateTooltipRect(target, content_width, content_height, pos, config.offset, config.arrow_size);
|
||||
state.tooltip_rect = tooltip_rect;
|
||||
|
||||
// Check render mode for fancy features
|
||||
const corner_radius: u8 = 4;
|
||||
const fancy = Style.isFancy();
|
||||
|
||||
// Draw shadow first (behind tooltip) in fancy mode
|
||||
if (fancy) {
|
||||
ctx.pushCommand(Command.shadow(tooltip_rect.x, tooltip_rect.y, tooltip_rect.w, tooltip_rect.h, corner_radius));
|
||||
}
|
||||
|
||||
// Draw tooltip background
|
||||
if (fancy) {
|
||||
ctx.pushCommand(Command.roundedRect(tooltip_rect.x, tooltip_rect.y, tooltip_rect.w, tooltip_rect.h, bg_color, corner_radius));
|
||||
} else {
|
||||
ctx.pushCommand(.{
|
||||
.rect = .{
|
||||
.x = tooltip_rect.x,
|
||||
|
|
@ -216,6 +228,7 @@ pub fn showEx(ctx: *Context, state: *State, text: []const u8, config: Config) Re
|
|||
.color = bg_color,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Draw border
|
||||
drawBorder(ctx, tooltip_rect, border_color);
|
||||
|
|
|
|||
Loading…
Reference in a new issue