diff --git a/src/core/command.zig b/src/core/command.zig index 3d966b0..5d912b2 100644 --- a/src/core/command.zig +++ b/src/core/command.zig @@ -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 // ============================================================================= diff --git a/src/render/software.zig b/src/render/software.zig index 95df96e..b442f54 100644 --- a/src/render/software.zig +++ b/src/render/software.zig @@ -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 // ============================================================================= diff --git a/src/widgets/button.zig b/src/widgets/button.zig index 114e24d..a3ab270 100644 --- a/src/widgets/button.zig +++ b/src/widgets/button.zig @@ -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)); diff --git a/src/widgets/menu.zig b/src/widgets/menu.zig index 4853b6b..7db20d7 100644 --- a/src/widgets/menu.zig +++ b/src/widgets/menu.zig @@ -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 - 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)); + 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)); diff --git a/src/widgets/modal.zig b/src/widgets/modal.zig index d37b250..214432f 100644 --- a/src/widgets/modal.zig +++ b/src/widgets/modal.zig @@ -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 diff --git a/src/widgets/panel.zig b/src/widgets/panel.zig index 24acf78..c2ae9bb 100644 --- a/src/widgets/panel.zig +++ b/src/widgets/panel.zig @@ -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 diff --git a/src/widgets/progress.zig b/src/widgets/progress.zig index 4e3691a..e07ac17 100644 --- a/src/widgets/progress.zig +++ b/src/widgets/progress.zig @@ -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; + // Use proper gradient command for smooth color transition + const start_color = base_color.lighten(25); + const end_color = base_color.darken(15); - 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, - ); - - if (vertical) { - ctx.pushCommand(.{ - .rect = .{ - .x = bounds.x, - .y = bounds.y + @as(i32, @intCast(i * steps)), - .w = bounds.w, - .h = steps, - .color = band_color, - }, - }); - } else { - ctx.pushCommand(.{ - .rect = .{ - .x = bounds.x + @as(i32, @intCast(i * steps)), - .y = bounds.y, - .w = steps, - .h = bounds.h, - .color = band_color, - }, - }); - } + if (vertical) { + ctx.pushCommand(Command.gradientV(bounds.x, bounds.y, bounds.w, bounds.h, start_color, end_color)); + } else { + ctx.pushCommand(Command.gradientH(bounds.x, bounds.y, bounds.w, bounds.h, start_color, end_color)); } } diff --git a/src/widgets/select.zig b/src/widgets/select.zig index e20a900..bccb94b 100644 --- a/src/widgets/select.zig +++ b/src/widgets/select.zig @@ -196,22 +196,48 @@ pub fn selectRect( const dropdown_h = visible_items * config.item_height; const dropdown_y = bounds.y + @as(i32, @intCast(bounds.h)); - // Dropdown background - ctx.pushCommand(Command.rect( - bounds.x, - dropdown_y, - bounds.w, - @intCast(dropdown_h), - theme.background.lighten(5), - )); + // Check render mode for fancy features + const fancy = Style.isFancy() and config.corner_radius > 0; - ctx.pushCommand(Command.rectOutline( - bounds.x, - dropdown_y, - bounds.w, - @intCast(dropdown_h), - theme.border, - )); + // 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, + bounds.w, + @intCast(dropdown_h), + theme.background.lighten(5), + )); + ctx.pushCommand(Command.rectOutline( + bounds.x, + dropdown_y, + bounds.w, + @intCast(dropdown_h), + theme.border, + )); + } // Draw visible items var item_y = dropdown_y; diff --git a/src/widgets/toast.zig b/src/widgets/toast.zig index cda5337..003b9de 100644 --- a/src/widgets/toast.zig +++ b/src/widgets/toast.zig @@ -477,16 +477,29 @@ 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 - ctx.pushCommand(.{ - .rect = .{ - .x = x, - .y = y, - .w = width, - .h = height, - .color = colors.background, - }, - }); + if (fancy) { + ctx.pushCommand(Command.roundedRect(x, y, width, height, colors.background, corner_radius)); + } else { + ctx.pushCommand(.{ + .rect = .{ + .x = x, + .y = y, + .w = width, + .h = height, + .color = colors.background, + }, + }); + } // Draw left accent bar ctx.pushCommand(.{ diff --git a/src/widgets/tooltip.zig b/src/widgets/tooltip.zig index a8feda5..c79aecf 100644 --- a/src/widgets/tooltip.zig +++ b/src/widgets/tooltip.zig @@ -206,16 +206,29 @@ 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 - ctx.pushCommand(.{ - .rect = .{ - .x = tooltip_rect.x, - .y = tooltip_rect.y, - .w = tooltip_rect.w, - .h = tooltip_rect.h, - .color = bg_color, - }, - }); + 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, + .y = tooltip_rect.y, + .w = tooltip_rect.w, + .h = tooltip_rect.h, + .color = bg_color, + }, + }); + } // Draw border drawBorder(ctx, tooltip_rect, border_color);