diff --git a/src/widgets/appbar.zig b/src/widgets/appbar.zig index fa6b347..116692e 100644 --- a/src/widgets/appbar.zig +++ b/src/widgets/appbar.zig @@ -9,7 +9,7 @@ const Command = @import("../core/command.zig"); const Layout = @import("../core/layout.zig"); const Style = @import("../core/style.zig"); const Input = @import("../core/input.zig"); -const icon_module = @import("icon.zig"); +const icon_module = @import("icon/icon.zig"); const iconbutton = @import("iconbutton.zig"); /// AppBar position diff --git a/src/widgets/icon/drawing_helpers.zig b/src/widgets/icon/drawing_helpers.zig new file mode 100644 index 0000000..75b611b --- /dev/null +++ b/src/widgets/icon/drawing_helpers.zig @@ -0,0 +1,93 @@ +//! Icon - Helpers de dibujo +//! +//! Funciones para dibujar líneas y círculos con grosor. + +const Context = @import("../../core/context.zig").Context; +const Command = @import("../../core/command.zig"); +const Style = @import("../../core/style.zig"); + +// ============================================================================= +// Drawing Helpers +// ============================================================================= + +/// Draw a line with thickness +pub fn drawLine(ctx: *Context, x1: i32, y1: i32, x2: i32, y2: i32, thickness: u32, color: Style.Color) void { + if (thickness <= 1) { + ctx.pushCommand(Command.line(x1, y1, x2, y2, color)); + } else { + const dx = x2 - x1; + const dy = y2 - y1; + const len = @sqrt(@as(f32, @floatFromInt(dx * dx + dy * dy))); + + if (len < 1) { + ctx.pushCommand(Command.rect(x1, y1, thickness, thickness, color)); + return; + } + + const nx = -@as(f32, @floatFromInt(dy)) / len; + const ny = @as(f32, @floatFromInt(dx)) / len; + + const half = @as(f32, @floatFromInt(thickness)) / 2.0; + var offset: f32 = -half; + while (offset < half) : (offset += 1.0) { + const ox = @as(i32, @intFromFloat(nx * offset)); + const oy = @as(i32, @intFromFloat(ny * offset)); + ctx.pushCommand(Command.line(x1 + ox, y1 + oy, x2 + ox, y2 + oy, color)); + } + } +} + +/// Fill a circle +pub fn fillCircle(ctx: *Context, cx: i32, cy: i32, radius: u32, color: Style.Color) void { + if (radius == 0) { + ctx.pushCommand(Command.rect(cx, cy, 1, 1, color)); + return; + } + + const r = @as(i32, @intCast(radius)); + var dy: i32 = -r; + while (dy <= r) : (dy += 1) { + const dy_f = @as(f32, @floatFromInt(dy)); + const r_f = @as(f32, @floatFromInt(r)); + const dx = @as(i32, @intFromFloat(@sqrt(r_f * r_f - dy_f * dy_f))); + ctx.pushCommand(Command.rect(cx - dx, cy + dy, @intCast(dx * 2 + 1), 1, color)); + } +} + +/// Stroke a circle +pub fn strokeCircle(ctx: *Context, cx: i32, cy: i32, radius: u32, thickness: u32, color: Style.Color) void { + if (radius == 0) return; + + const r = @as(i32, @intCast(radius)); + var px: i32 = 0; + var py: i32 = r; + var d: i32 = 3 - 2 * r; + + while (px <= py) { + setPixelThick(ctx, cx + px, cy + py, thickness, color); + setPixelThick(ctx, cx - px, cy + py, thickness, color); + setPixelThick(ctx, cx + px, cy - py, thickness, color); + setPixelThick(ctx, cx - px, cy - py, thickness, color); + setPixelThick(ctx, cx + py, cy + px, thickness, color); + setPixelThick(ctx, cx - py, cy + px, thickness, color); + setPixelThick(ctx, cx + py, cy - px, thickness, color); + setPixelThick(ctx, cx - py, cy - px, thickness, color); + + if (d < 0) { + d = d + 4 * px + 6; + } else { + d = d + 4 * (px - py) + 10; + py -= 1; + } + px += 1; + } +} + +fn setPixelThick(ctx: *Context, pixel_x: i32, pixel_y: i32, thickness: u32, color: Style.Color) void { + if (thickness <= 1) { + ctx.pushCommand(Command.rect(pixel_x, pixel_y, 1, 1, color)); + } else { + const half = @as(i32, @intCast(thickness / 2)); + ctx.pushCommand(Command.rect(pixel_x - half, pixel_y - half, thickness, thickness, color)); + } +} diff --git a/src/widgets/icon.zig b/src/widgets/icon/icon.zig similarity index 78% rename from src/widgets/icon.zig rename to src/widgets/icon/icon.zig index f1036c4..0acf8f2 100644 --- a/src/widgets/icon.zig +++ b/src/widgets/icon/icon.zig @@ -1,805 +1,515 @@ -//! Icon Widget - Vector icon system -//! -//! A lightweight icon system using simple vector drawing. -//! Icons are defined as draw commands for resolution independence. - -const std = @import("std"); -const Context = @import("../core/context.zig").Context; -const Command = @import("../core/command.zig"); -const Layout = @import("../core/layout.zig"); -const Style = @import("../core/style.zig"); - -/// Icon size presets -pub const Size = enum { - small, // 12x12 - medium, // 16x16 - large, // 24x24 - xlarge, // 32x32 - - pub fn pixels(self: Size) u32 { - return switch (self) { - .small => 12, - .medium => 16, - .large => 24, - .xlarge => 32, - }; - } -}; - -/// Built-in icon types -pub const IconType = enum { - // Navigation - arrow_up, - arrow_down, - arrow_left, - arrow_right, - chevron_up, - chevron_down, - chevron_left, - chevron_right, - home, - menu, - more_horizontal, - more_vertical, - - // Actions - check, - close, - plus, - minus, - edit, - delete, - refresh, - search, - settings, - filter, - sort, - copy, - paste, - cut, - undo, - redo, - - // Files - file, - folder, - folder_open, - document, - image_file, - download, - upload, - save, - - // Status - info, - warning, - error_icon, - success, - question, - star, - star_filled, - heart, - heart_filled, - - // UI elements - eye, - eye_off, - lock, - unlock, - user, - users, - calendar, - clock, - bell, - mail, - - // Media - play, - pause, - stop, - volume, - volume_off, - - // Misc - grip, - drag, - expand, - collapse, - maximize, - minimize, - external_link, -}; - -/// Icon configuration -pub const Config = struct { - /// Icon size - size: Size = .medium, - /// Custom size (overrides size preset) - custom_size: ?u32 = null, - /// Stroke width - stroke_width: u32 = 2, - /// Fill icon - filled: bool = false, -}; - -/// Icon colors -pub const Colors = struct { - foreground: Style.Color = Style.Color.rgba(220, 220, 220, 255), - background: ?Style.Color = null, - - pub fn fromTheme(theme: Style.Theme) Colors { - return .{ - .foreground = theme.foreground, - }; - } -}; - -/// Draw an icon -pub fn icon(ctx: *Context, icon_type: IconType) void { - iconEx(ctx, icon_type, .{}, .{}); -} - -/// Draw an icon with configuration -pub fn iconEx( - ctx: *Context, - icon_type: IconType, - config: Config, - colors: Colors, -) void { - const bounds = ctx.layout.nextRect(); - iconRect(ctx, bounds, icon_type, config, colors); -} - -/// Draw an icon in specific rectangle -pub fn iconRect( - ctx: *Context, - bounds: Layout.Rect, - icon_type: IconType, - config: Config, - colors: Colors, -) void { - if (bounds.isEmpty()) return; - - // Background - if (colors.background) |bg| { - ctx.pushCommand(Command.rect(bounds.x, bounds.y, bounds.w, bounds.h, bg)); - } - - const size = config.custom_size orelse config.size.pixels(); - const x = bounds.x + @as(i32, @intCast((bounds.w -| size) / 2)); - const y = bounds.y + @as(i32, @intCast((bounds.h -| size) / 2)); - const s: i32 = @intCast(size); - const sw = config.stroke_width; - - // Pre-calculated divisions to avoid runtime @divTrunc issues - const s2 = @divTrunc(s, 2); - const s3 = @divTrunc(s, 3); - const s4 = @divTrunc(s, 4); - const s5 = @divTrunc(s, 5); - const s6 = @divTrunc(s, 6); - const s23 = @divTrunc(s * 2, 3); - const s34 = @divTrunc(s * 3, 4); - - const fg = colors.foreground; - - // Draw icon based on type - switch (icon_type) { - // Arrows - .arrow_up => { - drawLine(ctx, x + s2, y + 2, x + 2, y + s - 4, sw, fg); - drawLine(ctx, x + s2, y + 2, x + s - 2, y + s - 4, sw, fg); - drawLine(ctx, x + s2, y + 2, x + s2, y + s - 2, sw, fg); - }, - .arrow_down => { - drawLine(ctx, x + s2, y + s - 2, x + 2, y + 4, sw, fg); - drawLine(ctx, x + s2, y + s - 2, x + s - 2, y + 4, sw, fg); - drawLine(ctx, x + s2, y + s - 2, x + s2, y + 2, sw, fg); - }, - .arrow_left => { - drawLine(ctx, x + 2, y + s2, x + s - 4, y + 2, sw, fg); - drawLine(ctx, x + 2, y + s2, x + s - 4, y + s - 2, sw, fg); - drawLine(ctx, x + 2, y + s2, x + s - 2, y + s2, sw, fg); - }, - .arrow_right => { - drawLine(ctx, x + s - 2, y + s2, x + 4, y + 2, sw, fg); - drawLine(ctx, x + s - 2, y + s2, x + 4, y + s - 2, sw, fg); - drawLine(ctx, x + s - 2, y + s2, x + 2, y + s2, sw, fg); - }, - - // Chevrons - .chevron_up => { - drawLine(ctx, x + 2, y + s23, x + s2, y + s3, sw, fg); - drawLine(ctx, x + s2, y + s3, x + s - 2, y + s23, sw, fg); - }, - .chevron_down => { - drawLine(ctx, x + 2, y + s3, x + s2, y + s23, sw, fg); - drawLine(ctx, x + s2, y + s23, x + s - 2, y + s3, sw, fg); - }, - .chevron_left => { - drawLine(ctx, x + s23, y + 2, x + s3, y + s2, sw, fg); - drawLine(ctx, x + s3, y + s2, x + s23, y + s - 2, sw, fg); - }, - .chevron_right => { - drawLine(ctx, x + s3, y + 2, x + s23, y + s2, sw, fg); - drawLine(ctx, x + s23, y + s2, x + s3, y + s - 2, sw, fg); - }, - - // Actions - .check => { - drawLine(ctx, x + 2, y + s2, x + s3, y + s - 4, sw, fg); - drawLine(ctx, x + s3, y + s - 4, x + s - 2, y + 3, sw, fg); - }, - .close => { - drawLine(ctx, x + 3, y + 3, x + s - 3, y + s - 3, sw, fg); - drawLine(ctx, x + s - 3, y + 3, x + 3, y + s - 3, sw, fg); - }, - .plus => { - drawLine(ctx, x + s2, y + 3, x + s2, y + s - 3, sw, fg); - drawLine(ctx, x + 3, y + s2, x + s - 3, y + s2, sw, fg); - }, - .minus => { - drawLine(ctx, x + 3, y + s2, x + s - 3, y + s2, sw, fg); - }, - - // Home - .home => { - drawLine(ctx, x + 2, y + s2, x + s2, y + 2, sw, fg); - drawLine(ctx, x + s2, y + 2, x + s - 2, y + s2, sw, fg); - drawLine(ctx, x + 3, y + s2, x + 3, y + s - 2, sw, fg); - drawLine(ctx, x + s - 3, y + s2, x + s - 3, y + s - 2, sw, fg); - drawLine(ctx, x + 3, y + s - 2, x + s - 3, y + s - 2, sw, fg); - }, - - // Menu - .menu => { - const bar_y1 = y + s4; - const bar_y2 = y + s2; - const bar_y3 = y + s34; - drawLine(ctx, x + 2, bar_y1, x + s - 2, bar_y1, sw, fg); - drawLine(ctx, x + 2, bar_y2, x + s - 2, bar_y2, sw, fg); - drawLine(ctx, x + 2, bar_y3, x + s - 2, bar_y3, sw, fg); - }, - - // More (dots) - .more_horizontal => { - const dot_r: u32 = @max(2, @as(u32, @intCast(s6))); - fillCircle(ctx, x + s4, y + s2, dot_r, fg); - fillCircle(ctx, x + s2, y + s2, dot_r, fg); - fillCircle(ctx, x + s34, y + s2, dot_r, fg); - }, - .more_vertical => { - const dot_r: u32 = @max(2, @as(u32, @intCast(s6))); - fillCircle(ctx, x + s2, y + s4, dot_r, fg); - fillCircle(ctx, x + s2, y + s2, dot_r, fg); - fillCircle(ctx, x + s2, y + s34, dot_r, fg); - }, - - // Search - .search => { - const r: u32 = @intCast(s3); - strokeCircle(ctx, x + s3, y + s3, r, sw, fg); - drawLine(ctx, x + s2, y + s2, x + s - 3, y + s - 3, sw, fg); - }, - - // Settings (gear) - .settings => { - const r: u32 = @intCast(s4); - strokeCircle(ctx, x + s2, y + s2, r, sw, fg); - drawLine(ctx, x + s2, y + 2, x + s2, y + s4, sw, fg); - drawLine(ctx, x + s2, y + s - 2, x + s2, y + s34, sw, fg); - drawLine(ctx, x + 2, y + s2, x + s4, y + s2, sw, fg); - drawLine(ctx, x + s - 2, y + s2, x + s34, y + s2, sw, fg); - }, - - // File - .file => { - ctx.pushCommand(Command.rectOutline(x + 2, y + 2, @intCast(s - 4), @intCast(s - 4), fg)); - drawLine(ctx, x + s - 6, y + 2, x + s - 2, y + 6, sw, fg); - }, - - // Folder - .folder => { - ctx.pushCommand(Command.rectOutline(x + 2, y + 4, @intCast(s - 4), @intCast(s - 6), fg)); - drawLine(ctx, x + 2, y + 4, x + s3, y + 4, sw, fg); - drawLine(ctx, x + s3, y + 4, x + s3 + 2, y + 2, sw, fg); - drawLine(ctx, x + s3 + 2, y + 2, x + s2, y + 2, sw, fg); - }, - - // Document - .document => { - ctx.pushCommand(Command.rectOutline(x + 3, y + 2, @intCast(s - 6), @intCast(s - 4), fg)); - drawLine(ctx, x + 5, y + s3, x + s - 5, y + s3, 1, fg); - drawLine(ctx, x + 5, y + s2, x + s - 5, y + s2, 1, fg); - drawLine(ctx, x + 5, y + s23, x + s - 7, y + s23, 1, fg); - }, - - // Save (floppy) - .save => { - ctx.pushCommand(Command.rectOutline(x + 2, y + 2, @intCast(s - 4), @intCast(s - 4), fg)); - ctx.pushCommand(Command.rect(x + 4, y + s2, @intCast(s - 8), @intCast(s3), fg)); - }, - - // Info - .info => { - const r: u32 = @intCast(s2 - 2); - strokeCircle(ctx, x + s2, y + s2, r, sw, fg); - fillCircle(ctx, x + s2, y + s3, 2, fg); - drawLine(ctx, x + s2, y + s2 - 1, x + s2, y + s23 + 1, sw, fg); - }, - - // Warning (triangle) - .warning => { - drawLine(ctx, x + s2, y + 2, x + 2, y + s - 2, sw, fg); - drawLine(ctx, x + s2, y + 2, x + s - 2, y + s - 2, sw, fg); - drawLine(ctx, x + 2, y + s - 2, x + s - 2, y + s - 2, sw, fg); - drawLine(ctx, x + s2, y + s3 + 1, x + s2, y + s2 + 1, sw, fg); - fillCircle(ctx, x + s2, y + s23, 2, fg); - }, - - // Error (X in circle) - .error_icon => { - const r: u32 = @intCast(s2 - 2); - strokeCircle(ctx, x + s2, y + s2, r, sw, fg); - drawLine(ctx, x + s3, y + s3, x + s23, y + s23, sw, fg); - drawLine(ctx, x + s23, y + s3, x + s3, y + s23, sw, fg); - }, - - // Success (checkmark in circle) - .success => { - const r: u32 = @intCast(s2 - 2); - strokeCircle(ctx, x + s2, y + s2, r, sw, fg); - drawLine(ctx, x + s4 + 1, y + s2, x + s2 - 1, y + s23, sw, fg); - drawLine(ctx, x + s2 - 1, y + s23, x + s34 - 1, y + s3, sw, fg); - }, - - // Question - .question => { - const r: u32 = @intCast(s2 - 2); - strokeCircle(ctx, x + s2, y + s2, r, sw, fg); - ctx.pushCommand(Command.text(x + s2 - 3, y + s3, "?", fg)); - }, - - // Star - .star => { - const s25 = @divTrunc(s * 2, 5); - const s35 = @divTrunc(s * 3, 5); - drawLine(ctx, x + s2, y + 2, x + s3, y + s25, sw, fg); - drawLine(ctx, x + s3, y + s25, x + 2, y + s25, sw, fg); - drawLine(ctx, x + 2, y + s25, x + s4, y + s35, sw, fg); - drawLine(ctx, x + s4, y + s35, x + s4, y + s - 2, sw, fg); - drawLine(ctx, x + s4, y + s - 2, x + s2, y + s34, sw, fg); - drawLine(ctx, x + s2, y + s34, x + s34, y + s - 2, sw, fg); - drawLine(ctx, x + s34, y + s - 2, x + s34, y + s35, sw, fg); - drawLine(ctx, x + s34, y + s35, x + s - 2, y + s25, sw, fg); - drawLine(ctx, x + s - 2, y + s25, x + s23, y + s25, sw, fg); - drawLine(ctx, x + s23, y + s25, x + s2, y + 2, sw, fg); - }, - .star_filled => { - fillCircle(ctx, x + s2, y + s2, @intCast(s3), fg); - }, - - // Heart - .heart => { - drawLine(ctx, x + s2, y + s - 3, x + 3, y + s2, sw, fg); - drawLine(ctx, x + s2, y + s - 3, x + s - 3, y + s2, sw, fg); - fillCircle(ctx, x + s3, y + s3, @intCast(s5), fg); - fillCircle(ctx, x + s23, y + s3, @intCast(s5), fg); - }, - .heart_filled => { - fillCircle(ctx, x + s3, y + s3, @intCast(s4), fg); - fillCircle(ctx, x + s23, y + s3, @intCast(s4), fg); - var dy: i32 = 0; - while (dy < s2) : (dy += 1) { - const hw = @divTrunc(s2 - dy, 2); - ctx.pushCommand(Command.rect(x + s2 - hw, y + s2 + dy, @intCast(hw * 2), 1, fg)); - } - }, - - // Eye - .eye => { - ctx.pushCommand(Command.rectOutline(x + 2, y + s3, @intCast(s - 4), @intCast(s3), fg)); - fillCircle(ctx, x + s2, y + s2, @intCast(s6), fg); - }, - .eye_off => { - ctx.pushCommand(Command.rectOutline(x + 2, y + s3, @intCast(s - 4), @intCast(s3), fg)); - drawLine(ctx, x + 2, y + s - 3, x + s - 2, y + 3, sw, fg); - }, - - // Lock - .lock => { - ctx.pushCommand(Command.rectOutline(x + 3, y + s2, @intCast(s - 6), @intCast(s2 - 2), fg)); - drawLine(ctx, x + s3, y + s2, x + s3, y + s4, sw, fg); - drawLine(ctx, x + s23, y + s2, x + s23, y + s4, sw, fg); - drawLine(ctx, x + s3, y + s4, x + s23, y + s4, sw, fg); - }, - .unlock => { - ctx.pushCommand(Command.rectOutline(x + 3, y + s2, @intCast(s - 6), @intCast(s2 - 2), fg)); - drawLine(ctx, x + s3, y + s2, x + s3, y + s4, sw, fg); - drawLine(ctx, x + s3, y + s4, x + s2, y + s4, sw, fg); - }, - - // User - .user => { - fillCircle(ctx, x + s2, y + s3, @intCast(s5), fg); - drawLine(ctx, x + s4, y + s - 2, x + s4, y + s2 + 2, sw, fg); - drawLine(ctx, x + s34, y + s - 2, x + s34, y + s2 + 2, sw, fg); - drawLine(ctx, x + s4, y + s2 + 2, x + s34, y + s2 + 2, sw, fg); - }, - .users => { - fillCircle(ctx, x + s3, y + s3 + 1, @intCast(s6), fg); - fillCircle(ctx, x + s23, y + s4, @intCast(s6), fg); - }, - - // Calendar - .calendar => { - ctx.pushCommand(Command.rectOutline(x + 2, y + 4, @intCast(s - 4), @intCast(s - 6), fg)); - drawLine(ctx, x + 2, y + s3, x + s - 2, y + s3, sw, fg); - drawLine(ctx, x + s3, y + 2, x + s3, y + 5, sw, fg); - drawLine(ctx, x + s23, y + 2, x + s23, y + 5, sw, fg); - }, - - // Clock - .clock => { - const r: u32 = @intCast(s2 - 2); - strokeCircle(ctx, x + s2, y + s2, r, sw, fg); - drawLine(ctx, x + s2, y + s2, x + s2, y + s3, sw, fg); - drawLine(ctx, x + s2, y + s2, x + s23, y + s2, sw, fg); - }, - - // Bell - .bell => { - drawLine(ctx, x + s3, y + s23, x + s3, y + s3, sw, fg); - drawLine(ctx, x + s23, y + s23, x + s23, y + s3, sw, fg); - drawLine(ctx, x + s3, y + s3, x + s23, y + s3, sw, fg); - drawLine(ctx, x + 3, y + s23, x + s - 3, y + s23, sw, fg); - fillCircle(ctx, x + s2, y + s - 3, 2, fg); - }, - - // Mail - .mail => { - ctx.pushCommand(Command.rectOutline(x + 2, y + s4, @intCast(s - 4), @intCast(s2), fg)); - drawLine(ctx, x + 2, y + s4, x + s2, y + s2, sw, fg); - drawLine(ctx, x + s - 2, y + s4, x + s2, y + s2, sw, fg); - }, - - // Play - .play => { - drawLine(ctx, x + s3, y + 3, x + s3, y + s - 3, sw, fg); - drawLine(ctx, x + s3, y + 3, x + s23 + 1, y + s2, sw, fg); - drawLine(ctx, x + s3, y + s - 3, x + s23 + 1, y + s2, sw, fg); - }, - - // Pause - .pause => { - ctx.pushCommand(Command.rect(x + s4, y + 3, @intCast(s5), @intCast(s - 6), fg)); - ctx.pushCommand(Command.rect(x + s - s4 - s5, y + 3, @intCast(s5), @intCast(s - 6), fg)); - }, - - // Stop - .stop => { - ctx.pushCommand(Command.rect(x + 3, y + 3, @intCast(s - 6), @intCast(s - 6), fg)); - }, - - // Volume - .volume => { - ctx.pushCommand(Command.rect(x + 2, y + s3, 4, @intCast(s3), fg)); - drawLine(ctx, x + 6, y + s3, x + s2, y + 3, sw, fg); - drawLine(ctx, x + 6, y + s23, x + s2, y + s - 3, sw, fg); - drawLine(ctx, x + s23, y + s3, x + s23, y + s23, sw, fg); - }, - .volume_off => { - ctx.pushCommand(Command.rect(x + 2, y + s3, 4, @intCast(s3), fg)); - drawLine(ctx, x + 6, y + s3, x + s2, y + 3, sw, fg); - drawLine(ctx, x + 6, y + s23, x + s2, y + s - 3, sw, fg); - drawLine(ctx, x + s23, y + s3, x + s - 3, y + s23, sw, fg); - drawLine(ctx, x + s - 3, y + s3, x + s23, y + s23, sw, fg); - }, - - // Edit - .edit => { - drawLine(ctx, x + 3, y + s - 5, x + s - 5, y + 3, sw, fg); - drawLine(ctx, x + s - 5, y + 3, x + s - 3, y + 5, sw, fg); - drawLine(ctx, x + s - 3, y + 5, x + 5, y + s - 3, sw, fg); - }, - - // Delete (trash) - .delete => { - ctx.pushCommand(Command.rectOutline(x + 4, y + 4, @intCast(s - 8), @intCast(s - 6), fg)); - drawLine(ctx, x + 2, y + 4, x + s - 2, y + 4, sw, fg); - drawLine(ctx, x + s3, y + 2, x + s23, y + 2, sw, fg); - }, - - // Refresh - .refresh => { - const r: u32 = @intCast(s3); - strokeCircle(ctx, x + s2, y + s2, r, sw, fg); - drawLine(ctx, x + s2 + @as(i32, @intCast(r)), y + s2 - 3, x + s2 + @as(i32, @intCast(r)) + 3, y + s2, sw, fg); - }, - - // Filter - .filter => { - drawLine(ctx, x + 2, y + 3, x + s - 2, y + 3, sw, fg); - drawLine(ctx, x + 2, y + 3, x + s2, y + s2, sw, fg); - drawLine(ctx, x + s - 2, y + 3, x + s2, y + s2, sw, fg); - drawLine(ctx, x + s2, y + s2, x + s2, y + s - 3, sw, fg); - }, - - // Sort - .sort => { - drawLine(ctx, x + 3, y + s4, x + s - 3, y + s4, sw, fg); - drawLine(ctx, x + 5, y + s2, x + s - 5, y + s2, sw, fg); - drawLine(ctx, x + 7, y + s34, x + s - 7, y + s34, sw, fg); - }, - - // Copy - .copy => { - ctx.pushCommand(Command.rectOutline(x + 2, y + 4, @intCast(s - 6), @intCast(s - 6), fg)); - ctx.pushCommand(Command.rectOutline(x + 4, y + 2, @intCast(s - 6), @intCast(s - 6), fg)); - }, - - // Paste - .paste => { - ctx.pushCommand(Command.rectOutline(x + 3, y + 3, @intCast(s - 6), @intCast(s - 5), fg)); - ctx.pushCommand(Command.rect(x + s3, y + 2, @intCast(s3), 3, fg)); - }, - - // Cut - .cut => { - fillCircle(ctx, x + s3, y + s23, @intCast(s6), fg); - fillCircle(ctx, x + s23, y + s23, @intCast(s6), fg); - drawLine(ctx, x + s3, y + s2, x + s2, y + 3, sw, fg); - drawLine(ctx, x + s23, y + s2, x + s2, y + 3, sw, fg); - }, - - // Undo - .undo => { - drawLine(ctx, x + 3, y + s3, x + s3, y + 3, sw, fg); - drawLine(ctx, x + 3, y + s3, x + s3, y + s2, sw, fg); - drawLine(ctx, x + 3, y + s3, x + s - 3, y + s3, sw, fg); - drawLine(ctx, x + s - 3, y + s3, x + s - 3, y + s23, sw, fg); - }, - - // Redo - .redo => { - drawLine(ctx, x + s - 3, y + s3, x + s23, y + 3, sw, fg); - drawLine(ctx, x + s - 3, y + s3, x + s23, y + s2, sw, fg); - drawLine(ctx, x + s - 3, y + s3, x + 3, y + s3, sw, fg); - drawLine(ctx, x + 3, y + s3, x + 3, y + s23, sw, fg); - }, - - // Folder open - .folder_open => { - ctx.pushCommand(Command.rectOutline(x + 2, y + 4, @intCast(s - 4), @intCast(s - 6), fg)); - drawLine(ctx, x + 2, y + s2, x + s3, y + s2, sw, fg); - drawLine(ctx, x + s3, y + s2, x + s2, y + s3, sw, fg); - }, - - // Image file - .image_file => { - ctx.pushCommand(Command.rectOutline(x + 2, y + 2, @intCast(s - 4), @intCast(s - 4), fg)); - drawLine(ctx, x + 4, y + s23, x + s3, y + s2, sw, fg); - drawLine(ctx, x + s3, y + s2, x + s23, y + s23, sw, fg); - fillCircle(ctx, x + s23, y + s3, 2, fg); - }, - - // Download - .download => { - drawLine(ctx, x + s2, y + 3, x + s2, y + s23, sw, fg); - drawLine(ctx, x + s2, y + s23, x + s3, y + s2, sw, fg); - drawLine(ctx, x + s2, y + s23, x + s23, y + s2, sw, fg); - drawLine(ctx, x + 3, y + s - 3, x + s - 3, y + s - 3, sw, fg); - }, - - // Upload - .upload => { - drawLine(ctx, x + s2, y + s23, x + s2, y + 3, sw, fg); - drawLine(ctx, x + s2, y + 3, x + s3, y + s4 + 1, sw, fg); - drawLine(ctx, x + s2, y + 3, x + s23, y + s4 + 1, sw, fg); - drawLine(ctx, x + 3, y + s - 3, x + s - 3, y + s - 3, sw, fg); - }, - - // Grip (drag handle) - .grip => { - const dot_r: u32 = 1; - fillCircle(ctx, x + s3, y + s3, dot_r, fg); - fillCircle(ctx, x + s23, y + s3, dot_r, fg); - fillCircle(ctx, x + s3, y + s2, dot_r, fg); - fillCircle(ctx, x + s23, y + s2, dot_r, fg); - fillCircle(ctx, x + s3, y + s23, dot_r, fg); - fillCircle(ctx, x + s23, y + s23, dot_r, fg); - }, - - // Drag - .drag => { - drawLine(ctx, x + s2, y + 2, x + s2, y + s - 2, sw, fg); - drawLine(ctx, x + 2, y + s2, x + s - 2, y + s2, sw, fg); - drawLine(ctx, x + s2, y + 2, x + s3, y + s4, sw, fg); - drawLine(ctx, x + s2, y + 2, x + s23, y + s4, sw, fg); - }, - - // Expand - .expand => { - drawLine(ctx, x + 2, y + 2, x + s3, y + 2, sw, fg); - drawLine(ctx, x + 2, y + 2, x + 2, y + s3, sw, fg); - drawLine(ctx, x + s - 2, y + 2, x + s23, y + 2, sw, fg); - drawLine(ctx, x + s - 2, y + 2, x + s - 2, y + s3, sw, fg); - drawLine(ctx, x + 2, y + s - 2, x + s3, y + s - 2, sw, fg); - drawLine(ctx, x + 2, y + s - 2, x + 2, y + s23, sw, fg); - drawLine(ctx, x + s - 2, y + s - 2, x + s23, y + s - 2, sw, fg); - drawLine(ctx, x + s - 2, y + s - 2, x + s - 2, y + s23, sw, fg); - }, - - // Collapse - .collapse => { - drawLine(ctx, x + s3, y + s3, x + 2, y + s3, sw, fg); - drawLine(ctx, x + s3, y + s3, x + s3, y + 2, sw, fg); - drawLine(ctx, x + s23, y + s3, x + s - 2, y + s3, sw, fg); - drawLine(ctx, x + s23, y + s3, x + s23, y + 2, sw, fg); - drawLine(ctx, x + s3, y + s23, x + 2, y + s23, sw, fg); - drawLine(ctx, x + s3, y + s23, x + s3, y + s - 2, sw, fg); - drawLine(ctx, x + s23, y + s23, x + s - 2, y + s23, sw, fg); - drawLine(ctx, x + s23, y + s23, x + s23, y + s - 2, sw, fg); - }, - - // Maximize - .maximize => { - ctx.pushCommand(Command.rectOutline(x + 3, y + 3, @intCast(s - 6), @intCast(s - 6), fg)); - drawLine(ctx, x + 3, y + 5, x + s - 3, y + 5, sw, fg); - }, - - // Minimize - .minimize => { - drawLine(ctx, x + 3, y + s - 4, x + s - 3, y + s - 4, sw, fg); - }, - - // External link - .external_link => { - ctx.pushCommand(Command.rectOutline(x + 2, y + 4, @intCast(s - 6), @intCast(s - 6), fg)); - drawLine(ctx, x + s2, y + 2, x + s - 2, y + 2, sw, fg); - drawLine(ctx, x + s - 2, y + 2, x + s - 2, y + s2, sw, fg); - drawLine(ctx, x + s - 2, y + 2, x + s2, y + s2, sw, fg); - }, - } -} - -// ============================================================================= -// Helper functions -// ============================================================================= - -/// Draw a line with thickness -fn drawLine(ctx: *Context, x1: i32, y1: i32, x2: i32, y2: i32, thickness: u32, color: Style.Color) void { - if (thickness <= 1) { - ctx.pushCommand(Command.line(x1, y1, x2, y2, color)); - } else { - const dx = x2 - x1; - const dy = y2 - y1; - const len = @sqrt(@as(f32, @floatFromInt(dx * dx + dy * dy))); - - if (len < 1) { - ctx.pushCommand(Command.rect(x1, y1, thickness, thickness, color)); - return; - } - - const nx = -@as(f32, @floatFromInt(dy)) / len; - const ny = @as(f32, @floatFromInt(dx)) / len; - - const half = @as(f32, @floatFromInt(thickness)) / 2.0; - var offset: f32 = -half; - while (offset < half) : (offset += 1.0) { - const ox = @as(i32, @intFromFloat(nx * offset)); - const oy = @as(i32, @intFromFloat(ny * offset)); - ctx.pushCommand(Command.line(x1 + ox, y1 + oy, x2 + ox, y2 + oy, color)); - } - } -} - -/// Fill a circle -fn fillCircle(ctx: *Context, cx: i32, cy: i32, radius: u32, color: Style.Color) void { - if (radius == 0) { - ctx.pushCommand(Command.rect(cx, cy, 1, 1, color)); - return; - } - - const r = @as(i32, @intCast(radius)); - var dy: i32 = -r; - while (dy <= r) : (dy += 1) { - const dy_f = @as(f32, @floatFromInt(dy)); - const r_f = @as(f32, @floatFromInt(r)); - const dx = @as(i32, @intFromFloat(@sqrt(r_f * r_f - dy_f * dy_f))); - ctx.pushCommand(Command.rect(cx - dx, cy + dy, @intCast(dx * 2 + 1), 1, color)); - } -} - -/// Stroke a circle -fn strokeCircle(ctx: *Context, cx: i32, cy: i32, radius: u32, thickness: u32, color: Style.Color) void { - if (radius == 0) return; - - const r = @as(i32, @intCast(radius)); - var px: i32 = 0; - var py: i32 = r; - var d: i32 = 3 - 2 * r; - - while (px <= py) { - setPixelThick(ctx, cx + px, cy + py, thickness, color); - setPixelThick(ctx, cx - px, cy + py, thickness, color); - setPixelThick(ctx, cx + px, cy - py, thickness, color); - setPixelThick(ctx, cx - px, cy - py, thickness, color); - setPixelThick(ctx, cx + py, cy + px, thickness, color); - setPixelThick(ctx, cx - py, cy + px, thickness, color); - setPixelThick(ctx, cx + py, cy - px, thickness, color); - setPixelThick(ctx, cx - py, cy - px, thickness, color); - - if (d < 0) { - d = d + 4 * px + 6; - } else { - d = d + 4 * (px - py) + 10; - py -= 1; - } - px += 1; - } -} - -fn setPixelThick(ctx: *Context, pixel_x: i32, pixel_y: i32, thickness: u32, color: Style.Color) void { - if (thickness <= 1) { - ctx.pushCommand(Command.rect(pixel_x, pixel_y, 1, 1, color)); - } else { - const half = @as(i32, @intCast(thickness / 2)); - ctx.pushCommand(Command.rect(pixel_x - half, pixel_y - half, thickness, thickness, color)); - } -} - -// ============================================================================= -// Tests -// ============================================================================= - -test "Size pixels" { - try std.testing.expectEqual(@as(u32, 12), Size.small.pixels()); - try std.testing.expectEqual(@as(u32, 16), Size.medium.pixels()); - try std.testing.expectEqual(@as(u32, 24), Size.large.pixels()); - try std.testing.expectEqual(@as(u32, 32), Size.xlarge.pixels()); -} - -test "icon generates commands" { - var ctx = try Context.init(std.testing.allocator, 800, 600); - defer ctx.deinit(); - - ctx.beginFrame(); - ctx.layout.row_height = 32; - - icon(&ctx, .check); - - try std.testing.expect(ctx.commands.items.len >= 1); - - ctx.endFrame(); -} - -test "iconEx with config" { - var ctx = try Context.init(std.testing.allocator, 800, 600); - defer ctx.deinit(); - - ctx.beginFrame(); - ctx.layout.row_height = 48; - - iconEx(&ctx, .home, .{ .size = .large, .stroke_width = 3 }, .{}); - - try std.testing.expect(ctx.commands.items.len >= 1); - - ctx.endFrame(); -} - -test "multiple icons" { - var ctx = try Context.init(std.testing.allocator, 800, 600); - defer ctx.deinit(); - - ctx.beginFrame(); - ctx.layout.row_height = 24; - - const icons = [_]IconType{ .check, .close, .plus, .minus, .search }; - for (icons) |i| { - icon(&ctx, i); - } - - try std.testing.expect(ctx.commands.items.len >= 5); - - ctx.endFrame(); -} +//! Icon Widget - Vector icon system +//! +//! A lightweight icon system using simple vector drawing. +//! Icons are defined as draw commands for resolution independence. +//! +//! ## Estructura modular +//! +//! - types.zig: Size, IconType, Config, Colors +//! - drawing_helpers.zig: drawLine, fillCircle, strokeCircle +//! - icon.zig: Hub principal (este archivo) + +const std = @import("std"); +const Context = @import("../../core/context.zig").Context; +const Command = @import("../../core/command.zig"); +const Layout = @import("../../core/layout.zig"); +const Style = @import("../../core/style.zig"); + +// Re-exports desde módulos +pub const types = @import("types.zig"); +pub const Size = types.Size; +pub const IconType = types.IconType; +pub const Config = types.Config; +pub const Colors = types.Colors; + +const helpers = @import("drawing_helpers.zig"); +const drawLine = helpers.drawLine; +const fillCircle = helpers.fillCircle; +const strokeCircle = helpers.strokeCircle; + +// ============================================================================= +// Public API +// ============================================================================= + +/// Draw an icon +pub fn icon(ctx: *Context, icon_type: IconType) void { + iconEx(ctx, icon_type, .{}, .{}); +} + +/// Draw an icon with configuration +pub fn iconEx( + ctx: *Context, + icon_type: IconType, + config: Config, + colors: Colors, +) void { + const bounds = ctx.layout.nextRect(); + iconRect(ctx, bounds, icon_type, config, colors); +} + +/// Draw an icon in specific rectangle +pub fn iconRect( + ctx: *Context, + bounds: Layout.Rect, + icon_type: IconType, + config: Config, + colors: Colors, +) void { + if (bounds.isEmpty()) return; + + // Background + if (colors.background) |bg| { + ctx.pushCommand(Command.rect(bounds.x, bounds.y, bounds.w, bounds.h, bg)); + } + + const size = config.custom_size orelse config.size.pixels(); + const x = bounds.x + @as(i32, @intCast((bounds.w -| size) / 2)); + const y = bounds.y + @as(i32, @intCast((bounds.h -| size) / 2)); + const s: i32 = @intCast(size); + const sw = config.stroke_width; + + // Pre-calculated divisions + const s2 = @divTrunc(s, 2); + const s3 = @divTrunc(s, 3); + const s4 = @divTrunc(s, 4); + const s5 = @divTrunc(s, 5); + const s6 = @divTrunc(s, 6); + const s23 = @divTrunc(s * 2, 3); + const s34 = @divTrunc(s * 3, 4); + + const fg = colors.foreground; + + // Draw icon based on type + switch (icon_type) { + .arrow_up => { + drawLine(ctx, x + s2, y + 2, x + 2, y + s - 4, sw, fg); + drawLine(ctx, x + s2, y + 2, x + s - 2, y + s - 4, sw, fg); + drawLine(ctx, x + s2, y + 2, x + s2, y + s - 2, sw, fg); + }, + .arrow_down => { + drawLine(ctx, x + s2, y + s - 2, x + 2, y + 4, sw, fg); + drawLine(ctx, x + s2, y + s - 2, x + s - 2, y + 4, sw, fg); + drawLine(ctx, x + s2, y + s - 2, x + s2, y + 2, sw, fg); + }, + .arrow_left => { + drawLine(ctx, x + 2, y + s2, x + s - 4, y + 2, sw, fg); + drawLine(ctx, x + 2, y + s2, x + s - 4, y + s - 2, sw, fg); + drawLine(ctx, x + 2, y + s2, x + s - 2, y + s2, sw, fg); + }, + .arrow_right => { + drawLine(ctx, x + s - 2, y + s2, x + 4, y + 2, sw, fg); + drawLine(ctx, x + s - 2, y + s2, x + 4, y + s - 2, sw, fg); + drawLine(ctx, x + s - 2, y + s2, x + 2, y + s2, sw, fg); + }, + .chevron_up => { + drawLine(ctx, x + 2, y + s23, x + s2, y + s3, sw, fg); + drawLine(ctx, x + s2, y + s3, x + s - 2, y + s23, sw, fg); + }, + .chevron_down => { + drawLine(ctx, x + 2, y + s3, x + s2, y + s23, sw, fg); + drawLine(ctx, x + s2, y + s23, x + s - 2, y + s3, sw, fg); + }, + .chevron_left => { + drawLine(ctx, x + s23, y + 2, x + s3, y + s2, sw, fg); + drawLine(ctx, x + s3, y + s2, x + s23, y + s - 2, sw, fg); + }, + .chevron_right => { + drawLine(ctx, x + s3, y + 2, x + s23, y + s2, sw, fg); + drawLine(ctx, x + s23, y + s2, x + s3, y + s - 2, sw, fg); + }, + .check => { + drawLine(ctx, x + 2, y + s2, x + s3, y + s - 4, sw, fg); + drawLine(ctx, x + s3, y + s - 4, x + s - 2, y + 3, sw, fg); + }, + .close => { + drawLine(ctx, x + 3, y + 3, x + s - 3, y + s - 3, sw, fg); + drawLine(ctx, x + s - 3, y + 3, x + 3, y + s - 3, sw, fg); + }, + .plus => { + drawLine(ctx, x + s2, y + 3, x + s2, y + s - 3, sw, fg); + drawLine(ctx, x + 3, y + s2, x + s - 3, y + s2, sw, fg); + }, + .minus => { + drawLine(ctx, x + 3, y + s2, x + s - 3, y + s2, sw, fg); + }, + .home => { + drawLine(ctx, x + 2, y + s2, x + s2, y + 2, sw, fg); + drawLine(ctx, x + s2, y + 2, x + s - 2, y + s2, sw, fg); + drawLine(ctx, x + 3, y + s2, x + 3, y + s - 2, sw, fg); + drawLine(ctx, x + s - 3, y + s2, x + s - 3, y + s - 2, sw, fg); + drawLine(ctx, x + 3, y + s - 2, x + s - 3, y + s - 2, sw, fg); + }, + .menu => { + const bar_y1 = y + s4; + const bar_y2 = y + s2; + const bar_y3 = y + s34; + drawLine(ctx, x + 2, bar_y1, x + s - 2, bar_y1, sw, fg); + drawLine(ctx, x + 2, bar_y2, x + s - 2, bar_y2, sw, fg); + drawLine(ctx, x + 2, bar_y3, x + s - 2, bar_y3, sw, fg); + }, + .more_horizontal => { + const dot_r: u32 = @max(2, @as(u32, @intCast(s6))); + fillCircle(ctx, x + s4, y + s2, dot_r, fg); + fillCircle(ctx, x + s2, y + s2, dot_r, fg); + fillCircle(ctx, x + s34, y + s2, dot_r, fg); + }, + .more_vertical => { + const dot_r: u32 = @max(2, @as(u32, @intCast(s6))); + fillCircle(ctx, x + s2, y + s4, dot_r, fg); + fillCircle(ctx, x + s2, y + s2, dot_r, fg); + fillCircle(ctx, x + s2, y + s34, dot_r, fg); + }, + .search => { + const r: u32 = @intCast(s3); + strokeCircle(ctx, x + s3, y + s3, r, sw, fg); + drawLine(ctx, x + s2, y + s2, x + s - 3, y + s - 3, sw, fg); + }, + .settings => { + const r: u32 = @intCast(s4); + strokeCircle(ctx, x + s2, y + s2, r, sw, fg); + drawLine(ctx, x + s2, y + 2, x + s2, y + s4, sw, fg); + drawLine(ctx, x + s2, y + s - 2, x + s2, y + s34, sw, fg); + drawLine(ctx, x + 2, y + s2, x + s4, y + s2, sw, fg); + drawLine(ctx, x + s - 2, y + s2, x + s34, y + s2, sw, fg); + }, + .file => { + ctx.pushCommand(Command.rectOutline(x + 2, y + 2, @intCast(s - 4), @intCast(s - 4), fg)); + drawLine(ctx, x + s - 6, y + 2, x + s - 2, y + 6, sw, fg); + }, + .folder => { + ctx.pushCommand(Command.rectOutline(x + 2, y + 4, @intCast(s - 4), @intCast(s - 6), fg)); + drawLine(ctx, x + 2, y + 4, x + s3, y + 4, sw, fg); + drawLine(ctx, x + s3, y + 4, x + s3 + 2, y + 2, sw, fg); + drawLine(ctx, x + s3 + 2, y + 2, x + s2, y + 2, sw, fg); + }, + .document => { + ctx.pushCommand(Command.rectOutline(x + 3, y + 2, @intCast(s - 6), @intCast(s - 4), fg)); + drawLine(ctx, x + 5, y + s3, x + s - 5, y + s3, 1, fg); + drawLine(ctx, x + 5, y + s2, x + s - 5, y + s2, 1, fg); + drawLine(ctx, x + 5, y + s23, x + s - 7, y + s23, 1, fg); + }, + .save => { + ctx.pushCommand(Command.rectOutline(x + 2, y + 2, @intCast(s - 4), @intCast(s - 4), fg)); + ctx.pushCommand(Command.rect(x + 4, y + s2, @intCast(s - 8), @intCast(s3), fg)); + }, + .info => { + const r: u32 = @intCast(s2 - 2); + strokeCircle(ctx, x + s2, y + s2, r, sw, fg); + fillCircle(ctx, x + s2, y + s3, 2, fg); + drawLine(ctx, x + s2, y + s2 - 1, x + s2, y + s23 + 1, sw, fg); + }, + .warning => { + drawLine(ctx, x + s2, y + 2, x + 2, y + s - 2, sw, fg); + drawLine(ctx, x + s2, y + 2, x + s - 2, y + s - 2, sw, fg); + drawLine(ctx, x + 2, y + s - 2, x + s - 2, y + s - 2, sw, fg); + drawLine(ctx, x + s2, y + s3 + 1, x + s2, y + s2 + 1, sw, fg); + fillCircle(ctx, x + s2, y + s23, 2, fg); + }, + .error_icon => { + const r: u32 = @intCast(s2 - 2); + strokeCircle(ctx, x + s2, y + s2, r, sw, fg); + drawLine(ctx, x + s3, y + s3, x + s23, y + s23, sw, fg); + drawLine(ctx, x + s23, y + s3, x + s3, y + s23, sw, fg); + }, + .success => { + const r: u32 = @intCast(s2 - 2); + strokeCircle(ctx, x + s2, y + s2, r, sw, fg); + drawLine(ctx, x + s4 + 1, y + s2, x + s2 - 1, y + s23, sw, fg); + drawLine(ctx, x + s2 - 1, y + s23, x + s34 - 1, y + s3, sw, fg); + }, + .question => { + const r: u32 = @intCast(s2 - 2); + strokeCircle(ctx, x + s2, y + s2, r, sw, fg); + ctx.pushCommand(Command.text(x + s2 - 3, y + s3, "?", fg)); + }, + .star => { + const s25 = @divTrunc(s * 2, 5); + const s35 = @divTrunc(s * 3, 5); + drawLine(ctx, x + s2, y + 2, x + s3, y + s25, sw, fg); + drawLine(ctx, x + s3, y + s25, x + 2, y + s25, sw, fg); + drawLine(ctx, x + 2, y + s25, x + s4, y + s35, sw, fg); + drawLine(ctx, x + s4, y + s35, x + s4, y + s - 2, sw, fg); + drawLine(ctx, x + s4, y + s - 2, x + s2, y + s34, sw, fg); + drawLine(ctx, x + s2, y + s34, x + s34, y + s - 2, sw, fg); + drawLine(ctx, x + s34, y + s - 2, x + s34, y + s35, sw, fg); + drawLine(ctx, x + s34, y + s35, x + s - 2, y + s25, sw, fg); + drawLine(ctx, x + s - 2, y + s25, x + s23, y + s25, sw, fg); + drawLine(ctx, x + s23, y + s25, x + s2, y + 2, sw, fg); + }, + .star_filled => { + fillCircle(ctx, x + s2, y + s2, @intCast(s3), fg); + }, + .heart => { + drawLine(ctx, x + s2, y + s - 3, x + 3, y + s2, sw, fg); + drawLine(ctx, x + s2, y + s - 3, x + s - 3, y + s2, sw, fg); + fillCircle(ctx, x + s3, y + s3, @intCast(s5), fg); + fillCircle(ctx, x + s23, y + s3, @intCast(s5), fg); + }, + .heart_filled => { + fillCircle(ctx, x + s3, y + s3, @intCast(s4), fg); + fillCircle(ctx, x + s23, y + s3, @intCast(s4), fg); + var dy: i32 = 0; + while (dy < s2) : (dy += 1) { + const hw = @divTrunc(s2 - dy, 2); + ctx.pushCommand(Command.rect(x + s2 - hw, y + s2 + dy, @intCast(hw * 2), 1, fg)); + } + }, + .eye => { + ctx.pushCommand(Command.rectOutline(x + 2, y + s3, @intCast(s - 4), @intCast(s3), fg)); + fillCircle(ctx, x + s2, y + s2, @intCast(s6), fg); + }, + .eye_off => { + ctx.pushCommand(Command.rectOutline(x + 2, y + s3, @intCast(s - 4), @intCast(s3), fg)); + drawLine(ctx, x + 2, y + s - 3, x + s - 2, y + 3, sw, fg); + }, + .lock => { + ctx.pushCommand(Command.rectOutline(x + 3, y + s2, @intCast(s - 6), @intCast(s2 - 2), fg)); + drawLine(ctx, x + s3, y + s2, x + s3, y + s4, sw, fg); + drawLine(ctx, x + s23, y + s2, x + s23, y + s4, sw, fg); + drawLine(ctx, x + s3, y + s4, x + s23, y + s4, sw, fg); + }, + .unlock => { + ctx.pushCommand(Command.rectOutline(x + 3, y + s2, @intCast(s - 6), @intCast(s2 - 2), fg)); + drawLine(ctx, x + s3, y + s2, x + s3, y + s4, sw, fg); + drawLine(ctx, x + s3, y + s4, x + s2, y + s4, sw, fg); + }, + .user => { + fillCircle(ctx, x + s2, y + s3, @intCast(s5), fg); + drawLine(ctx, x + s4, y + s - 2, x + s4, y + s2 + 2, sw, fg); + drawLine(ctx, x + s34, y + s - 2, x + s34, y + s2 + 2, sw, fg); + drawLine(ctx, x + s4, y + s2 + 2, x + s34, y + s2 + 2, sw, fg); + }, + .users => { + fillCircle(ctx, x + s3, y + s3 + 1, @intCast(s6), fg); + fillCircle(ctx, x + s23, y + s4, @intCast(s6), fg); + }, + .calendar => { + ctx.pushCommand(Command.rectOutline(x + 2, y + 4, @intCast(s - 4), @intCast(s - 6), fg)); + drawLine(ctx, x + 2, y + s3, x + s - 2, y + s3, sw, fg); + drawLine(ctx, x + s3, y + 2, x + s3, y + 5, sw, fg); + drawLine(ctx, x + s23, y + 2, x + s23, y + 5, sw, fg); + }, + .clock => { + const r: u32 = @intCast(s2 - 2); + strokeCircle(ctx, x + s2, y + s2, r, sw, fg); + drawLine(ctx, x + s2, y + s2, x + s2, y + s3, sw, fg); + drawLine(ctx, x + s2, y + s2, x + s23, y + s2, sw, fg); + }, + .bell => { + drawLine(ctx, x + s3, y + s23, x + s3, y + s3, sw, fg); + drawLine(ctx, x + s23, y + s23, x + s23, y + s3, sw, fg); + drawLine(ctx, x + s3, y + s3, x + s23, y + s3, sw, fg); + drawLine(ctx, x + 3, y + s23, x + s - 3, y + s23, sw, fg); + fillCircle(ctx, x + s2, y + s - 3, 2, fg); + }, + .mail => { + ctx.pushCommand(Command.rectOutline(x + 2, y + s4, @intCast(s - 4), @intCast(s2), fg)); + drawLine(ctx, x + 2, y + s4, x + s2, y + s2, sw, fg); + drawLine(ctx, x + s - 2, y + s4, x + s2, y + s2, sw, fg); + }, + .play => { + drawLine(ctx, x + s3, y + 3, x + s3, y + s - 3, sw, fg); + drawLine(ctx, x + s3, y + 3, x + s23 + 1, y + s2, sw, fg); + drawLine(ctx, x + s3, y + s - 3, x + s23 + 1, y + s2, sw, fg); + }, + .pause => { + ctx.pushCommand(Command.rect(x + s4, y + 3, @intCast(s5), @intCast(s - 6), fg)); + ctx.pushCommand(Command.rect(x + s - s4 - s5, y + 3, @intCast(s5), @intCast(s - 6), fg)); + }, + .stop => { + ctx.pushCommand(Command.rect(x + 3, y + 3, @intCast(s - 6), @intCast(s - 6), fg)); + }, + .volume => { + ctx.pushCommand(Command.rect(x + 2, y + s3, 4, @intCast(s3), fg)); + drawLine(ctx, x + 6, y + s3, x + s2, y + 3, sw, fg); + drawLine(ctx, x + 6, y + s23, x + s2, y + s - 3, sw, fg); + drawLine(ctx, x + s23, y + s3, x + s23, y + s23, sw, fg); + }, + .volume_off => { + ctx.pushCommand(Command.rect(x + 2, y + s3, 4, @intCast(s3), fg)); + drawLine(ctx, x + 6, y + s3, x + s2, y + 3, sw, fg); + drawLine(ctx, x + 6, y + s23, x + s2, y + s - 3, sw, fg); + drawLine(ctx, x + s23, y + s3, x + s - 3, y + s23, sw, fg); + drawLine(ctx, x + s - 3, y + s3, x + s23, y + s23, sw, fg); + }, + .edit => { + drawLine(ctx, x + 3, y + s - 5, x + s - 5, y + 3, sw, fg); + drawLine(ctx, x + s - 5, y + 3, x + s - 3, y + 5, sw, fg); + drawLine(ctx, x + s - 3, y + 5, x + 5, y + s - 3, sw, fg); + }, + .delete => { + ctx.pushCommand(Command.rectOutline(x + 4, y + 4, @intCast(s - 8), @intCast(s - 6), fg)); + drawLine(ctx, x + 2, y + 4, x + s - 2, y + 4, sw, fg); + drawLine(ctx, x + s3, y + 2, x + s23, y + 2, sw, fg); + }, + .refresh => { + const r: u32 = @intCast(s3); + strokeCircle(ctx, x + s2, y + s2, r, sw, fg); + drawLine(ctx, x + s2 + @as(i32, @intCast(r)), y + s2 - 3, x + s2 + @as(i32, @intCast(r)) + 3, y + s2, sw, fg); + }, + .filter => { + drawLine(ctx, x + 2, y + 3, x + s - 2, y + 3, sw, fg); + drawLine(ctx, x + 2, y + 3, x + s2, y + s2, sw, fg); + drawLine(ctx, x + s - 2, y + 3, x + s2, y + s2, sw, fg); + drawLine(ctx, x + s2, y + s2, x + s2, y + s - 3, sw, fg); + }, + .sort => { + drawLine(ctx, x + 3, y + s4, x + s - 3, y + s4, sw, fg); + drawLine(ctx, x + 5, y + s2, x + s - 5, y + s2, sw, fg); + drawLine(ctx, x + 7, y + s34, x + s - 7, y + s34, sw, fg); + }, + .copy => { + ctx.pushCommand(Command.rectOutline(x + 2, y + 4, @intCast(s - 6), @intCast(s - 6), fg)); + ctx.pushCommand(Command.rectOutline(x + 4, y + 2, @intCast(s - 6), @intCast(s - 6), fg)); + }, + .paste => { + ctx.pushCommand(Command.rectOutline(x + 3, y + 3, @intCast(s - 6), @intCast(s - 5), fg)); + ctx.pushCommand(Command.rect(x + s3, y + 2, @intCast(s3), 3, fg)); + }, + .cut => { + fillCircle(ctx, x + s3, y + s23, @intCast(s6), fg); + fillCircle(ctx, x + s23, y + s23, @intCast(s6), fg); + drawLine(ctx, x + s3, y + s2, x + s2, y + 3, sw, fg); + drawLine(ctx, x + s23, y + s2, x + s2, y + 3, sw, fg); + }, + .undo => { + drawLine(ctx, x + 3, y + s3, x + s3, y + 3, sw, fg); + drawLine(ctx, x + 3, y + s3, x + s3, y + s2, sw, fg); + drawLine(ctx, x + 3, y + s3, x + s - 3, y + s3, sw, fg); + drawLine(ctx, x + s - 3, y + s3, x + s - 3, y + s23, sw, fg); + }, + .redo => { + drawLine(ctx, x + s - 3, y + s3, x + s23, y + 3, sw, fg); + drawLine(ctx, x + s - 3, y + s3, x + s23, y + s2, sw, fg); + drawLine(ctx, x + s - 3, y + s3, x + 3, y + s3, sw, fg); + drawLine(ctx, x + 3, y + s3, x + 3, y + s23, sw, fg); + }, + .folder_open => { + ctx.pushCommand(Command.rectOutline(x + 2, y + 4, @intCast(s - 4), @intCast(s - 6), fg)); + drawLine(ctx, x + 2, y + s2, x + s3, y + s2, sw, fg); + drawLine(ctx, x + s3, y + s2, x + s2, y + s3, sw, fg); + }, + .image_file => { + ctx.pushCommand(Command.rectOutline(x + 2, y + 2, @intCast(s - 4), @intCast(s - 4), fg)); + drawLine(ctx, x + 4, y + s23, x + s3, y + s2, sw, fg); + drawLine(ctx, x + s3, y + s2, x + s23, y + s23, sw, fg); + fillCircle(ctx, x + s23, y + s3, 2, fg); + }, + .download => { + drawLine(ctx, x + s2, y + 3, x + s2, y + s23, sw, fg); + drawLine(ctx, x + s2, y + s23, x + s3, y + s2, sw, fg); + drawLine(ctx, x + s2, y + s23, x + s23, y + s2, sw, fg); + drawLine(ctx, x + 3, y + s - 3, x + s - 3, y + s - 3, sw, fg); + }, + .upload => { + drawLine(ctx, x + s2, y + s23, x + s2, y + 3, sw, fg); + drawLine(ctx, x + s2, y + 3, x + s3, y + s4 + 1, sw, fg); + drawLine(ctx, x + s2, y + 3, x + s23, y + s4 + 1, sw, fg); + drawLine(ctx, x + 3, y + s - 3, x + s - 3, y + s - 3, sw, fg); + }, + .grip => { + const dot_r: u32 = 1; + fillCircle(ctx, x + s3, y + s3, dot_r, fg); + fillCircle(ctx, x + s23, y + s3, dot_r, fg); + fillCircle(ctx, x + s3, y + s2, dot_r, fg); + fillCircle(ctx, x + s23, y + s2, dot_r, fg); + fillCircle(ctx, x + s3, y + s23, dot_r, fg); + fillCircle(ctx, x + s23, y + s23, dot_r, fg); + }, + .drag => { + drawLine(ctx, x + s2, y + 2, x + s2, y + s - 2, sw, fg); + drawLine(ctx, x + 2, y + s2, x + s - 2, y + s2, sw, fg); + drawLine(ctx, x + s2, y + 2, x + s3, y + s4, sw, fg); + drawLine(ctx, x + s2, y + 2, x + s23, y + s4, sw, fg); + }, + .expand => { + drawLine(ctx, x + 2, y + 2, x + s3, y + 2, sw, fg); + drawLine(ctx, x + 2, y + 2, x + 2, y + s3, sw, fg); + drawLine(ctx, x + s - 2, y + 2, x + s23, y + 2, sw, fg); + drawLine(ctx, x + s - 2, y + 2, x + s - 2, y + s3, sw, fg); + drawLine(ctx, x + 2, y + s - 2, x + s3, y + s - 2, sw, fg); + drawLine(ctx, x + 2, y + s - 2, x + 2, y + s23, sw, fg); + drawLine(ctx, x + s - 2, y + s - 2, x + s23, y + s - 2, sw, fg); + drawLine(ctx, x + s - 2, y + s - 2, x + s - 2, y + s23, sw, fg); + }, + .collapse => { + drawLine(ctx, x + s3, y + s3, x + 2, y + s3, sw, fg); + drawLine(ctx, x + s3, y + s3, x + s3, y + 2, sw, fg); + drawLine(ctx, x + s23, y + s3, x + s - 2, y + s3, sw, fg); + drawLine(ctx, x + s23, y + s3, x + s23, y + 2, sw, fg); + drawLine(ctx, x + s3, y + s23, x + 2, y + s23, sw, fg); + drawLine(ctx, x + s3, y + s23, x + s3, y + s - 2, sw, fg); + drawLine(ctx, x + s23, y + s23, x + s - 2, y + s23, sw, fg); + drawLine(ctx, x + s23, y + s23, x + s23, y + s - 2, sw, fg); + }, + .maximize => { + ctx.pushCommand(Command.rectOutline(x + 3, y + 3, @intCast(s - 6), @intCast(s - 6), fg)); + drawLine(ctx, x + 3, y + 5, x + s - 3, y + 5, sw, fg); + }, + .minimize => { + drawLine(ctx, x + 3, y + s - 4, x + s - 3, y + s - 4, sw, fg); + }, + .external_link => { + ctx.pushCommand(Command.rectOutline(x + 2, y + 4, @intCast(s - 6), @intCast(s - 6), fg)); + drawLine(ctx, x + s2, y + 2, x + s - 2, y + 2, sw, fg); + drawLine(ctx, x + s - 2, y + 2, x + s - 2, y + s2, sw, fg); + drawLine(ctx, x + s - 2, y + 2, x + s2, y + s2, sw, fg); + }, + } +} + +// ============================================================================= +// Tests +// ============================================================================= + +test "Size pixels" { + try std.testing.expectEqual(@as(u32, 12), Size.small.pixels()); + try std.testing.expectEqual(@as(u32, 16), Size.medium.pixels()); + try std.testing.expectEqual(@as(u32, 24), Size.large.pixels()); + try std.testing.expectEqual(@as(u32, 32), Size.xlarge.pixels()); +} + +test "icon generates commands" { + var ctx = try Context.init(std.testing.allocator, 800, 600); + defer ctx.deinit(); + + ctx.beginFrame(); + ctx.layout.row_height = 32; + + icon(&ctx, .check); + + try std.testing.expect(ctx.commands.items.len >= 1); + + ctx.endFrame(); +} + +test "iconEx with config" { + var ctx = try Context.init(std.testing.allocator, 800, 600); + defer ctx.deinit(); + + ctx.beginFrame(); + ctx.layout.row_height = 48; + + iconEx(&ctx, .home, .{ .size = .large, .stroke_width = 3 }, .{}); + + try std.testing.expect(ctx.commands.items.len >= 1); + + ctx.endFrame(); +} + +test "multiple icons" { + var ctx = try Context.init(std.testing.allocator, 800, 600); + defer ctx.deinit(); + + ctx.beginFrame(); + ctx.layout.row_height = 24; + + const icons = [_]IconType{ .check, .close, .plus, .minus, .search }; + for (icons) |i| { + icon(&ctx, i); + } + + try std.testing.expect(ctx.commands.items.len >= 5); + + ctx.endFrame(); +} diff --git a/src/widgets/icon/types.zig b/src/widgets/icon/types.zig new file mode 100644 index 0000000..2dea120 --- /dev/null +++ b/src/widgets/icon/types.zig @@ -0,0 +1,146 @@ +//! Icon - Tipos y configuración +//! +//! Size, IconType, Config, Colors. + +const Style = @import("../../core/style.zig"); + +// ============================================================================= +// Size +// ============================================================================= + +/// Icon size presets +pub const Size = enum { + small, // 12x12 + medium, // 16x16 + large, // 24x24 + xlarge, // 32x32 + + pub fn pixels(self: Size) u32 { + return switch (self) { + .small => 12, + .medium => 16, + .large => 24, + .xlarge => 32, + }; + } +}; + +// ============================================================================= +// IconType +// ============================================================================= + +/// Built-in icon types +pub const IconType = enum { + // Navigation + arrow_up, + arrow_down, + arrow_left, + arrow_right, + chevron_up, + chevron_down, + chevron_left, + chevron_right, + home, + menu, + more_horizontal, + more_vertical, + + // Actions + check, + close, + plus, + minus, + edit, + delete, + refresh, + search, + settings, + filter, + sort, + copy, + paste, + cut, + undo, + redo, + + // Files + file, + folder, + folder_open, + document, + image_file, + download, + upload, + save, + + // Status + info, + warning, + error_icon, + success, + question, + star, + star_filled, + heart, + heart_filled, + + // UI elements + eye, + eye_off, + lock, + unlock, + user, + users, + calendar, + clock, + bell, + mail, + + // Media + play, + pause, + stop, + volume, + volume_off, + + // Misc + grip, + drag, + expand, + collapse, + maximize, + minimize, + external_link, +}; + +// ============================================================================= +// Config +// ============================================================================= + +/// Icon configuration +pub const Config = struct { + /// Icon size + size: Size = .medium, + /// Custom size (overrides size preset) + custom_size: ?u32 = null, + /// Stroke width + stroke_width: u32 = 2, + /// Fill icon + filled: bool = false, +}; + +// ============================================================================= +// Colors +// ============================================================================= + +/// Icon colors +pub const Colors = struct { + foreground: Style.Color = Style.Color.rgba(220, 220, 220, 255), + background: ?Style.Color = null, + + pub fn fromTheme(theme: Style.Theme) Colors { + return .{ + .foreground = theme.foreground, + }; + } +}; diff --git a/src/widgets/iconbutton.zig b/src/widgets/iconbutton.zig index 74e79ef..34b1efe 100644 --- a/src/widgets/iconbutton.zig +++ b/src/widgets/iconbutton.zig @@ -9,7 +9,7 @@ const Command = @import("../core/command.zig"); const Layout = @import("../core/layout.zig"); const Style = @import("../core/style.zig"); const Input = @import("../core/input.zig"); -const icon_module = @import("icon.zig"); +const icon_module = @import("icon/icon.zig"); /// IconButton style variants pub const ButtonStyle = enum { diff --git a/src/widgets/navdrawer.zig b/src/widgets/navdrawer.zig index 1aa9aef..7e0ad72 100644 --- a/src/widgets/navdrawer.zig +++ b/src/widgets/navdrawer.zig @@ -9,7 +9,7 @@ const Command = @import("../core/command.zig"); const Layout = @import("../core/layout.zig"); const Style = @import("../core/style.zig"); const Input = @import("../core/input.zig"); -const icon_module = @import("icon.zig"); +const icon_module = @import("icon/icon.zig"); /// Navigation item pub const NavItem = struct { diff --git a/src/widgets/widgets.zig b/src/widgets/widgets.zig index 20b4783..9d56b0d 100644 --- a/src/widgets/widgets.zig +++ b/src/widgets/widgets.zig @@ -39,7 +39,7 @@ pub const richtext = @import("richtext.zig"); pub const breadcrumb = @import("breadcrumb.zig"); pub const canvas = @import("canvas.zig"); pub const chart = @import("chart.zig"); -pub const icon = @import("icon.zig"); +pub const icon = @import("icon/icon.zig"); pub const virtual_scroll = @import("virtual_scroll.zig"); pub const virtual_advanced_table = @import("virtual_advanced_table/virtual_advanced_table.zig");