Compare commits

..

No commits in common. "ebad736c756afee44e51255728ae9632ec65f3da" and "6bf1eb1eb882b56c16ddc08ba7167181b8b777fb" have entirely different histories.

10 changed files with 69 additions and 640 deletions

View file

@ -78,67 +78,54 @@ Resumen breve (1-2 frases). Resultado principal.
--- ---
## ⭐ Paridad Visual DVUI - Fase 1 COMPLETADA ✅ ## ⭐ TAREA PENDIENTE: Paridad Visual con DVUI
> **Estado**: Fase 1 completada (2025-12-17) > **Prioridad**: ALTA
> **Auditoría original**: `docs/research/DVUI_AUDIT_2025-12-17.md` (570 líneas) > **Auditoría completa**: `docs/research/DVUI_AUDIT_2025-12-17.md` (570 líneas)
> **Consensuado**: 2025-12-17
### El Problema (resuelto) ### El Problema
zcatgui tenía MÁS widgets que DVUI pero DVUI **se veía mejor** por falta de: zcatgui tiene MÁS widgets que DVUI (~45 vs ~42), pero DVUI **se ve mejor** porque:
1. **Esquinas redondeadas**`fillRoundedRect` con edge-fade AA 1. **Esquinas redondeadas** en todos los widgets
2. **Anti-aliasing en bordes** → edge-fade technique implementada 2. **Anti-aliasing en bordes** via edge-fade (no solo en TTF)
3. **Transiciones suaves** (Fase 2) 3. **Transiciones suaves** entre estados (hover, press)
4. **Sombras en paneles/modales** → Panel/Modal con shadow 4. **Sombras aplicadas** a paneles y modales
### Sistema Dual Implementado ### Solución Acordada
```zig Implementar sistema dual: `RenderMode.simple` (rápido) vs `RenderMode.fancy` (bonito)
// style.zig
pub const RenderMode = enum { simple, fancy };
var global_render_mode: RenderMode = .fancy; // Default: bonito
// Uso en widgets ### Fases de Implementación
if (Style.isFancy() and config.corner_radius > 0) {
ctx.pushCommand(Command.roundedRect(...));
} else {
ctx.pushCommand(Command.rect(...));
}
```
### Widgets Actualizados **Fase 1: Rendering Visual (CRÍTICO)**
1. Crear `src/render/path.zig` - Paths con arcos para esquinas redondeadas
| Widget | corner_radius | shadow | Notas | 2. Modificar `software.zig` - AA en bordes via edge-fade technique
|--------|---------------|--------|-------| 3. Actualizar widgets - corner_radius en Button, Panel, TextInput, Select, Modal
| Button | 4 | - | Esquinas redondeadas en fancy mode |
| Panel | 6 | ✅ offset 4px | Borde y shadow |
| TextInput | 3 | - | Esquinas sutiles |
| Select | 3 | - | Esquinas sutiles |
| Modal | 8 | ✅ offset 6px | Diálogo + botones + input |
### Código Añadido
- `framebuffer.zig`: +350 LOC (`fillRoundedRect`, `drawRoundedRect`, edge-fade AA)
- `command.zig`: +69 LOC (nuevos comandos `rounded_rect`, `rounded_rect_outline`)
- `style.zig`: +33 LOC (RenderMode system)
- Widgets: ~120 LOC entre todos
**Total: ~590 líneas nuevas/modificadas**
### Fases Pendientes
**Fase 2: Estados Visuales** **Fase 2: Estados Visuales**
- Integrar AnimationManager en widgets para transiciones hover/press 4. Integrar AnimationManager en widgets para transiciones hover/press
- Focus ring con anti-aliasing 5. Focus ring con anti-aliasing
**Fase 3: Efectos Avanzados** **Fase 3: Efectos**
- Mejorar uso de gradientes 6. Activar Shadow API existente (`effects.zig`) en Panel/Modal
- Blur effect para backdrops 7. Mejorar uso de gradientes
**Estimación**: ~1,400 LOC nuevas/modificadas
### Técnica Edge-Fade (de DVUI)
```
Cada borde tiene 2 vértices paralelos:
- Interno: color sólido (alpha = 1.0)
- Externo: transparente (alpha = 0.0)
GPU/CPU interpola → bordes suaves sin multisampling
```
### Documentación de Referencia ### Documentación de Referencia
- `docs/research/DVUI_AUDIT_2025-12-17.md` - Auditoría completa - `docs/research/DVUI_AUDIT_2025-12-17.md` - Auditoría completa
- `docs/research/WIDGET_COMPARISON.md` - Comparativa (actualizar después) - `docs/research/WIDGET_COMPARISON.md` - Comparativa anterior (actualizar después)
--- ---
@ -188,7 +175,7 @@ ttf.drawText(fb, x, y, "Hola UTF-8: áéíóú ñ €", color, clip);
## Fuentes TTF: Estado Técnico ## Fuentes TTF: Estado Técnico
### Estado actual (v0.18.0) - FUNCIONAL ### Estado actual (v0.17.0) - FUNCIONAL
- ✅ Parsing TTF via zcatttf (cmap format 4 y 12) - ✅ Parsing TTF via zcatttf (cmap format 4 y 12)
- ✅ Rasterización con áreas trapezoidales (antialiasing) - ✅ Rasterización con áreas trapezoidales (antialiasing)
- ✅ Fuente embebida (DroidSans) - ✅ Fuente embebida (DroidSans)
@ -217,7 +204,7 @@ font.drawText(fb, x, y, "Texto con UTF-8: ñ €", color, clip);
| Campo | Valor | | Campo | Valor |
|-------|-------| |-------|-------|
| **Nombre** | zcatgui | | **Nombre** | zcatgui |
| **Versión** | v0.18.0 | | **Versión** | v0.17.0 |
| **Fecha inicio** | 2025-12-09 | | **Fecha inicio** | 2025-12-09 |
| **Estado** | ✅ COMPLETO - 37 widgets, ~35K LOC, 4 backends, TTF funcional | | **Estado** | ✅ COMPLETO - 37 widgets, ~35K LOC, 4 backends, TTF funcional |
| **Lenguaje** | Zig 0.15.2 | | **Lenguaje** | Zig 0.15.2 |
@ -779,13 +766,12 @@ const stdout = std.fs.File.stdout(); // NO std.io.getStdOut()
| 2025-12-16 | v0.16.1 | Fuente embebida: TtfFont.initEmbedded() | | 2025-12-16 | v0.16.1 | Fuente embebida: TtfFont.initEmbedded() |
| 2025-12-16 | v0.16.2 | Fix TTF: DroidSans (187KB) reemplaza AdwaitaSans (variable). Y-flip rasterización. | | 2025-12-16 | v0.16.2 | Fix TTF: DroidSans (187KB) reemplaza AdwaitaSans (variable). Y-flip rasterización. |
| 2025-12-17 | v0.17.0 | ⭐⭐⭐ Integración zcatttf v1.0 - TTF FUNCIONA PERFECTAMENTE | | 2025-12-17 | v0.17.0 | ⭐⭐⭐ Integración zcatttf v1.0 - TTF FUNCIONA PERFECTAMENTE |
| 2025-12-17 | v0.18.0 | Paridad Visual DVUI Fase 1: RenderMode dual, esquinas redondeadas, sombras |
--- ---
## ESTADO ACTUAL ## ESTADO ACTUAL
**✅ PROYECTO COMPLETADO - v0.18.0** **✅ PROYECTO COMPLETADO - v0.17.0**
> **Para detalles técnicos completos, ver `REFERENCE.md`** (1370 líneas de documentación) > **Para detalles técnicos completos, ver `REFERENCE.md`** (1370 líneas de documentación)

View file

@ -12,9 +12,6 @@ pub const DrawCommand = union(enum) {
/// Draw a filled rectangle /// Draw a filled rectangle
rect: RectCommand, rect: RectCommand,
/// Draw a filled rounded rectangle (fancy mode)
rounded_rect: RoundedRectCommand,
/// Draw text /// Draw text
text: TextCommand, text: TextCommand,
@ -24,9 +21,6 @@ pub const DrawCommand = union(enum) {
/// Draw a rectangle outline (border) /// Draw a rectangle outline (border)
rect_outline: RectOutlineCommand, rect_outline: RectOutlineCommand,
/// Draw a rounded rectangle outline (fancy mode)
rounded_rect_outline: RoundedRectOutlineCommand,
/// Begin clipping to a rectangle /// Begin clipping to a rectangle
clip: ClipCommand, clip: ClipCommand,
@ -75,29 +69,6 @@ pub const RectOutlineCommand = struct {
thickness: u32 = 1, thickness: u32 = 1,
}; };
/// Draw a filled rounded rectangle (fancy mode)
pub const RoundedRectCommand = struct {
x: i32,
y: i32,
w: u32,
h: u32,
color: Style.Color,
radius: u8,
aa: bool = true,
};
/// Draw a rounded rectangle outline (fancy mode)
pub const RoundedRectOutlineCommand = struct {
x: i32,
y: i32,
w: u32,
h: u32,
color: Style.Color,
radius: u8,
thickness: u8 = 1,
aa: bool = true,
};
/// Begin clipping to a rectangle /// Begin clipping to a rectangle
pub const ClipCommand = struct { pub const ClipCommand = struct {
x: i32, x: i32,
@ -168,46 +139,6 @@ pub fn clipEnd() DrawCommand {
return .clip_end; return .clip_end;
} }
/// Create a rounded rect command (fancy mode)
pub fn roundedRect(x: i32, y: i32, w: u32, h: u32, color: Style.Color, radius: u8) DrawCommand {
return .{ .rounded_rect = .{
.x = x,
.y = y,
.w = w,
.h = h,
.color = color,
.radius = radius,
.aa = true,
} };
}
/// Create a rounded rect command with configurable AA
pub fn roundedRectAA(x: i32, y: i32, w: u32, h: u32, color: Style.Color, radius: u8, aa: bool) DrawCommand {
return .{ .rounded_rect = .{
.x = x,
.y = y,
.w = w,
.h = h,
.color = color,
.radius = radius,
.aa = aa,
} };
}
/// Create a rounded rect outline command (fancy mode)
pub fn roundedRectOutline(x: i32, y: i32, w: u32, h: u32, color: Style.Color, radius: u8) DrawCommand {
return .{ .rounded_rect_outline = .{
.x = x,
.y = y,
.w = w,
.h = h,
.color = color,
.radius = radius,
.thickness = 1,
.aa = true,
} };
}
// ============================================================================= // =============================================================================
// Tests // Tests
// ============================================================================= // =============================================================================

View file

@ -4,39 +4,6 @@
const std = @import("std"); const std = @import("std");
// =============================================================================
// Render Mode - Simple vs Fancy
// =============================================================================
/// Render mode controls visual quality vs performance tradeoff
pub const RenderMode = enum {
/// Fast rendering: rectangles, no AA on shapes, no shadows
/// Best for: low-end hardware, SSH, WASM with limited resources
simple,
/// Pretty rendering: rounded corners, edge-fade AA, shadows
/// Best for: desktop with good CPU, visual polish needed
fancy,
};
/// Global render mode - widgets check this to decide how to render
var global_render_mode: RenderMode = .fancy;
/// Get current render mode
pub fn getRenderMode() RenderMode {
return global_render_mode;
}
/// Set render mode
pub fn setRenderMode(mode: RenderMode) void {
global_render_mode = mode;
}
/// Check if fancy rendering is enabled
pub fn isFancy() bool {
return global_render_mode == .fancy;
}
/// RGBA Color /// RGBA Color
pub const Color = struct { pub const Color = struct {
r: u8, r: u8,

View file

@ -217,360 +217,6 @@ pub const Framebuffer = struct {
pub fn getPitch(self: Self) u32 { pub fn getPitch(self: Self) u32 {
return self.width * 4; return self.width * 4;
} }
// =========================================================================
// Rounded Rectangle Drawing (Fancy Mode)
// =========================================================================
/// Draw a filled rounded rectangle with optional edge-fade anti-aliasing
/// radius: corner radius in pixels
/// aa: if true, applies 1-pixel edge fade for smooth borders
pub fn fillRoundedRect(self: *Self, x: i32, y: i32, w: u32, h: u32, color: Color, radius: u8, aa: bool) void {
if (w == 0 or h == 0) return;
// Clamp radius to half the smallest dimension
const max_radius = @min(w, h) / 2;
const r: u32 = @min(@as(u32, radius), max_radius);
if (r == 0) {
// No radius, use fast path
self.fillRect(x, y, w, h, color);
return;
}
// Calculate bounds
const x_start = @max(0, x);
const y_start = @max(0, y);
const x_end = @min(@as(i32, @intCast(self.width)), x + @as(i32, @intCast(w)));
const y_end = @min(@as(i32, @intCast(self.height)), y + @as(i32, @intCast(h)));
if (x_start >= x_end or y_start >= y_end) return;
// Corner circle centers (relative to rect origin)
const r_i32: i32 = @intCast(r);
const w_i32: i32 = @intCast(w);
const h_i32: i32 = @intCast(h);
// Corner centers in screen coordinates
const tl_cx = x + r_i32; // top-left
const tl_cy = y + r_i32;
const tr_cx = x + w_i32 - r_i32; // top-right
const tr_cy = y + r_i32;
const bl_cx = x + r_i32; // bottom-left
const bl_cy = y + h_i32 - r_i32;
const br_cx = x + w_i32 - r_i32; // bottom-right
const br_cy = y + h_i32 - r_i32;
const r_f: f32 = @floatFromInt(r);
var py = y_start;
while (py < y_end) : (py += 1) {
const row_start = @as(u32, @intCast(py)) * self.width;
var px = x_start;
while (px < x_end) : (px += 1) {
// Check which region the pixel is in
const in_corner = self.getCornerDistance(px, py, x, y, w_i32, h_i32, tl_cx, tl_cy, tr_cx, tr_cy, bl_cx, bl_cy, br_cx, br_cy, r_f);
if (in_corner) |dist| {
// In corner region - check distance to arc
if (dist <= r_f) {
// Inside the arc
if (aa and dist > r_f - 1.0) {
// Edge fade zone (last pixel)
const alpha_f = r_f - dist;
const alpha: u8 = @intFromFloat(@min(255.0, @max(0.0, alpha_f * 255.0)));
const aa_color = color.withAlpha(@as(u8, @intCast((@as(u16, color.a) * alpha) / 255)));
self.blendPixelAt(row_start + @as(u32, @intCast(px)), aa_color);
} else {
// Fully inside
self.blendPixelAt(row_start + @as(u32, @intCast(px)), color);
}
}
// Outside arc - don't draw
} else {
// Not in corner region - check edge fade for straight edges
if (aa) {
const edge_dist = self.getEdgeDistance(px, py, x, y, w_i32, h_i32);
if (edge_dist < 1.0) {
const alpha: u8 = @intFromFloat(@min(255.0, edge_dist * 255.0));
const aa_color = color.withAlpha(@as(u8, @intCast((@as(u16, color.a) * alpha) / 255)));
self.blendPixelAt(row_start + @as(u32, @intCast(px)), aa_color);
} else {
self.blendPixelAt(row_start + @as(u32, @intCast(px)), color);
}
} else {
self.blendPixelAt(row_start + @as(u32, @intCast(px)), color);
}
}
}
}
}
/// Draw a rounded rectangle outline with optional AA
pub fn drawRoundedRect(self: *Self, x: i32, y: i32, w: u32, h: u32, color: Color, radius: u8, thickness: u8, aa: bool) void {
if (w == 0 or h == 0 or thickness == 0) return;
const t: u32 = thickness;
// For thin outlines, we can use the difference of two rounded rects
// Outer rect
self.fillRoundedRect(x, y, w, h, color, radius, aa);
// Inner rect (punch out with background)
// This is a simplification - proper impl would track background color
// For now, we'll draw the outline pixel by pixel
// Actually, let's do this properly with a stroke approach
const max_radius = @min(w, h) / 2;
const r: u32 = @min(@as(u32, radius), max_radius);
const inner_r: u32 = if (r > t) r - t else 0;
// Draw using edge detection
const x_start = @max(0, x);
const y_start = @max(0, y);
const x_end = @min(@as(i32, @intCast(self.width)), x + @as(i32, @intCast(w)));
const y_end = @min(@as(i32, @intCast(self.height)), y + @as(i32, @intCast(h)));
if (x_start >= x_end or y_start >= y_end) return;
const r_i32: i32 = @intCast(r);
const w_i32: i32 = @intCast(w);
const h_i32: i32 = @intCast(h);
const t_i32: i32 = @intCast(t);
const r_f: f32 = @floatFromInt(r);
const inner_r_f: f32 = @floatFromInt(inner_r);
const t_f: f32 = @floatFromInt(t);
// Corner centers
const tl_cx = x + r_i32;
const tl_cy = y + r_i32;
const tr_cx = x + w_i32 - r_i32;
const tr_cy = y + r_i32;
const bl_cx = x + r_i32;
const bl_cy = y + h_i32 - r_i32;
const br_cx = x + w_i32 - r_i32;
const br_cy = y + h_i32 - r_i32;
var py = y_start;
while (py < y_end) : (py += 1) {
const row_start = @as(u32, @intCast(py)) * self.width;
var px = x_start;
while (px < x_end) : (px += 1) {
// Check if pixel is in the stroke region (between outer and inner bounds)
const in_stroke = self.isInStroke(px, py, x, y, w_i32, h_i32, t_i32, tl_cx, tl_cy, tr_cx, tr_cy, bl_cx, bl_cy, br_cx, br_cy, r_f, inner_r_f, t_f, aa);
if (in_stroke) |alpha_mult| {
if (alpha_mult >= 1.0) {
self.blendPixelAt(row_start + @as(u32, @intCast(px)), color);
} else if (alpha_mult > 0.0) {
const alpha: u8 = @intFromFloat(@min(255.0, alpha_mult * 255.0));
const aa_color = color.withAlpha(@as(u8, @intCast((@as(u16, color.a) * alpha) / 255)));
self.blendPixelAt(row_start + @as(u32, @intCast(px)), aa_color);
}
}
}
}
}
// Helper: blend pixel at index with alpha
fn blendPixelAt(self: *Self, idx: u32, color: Color) void {
if (idx >= self.pixels.len) return;
if (color.a == 255) {
self.pixels[idx] = color.toABGR();
} else if (color.a > 0) {
const existing = self.pixels[idx];
const bg = Color{
.r = @truncate(existing),
.g = @truncate(existing >> 8),
.b = @truncate(existing >> 16),
.a = @truncate(existing >> 24),
};
self.pixels[idx] = color.blend(bg).toABGR();
}
}
// Helper: get distance from pixel to corner arc (null if not in corner region)
fn getCornerDistance(
self: *Self,
px: i32,
py: i32,
rect_x: i32,
rect_y: i32,
rect_w: i32,
rect_h: i32,
tl_cx: i32,
tl_cy: i32,
tr_cx: i32,
tr_cy: i32,
bl_cx: i32,
bl_cy: i32,
br_cx: i32,
br_cy: i32,
radius: f32,
) ?f32 {
_ = self;
_ = rect_w;
_ = rect_h;
// Check if pixel is in a corner region
// Top-left corner
if (px < tl_cx and py < tl_cy) {
const dx: f32 = @floatFromInt(tl_cx - px);
const dy: f32 = @floatFromInt(tl_cy - py);
return @sqrt(dx * dx + dy * dy);
}
// Top-right corner
if (px > tr_cx and py < tr_cy) {
const dx: f32 = @floatFromInt(px - tr_cx);
const dy: f32 = @floatFromInt(tr_cy - py);
return @sqrt(dx * dx + dy * dy);
}
// Bottom-left corner
if (px < bl_cx and py > bl_cy) {
const dx: f32 = @floatFromInt(bl_cx - px);
const dy: f32 = @floatFromInt(py - bl_cy);
return @sqrt(dx * dx + dy * dy);
}
// Bottom-right corner
if (px > br_cx and py > br_cy) {
const dx: f32 = @floatFromInt(px - br_cx);
const dy: f32 = @floatFromInt(py - br_cy);
return @sqrt(dx * dx + dy * dy);
}
// Also check if outside rect bounds entirely
if (px < rect_x or py < rect_y) return radius + 10.0; // Outside
return null; // Not in corner region
}
// Helper: get minimum distance to edge (for straight edges AA)
fn getEdgeDistance(self: *Self, px: i32, py: i32, rect_x: i32, rect_y: i32, rect_w: i32, rect_h: i32) f32 {
_ = self;
const left: f32 = @floatFromInt(px - rect_x);
const right: f32 = @floatFromInt((rect_x + rect_w - 1) - px);
const top: f32 = @floatFromInt(py - rect_y);
const bottom: f32 = @floatFromInt((rect_y + rect_h - 1) - py);
// Return minimum distance to any edge (clamped to positive)
return @max(0.0, @min(@min(left, right), @min(top, bottom))) + 1.0;
}
// Helper: check if pixel is in stroke region for outline
fn isInStroke(
self: *Self,
px: i32,
py: i32,
rect_x: i32,
rect_y: i32,
rect_w: i32,
rect_h: i32,
thickness: i32,
tl_cx: i32,
tl_cy: i32,
tr_cx: i32,
tr_cy: i32,
bl_cx: i32,
bl_cy: i32,
br_cx: i32,
br_cy: i32,
outer_r: f32,
inner_r: f32,
t_f: f32,
aa: bool,
) ?f32 {
_ = self;
_ = thickness;
// Check corners first
// Top-left
if (px < tl_cx and py < tl_cy) {
const dx: f32 = @floatFromInt(tl_cx - px);
const dy: f32 = @floatFromInt(tl_cy - py);
const dist = @sqrt(dx * dx + dy * dy);
if (dist > outer_r) {
if (aa and dist < outer_r + 1.0) return outer_r + 1.0 - dist;
return null;
}
if (dist < inner_r) {
if (aa and dist > inner_r - 1.0) return dist - (inner_r - 1.0);
return null;
}
return 1.0;
}
// Top-right
if (px > tr_cx and py < tr_cy) {
const dx: f32 = @floatFromInt(px - tr_cx);
const dy: f32 = @floatFromInt(tr_cy - py);
const dist = @sqrt(dx * dx + dy * dy);
if (dist > outer_r) {
if (aa and dist < outer_r + 1.0) return outer_r + 1.0 - dist;
return null;
}
if (dist < inner_r) {
if (aa and dist > inner_r - 1.0) return dist - (inner_r - 1.0);
return null;
}
return 1.0;
}
// Bottom-left
if (px < bl_cx and py > bl_cy) {
const dx: f32 = @floatFromInt(bl_cx - px);
const dy: f32 = @floatFromInt(py - bl_cy);
const dist = @sqrt(dx * dx + dy * dy);
if (dist > outer_r) {
if (aa and dist < outer_r + 1.0) return outer_r + 1.0 - dist;
return null;
}
if (dist < inner_r) {
if (aa and dist > inner_r - 1.0) return dist - (inner_r - 1.0);
return null;
}
return 1.0;
}
// Bottom-right
if (px > br_cx and py > br_cy) {
const dx: f32 = @floatFromInt(px - br_cx);
const dy: f32 = @floatFromInt(py - br_cy);
const dist = @sqrt(dx * dx + dy * dy);
if (dist > outer_r) {
if (aa and dist < outer_r + 1.0) return outer_r + 1.0 - dist;
return null;
}
if (dist < inner_r) {
if (aa and dist > inner_r - 1.0) return dist - (inner_r - 1.0);
return null;
}
return 1.0;
}
// Straight edges
const left_dist: f32 = @floatFromInt(px - rect_x);
const right_dist: f32 = @floatFromInt((rect_x + rect_w - 1) - px);
const top_dist: f32 = @floatFromInt(py - rect_y);
const bottom_dist: f32 = @floatFromInt((rect_y + rect_h - 1) - py);
// Check if in stroke region for straight edges
const in_left_stroke = left_dist >= 0 and left_dist < t_f;
const in_right_stroke = right_dist >= 0 and right_dist < t_f;
const in_top_stroke = top_dist >= 0 and top_dist < t_f;
const in_bottom_stroke = bottom_dist >= 0 and bottom_dist < t_f;
if (in_left_stroke or in_right_stroke or in_top_stroke or in_bottom_stroke) {
// AA for outer edge
if (aa) {
const min_outer = @min(@min(left_dist, right_dist), @min(top_dist, bottom_dist));
if (min_outer < 1.0 and min_outer >= 0) {
return min_outer;
}
}
return 1.0;
}
return null;
}
}; };
// ============================================================================= // =============================================================================

View file

@ -86,11 +86,9 @@ pub const SoftwareRenderer = struct {
pub fn execute(self: *Self, cmd: DrawCommand) void { pub fn execute(self: *Self, cmd: DrawCommand) void {
switch (cmd) { switch (cmd) {
.rect => |r| self.drawRect(r), .rect => |r| self.drawRect(r),
.rounded_rect => |r| self.drawRoundedRect(r),
.text => |t| self.drawText(t), .text => |t| self.drawText(t),
.line => |l| self.drawLine(l), .line => |l| self.drawLine(l),
.rect_outline => |r| self.drawRectOutline(r), .rect_outline => |r| self.drawRectOutline(r),
.rounded_rect_outline => |r| self.drawRoundedRectOutline(r),
.clip => |c| self.pushClip(c), .clip => |c| self.pushClip(c),
.clip_end => self.popClip(), .clip_end => self.popClip(),
.nop => {}, .nop => {},
@ -289,17 +287,6 @@ pub const SoftwareRenderer = struct {
} }
} }
fn drawRoundedRect(self: *Self, r: Command.RoundedRectCommand) void {
// TODO: Apply clipping (for now, draw directly)
// The fillRoundedRect function handles bounds checking internally
self.framebuffer.fillRoundedRect(r.x, r.y, r.w, r.h, r.color, r.radius, r.aa);
}
fn drawRoundedRectOutline(self: *Self, r: Command.RoundedRectOutlineCommand) void {
// TODO: Apply clipping
self.framebuffer.drawRoundedRect(r.x, r.y, r.w, r.h, r.color, r.radius, r.thickness, r.aa);
}
fn pushClip(self: *Self, c: Command.ClipCommand) void { fn pushClip(self: *Self, c: Command.ClipCommand) void {
if (self.clip_depth >= self.clip_stack.len) return; if (self.clip_depth >= self.clip_stack.len) return;
@ -326,7 +313,6 @@ pub const SoftwareRenderer = struct {
fn commandBounds(cmd: DrawCommand) ?Rect { fn commandBounds(cmd: DrawCommand) ?Rect {
return switch (cmd) { return switch (cmd) {
.rect => |r| Rect.init(r.x, r.y, r.w, r.h), .rect => |r| Rect.init(r.x, r.y, r.w, r.h),
.rounded_rect => |r| Rect.init(r.x, r.y, r.w, r.h),
.text => |t| blk: { .text => |t| blk: {
// Estimate text bounds (width based on text length, height based on font) // Estimate text bounds (width based on text length, height based on font)
// This is approximate; actual font metrics would be better // This is approximate; actual font metrics would be better
@ -348,7 +334,6 @@ fn commandBounds(cmd: DrawCommand) ?Rect {
); );
}, },
.rect_outline => |r| Rect.init(r.x, r.y, r.w, r.h), .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),
.clip, .clip_end, .nop => null, .clip, .clip_end, .nop => null,
}; };
} }

View file

@ -29,8 +29,6 @@ pub const ButtonConfig = struct {
disabled: bool = false, disabled: bool = false,
/// Padding around text /// Padding around text
padding: u32 = 8, padding: u32 = 8,
/// Corner radius (0 = square, default 4 for fancy mode)
corner_radius: u8 = 4,
}; };
/// Draw a button and return true if clicked /// Draw a button and return true if clicked
@ -80,16 +78,11 @@ pub fn buttonRect(ctx: *Context, bounds: Layout.Rect, text: []const u8, config:
else else
theme.button_fg; theme.button_fg;
// Draw background and border based on render mode // Draw background
if (Style.isFancy() and config.corner_radius > 0) { ctx.pushCommand(Command.rect(bounds.x, bounds.y, bounds.w, bounds.h, bg_color));
// Fancy mode: rounded corners with AA
ctx.pushCommand(Command.roundedRect(bounds.x, bounds.y, bounds.w, bounds.h, bg_color, config.corner_radius)); // Draw border
ctx.pushCommand(Command.roundedRectOutline(bounds.x, bounds.y, bounds.w, bounds.h, theme.border, config.corner_radius)); ctx.pushCommand(Command.rectOutline(bounds.x, bounds.y, bounds.w, bounds.h, theme.border));
} else {
// Simple mode: square corners
ctx.pushCommand(Command.rect(bounds.x, bounds.y, bounds.w, bounds.h, bg_color));
ctx.pushCommand(Command.rectOutline(bounds.x, bounds.y, bounds.w, bounds.h, theme.border));
}
// Draw text centered // Draw text centered
const char_width: u32 = 8; const char_width: u32 = 8;

View file

@ -111,10 +111,6 @@ pub const ModalConfig = struct {
show_input: bool = false, show_input: bool = false,
/// Input placeholder /// Input placeholder
input_placeholder: []const u8 = "", input_placeholder: []const u8 = "",
/// Corner radius (default 8 for fancy mode)
corner_radius: u8 = 8,
/// Show shadow (fancy mode only)
show_shadow: bool = true,
}; };
/// Modal colors /// Modal colors
@ -131,8 +127,6 @@ pub const ModalColors = struct {
title_fg: Style.Color = Style.Color.rgb(220, 220, 220), title_fg: Style.Color = Style.Color.rgb(220, 220, 220),
/// Message text color /// Message text color
message_fg: Style.Color = Style.Color.rgb(200, 200, 200), message_fg: Style.Color = Style.Color.rgb(200, 200, 200),
/// Shadow color (fancy mode only)
shadow: Style.Color = Style.Color.rgba(0, 0, 0, 80),
}; };
/// Modal result /// Modal result
@ -193,40 +187,19 @@ pub fn modalEx(
// Draw backdrop (semi-transparent overlay) // Draw backdrop (semi-transparent overlay)
ctx.pushCommand(Command.rect(0, 0, screen_w, screen_h, colors.backdrop)); ctx.pushCommand(Command.rect(0, 0, screen_w, screen_h, colors.backdrop));
// Check render mode for fancy features // Draw dialog border
const fancy = Style.isFancy() and config.corner_radius > 0; ctx.pushCommand(Command.rectOutline(
dialog_x - 1,
dialog_y - 1,
dialog_w + 2,
dialog_h + 2,
colors.border,
));
// Draw shadow first (behind dialog) in fancy mode // Draw dialog background
if (fancy and config.show_shadow) { ctx.pushCommand(Command.rect(dialog_x, dialog_y, dialog_w, dialog_h, colors.background));
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,
));
}
// Draw dialog border and background based on render mode // Draw title bar
if (fancy) {
// Fancy mode: rounded corners
ctx.pushCommand(Command.roundedRect(dialog_x, dialog_y, dialog_w, dialog_h, colors.background, config.corner_radius));
ctx.pushCommand(Command.roundedRectOutline(dialog_x, dialog_y, dialog_w, dialog_h, colors.border, config.corner_radius));
} else {
// Simple mode: square corners
ctx.pushCommand(Command.rectOutline(
dialog_x - 1,
dialog_y - 1,
dialog_w + 2,
dialog_h + 2,
colors.border,
));
ctx.pushCommand(Command.rect(dialog_x, dialog_y, dialog_w, dialog_h, colors.background));
}
// Draw title bar (inside dialog, so no rounded corners needed)
ctx.pushCommand(Command.rect(dialog_x, dialog_y, dialog_w, title_h, colors.title_bg)); ctx.pushCommand(Command.rect(dialog_x, dialog_y, dialog_w, title_h, colors.title_bg));
// Draw title text // Draw title text
@ -250,16 +223,10 @@ pub fn modalEx(
24, 24,
); );
// Input rendering // Simple input rendering
const input_bg = Style.Color.rgb(35, 35, 40); const input_bg = Style.Color.rgb(35, 35, 40);
const input_radius: u8 = 3; ctx.pushCommand(Command.rect(input_rect.x, input_rect.y, input_rect.w, input_rect.h, input_bg));
if (fancy) { ctx.pushCommand(Command.rectOutline(input_rect.x, input_rect.y, input_rect.w, input_rect.h, colors.border));
ctx.pushCommand(Command.roundedRect(input_rect.x, input_rect.y, input_rect.w, input_rect.h, input_bg, input_radius));
ctx.pushCommand(Command.roundedRectOutline(input_rect.x, input_rect.y, input_rect.w, input_rect.h, colors.border, input_radius));
} else {
ctx.pushCommand(Command.rect(input_rect.x, input_rect.y, input_rect.w, input_rect.h, input_bg));
ctx.pushCommand(Command.rectOutline(input_rect.x, input_rect.y, input_rect.w, input_rect.h, colors.border));
}
const txt = input_st.text(); const txt = input_st.text();
if (txt.len > 0) { if (txt.len > 0) {
@ -297,17 +264,10 @@ pub fn modalEx(
.danger => Style.Color.danger.darken(30), .danger => Style.Color.danger.darken(30),
}; };
const btn_radius: u8 = 4; ctx.pushCommand(Command.rect(btn_x, btn_y, btn_width, button_h - 4, btn_bg));
if (fancy) {
ctx.pushCommand(Command.roundedRect(btn_x, btn_y, btn_width, button_h - 4, btn_bg, btn_radius)); if (is_focused) {
if (is_focused) { ctx.pushCommand(Command.rectOutline(btn_x, btn_y, btn_width, button_h - 4, Style.Color.rgb(200, 200, 200)));
ctx.pushCommand(Command.roundedRectOutline(btn_x, btn_y, btn_width, button_h - 4, Style.Color.rgb(200, 200, 200), btn_radius));
}
} else {
ctx.pushCommand(Command.rect(btn_x, btn_y, btn_width, button_h - 4, btn_bg));
if (is_focused) {
ctx.pushCommand(Command.rectOutline(btn_x, btn_y, btn_width, button_h - 4, Style.Color.rgb(200, 200, 200)));
}
} }
// Button text // Button text

View file

@ -32,10 +32,6 @@ pub const PanelConfig = struct {
collapsible: bool = false, collapsible: bool = false,
/// Show close button (X) /// Show close button (X)
closable: bool = false, closable: bool = false,
/// Corner radius (default 6 for fancy mode)
corner_radius: u8 = 6,
/// Show shadow (fancy mode only)
show_shadow: bool = true,
}; };
/// Panel colors /// Panel colors
@ -46,7 +42,6 @@ pub const PanelColors = struct {
content_bg: Style.Color = Style.Color.rgb(35, 35, 40), content_bg: Style.Color = Style.Color.rgb(35, 35, 40),
border: Style.Color = Style.Color.rgb(70, 70, 75), border: Style.Color = Style.Color.rgb(70, 70, 75),
border_focused: Style.Color = Style.Color.primary, border_focused: Style.Color = Style.Color.primary,
shadow: Style.Color = Style.Color.rgba(0, 0, 0, 60),
}; };
/// Panel result /// Panel result
@ -109,28 +104,8 @@ pub fn panelRect(
// Border color // Border color
const border_color = if (state.focused) colors.border_focused else colors.border; const border_color = if (state.focused) colors.border_focused else colors.border;
// Check render mode for fancy features
const fancy = Style.isFancy() and config.corner_radius > 0;
// 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,
));
}
// Draw outer border // Draw outer border
if (fancy) { ctx.pushCommand(Command.rectOutline(bounds.x, bounds.y, bounds.w, bounds.h, border_color));
ctx.pushCommand(Command.roundedRectOutline(bounds.x, bounds.y, bounds.w, bounds.h, border_color, config.corner_radius));
} else {
ctx.pushCommand(Command.rectOutline(bounds.x, bounds.y, bounds.w, bounds.h, border_color));
}
// Title bar bounds // Title bar bounds
const title_bounds = Layout.Rect.init( const title_bounds = Layout.Rect.init(

View file

@ -40,8 +40,6 @@ pub const SelectConfig = struct {
item_height: u32 = 24, item_height: u32 = 24,
/// Padding /// Padding
padding: u32 = 4, padding: u32 = 4,
/// Corner radius (default 3 for fancy mode)
corner_radius: u8 = 3,
}; };
/// Select result /// Select result
@ -122,14 +120,9 @@ pub fn selectRect(
const border_color = if (has_focus or state.open) theme.primary else theme.border; const border_color = if (has_focus or state.open) theme.primary else theme.border;
// Draw main button background based on render mode // Draw main button background
if (Style.isFancy() and config.corner_radius > 0) { ctx.pushCommand(Command.rect(bounds.x, bounds.y, bounds.w, bounds.h, bg_color));
ctx.pushCommand(Command.roundedRect(bounds.x, bounds.y, bounds.w, bounds.h, bg_color, config.corner_radius)); ctx.pushCommand(Command.rectOutline(bounds.x, bounds.y, bounds.w, bounds.h, border_color));
ctx.pushCommand(Command.roundedRectOutline(bounds.x, bounds.y, bounds.w, bounds.h, border_color, config.corner_radius));
} else {
ctx.pushCommand(Command.rect(bounds.x, bounds.y, bounds.w, bounds.h, bg_color));
ctx.pushCommand(Command.rectOutline(bounds.x, bounds.y, bounds.w, bounds.h, border_color));
}
// Draw selected text or placeholder // Draw selected text or placeholder
const display_text = if (state.selectedIndex()) |idx| const display_text = if (state.selectedIndex()) |idx|

View file

@ -215,8 +215,6 @@ pub const TextInputConfig = struct {
text_color: ?Style.Color = null, text_color: ?Style.Color = null,
/// Override border color (for validation feedback). If null, uses theme default. /// Override border color (for validation feedback). If null, uses theme default.
border_color: ?Style.Color = null, border_color: ?Style.Color = null,
/// Corner radius (default 3 for fancy mode)
corner_radius: u8 = 3,
}; };
/// Result of text input widget /// Result of text input widget
@ -286,16 +284,11 @@ pub fn textInputRect(
const text_color = config.text_color orelse theme.input_fg; const text_color = config.text_color orelse theme.input_fg;
const placeholder_color = theme.secondary; const placeholder_color = theme.secondary;
// Draw background and border based on render mode // Draw background
if (Style.isFancy() and config.corner_radius > 0) { ctx.pushCommand(Command.rect(bounds.x, bounds.y, bounds.w, bounds.h, bg_color));
// Fancy mode: rounded corners
ctx.pushCommand(Command.roundedRect(bounds.x, bounds.y, bounds.w, bounds.h, bg_color, config.corner_radius)); // Draw border
ctx.pushCommand(Command.roundedRectOutline(bounds.x, bounds.y, bounds.w, bounds.h, border_color, config.corner_radius)); ctx.pushCommand(Command.rectOutline(bounds.x, bounds.y, bounds.w, bounds.h, border_color));
} else {
// Simple mode: square corners
ctx.pushCommand(Command.rect(bounds.x, bounds.y, bounds.w, bounds.h, bg_color));
ctx.pushCommand(Command.rectOutline(bounds.x, bounds.y, bounds.w, bounds.h, border_color));
}
// Inner area // Inner area
const inner = bounds.shrink(config.padding); const inner = bounds.shrink(config.padding);