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:
reugenio 2025-12-17 13:27:48 +01:00
parent 83049a99be
commit 2dccddeab0
10 changed files with 568 additions and 92 deletions

View file

@ -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
// =============================================================================

View file

@ -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
// =============================================================================

View file

@ -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));

View file

@ -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));

View file

@ -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

View file

@ -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

View file

@ -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));
}
}

View file

@ -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;

View file

@ -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(.{

View file

@ -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);