Compare commits

..

No commits in common. "9c29faaa810c6b25134fe76b54c2f740649175bd" and "b67de1ce01b03e9d121ab6093f0a46b44cf0b129" have entirely different histories.

12 changed files with 38 additions and 77 deletions

View file

@ -285,14 +285,6 @@ zcatgui/
## HITOS RECIENTES
### Liquid UI V2 ✅ (2025-12-30)
Sistema de transiciones de color suaves para paneles:
- **ColorTransition**: 500ms (medio segundo) para transiciones perceptibles
- **Mayor contraste**: Dark mode 4%/20% base, Light mode 1%/6% base
- **requestAnimationFrame()**: Paneles solicitan redraw durante animación
- Fondo de paneles "fluye" al cambiar focus
→ Archivos: `render/animation.zig`, `core/style.zig`, `core/context.zig`
### IdleCompanion Widget ✅ (2025-12-30)
Mascota animada que aparece tras inactividad del usuario:
- Se asoma por bordes de paneles aleatorios

View file

@ -102,10 +102,6 @@ pub const Context = struct {
/// Used for idle detection (e.g., cursor stops blinking after inactivity)
last_input_time_ms: u64 = 0,
/// Flag set by widgets that have ongoing animations (e.g., color transitions).
/// Main loop should check this and request another frame if true.
needs_animation_frame: bool = false,
/// Optional text measurement function (set by application with TTF font)
/// Returns pixel width of text. If null, falls back to char_width * len.
text_measure_fn: ?*const fn ([]const u8) u32 = null,
@ -208,9 +204,6 @@ pub const Context = struct {
// Focus system frame start
self.focus.beginFrame();
// Reset animation request (set by widgets during draw)
self.needs_animation_frame = false;
self.frame += 1;
}
@ -370,19 +363,6 @@ pub const Context = struct {
return @intCast(@max(time_until_toggle, 16)); // Minimum 16ms to avoid busy loop
}
/// Request another animation frame (for color transitions, etc.).
/// Widgets call this during draw when they have ongoing animations.
/// Main loop should check needsAnimationFrame() after draw and schedule redraw.
pub fn requestAnimationFrame(self: *Self) void {
self.needs_animation_frame = true;
}
/// Check if any widget requested an animation frame.
/// Call after draw to determine if immediate redraw is needed.
pub fn needsAnimationFrame(self: Self) bool {
return self.needs_animation_frame;
}
/// Determina si el cursor debe ser visible basado en tiempo de actividad.
/// Usa parpadeo mientras hay actividad reciente, sólido cuando está idle.
/// @param last_activity_time_ms: Última vez que hubo actividad (edición, input)

View file

@ -1031,9 +1031,9 @@ pub fn derivePanelPalette(base: Color, mode: ThemeMode) PanelColorScheme {
}
/// Derive palette for dark mode (dark backgrounds, light text)
/// Z-Design V2 + Liquid UI: Mayor contraste para transiciones perceptibles
/// - fondo_sin_focus: 4% base color (más oscuro, punto de partida bajo)
/// - fondo_con_focus: 20% base color (brilla al ganar foco, destino alto)
/// Z-Design V2: "Atmósfera, no fogonazo"
/// - fondo_sin_focus: 7% base color (sutil identidad del panel)
/// - fondo_con_focus: 15% base color (brilla al ganar foco)
fn deriveDarkPalette(base: Color) PanelColorScheme {
// Reference colors for dark mode
const black = Color.soft_black; // RGB(17, 17, 20) - not pure black
@ -1042,11 +1042,11 @@ fn deriveDarkPalette(base: Color) PanelColorScheme {
const dark_border = Color.rgb(60, 60, 65);
return .{
// Backgrounds: Liquid UI V2 - mayor recorrido para transición perceptible
// Focus: 20% base, 80% black (destino luminoso)
.fondo_con_focus = base.blendTowards(black, 80),
// Sin focus: 4% base, 96% black (punto de partida oscuro)
.fondo_sin_focus = base.blendTowards(black, 96),
// Backgrounds: Z-Design V2 - panel siempre tiene su "identidad"
// Focus: 15% base, 85% black (brilla más)
.fondo_con_focus = base.blendTowards(black, 85),
// Sin focus: 7% base, 93% black (sutil pero presente)
.fondo_sin_focus = base.blendTowards(black, 93),
// Text: high contrast
.datos = white,
@ -1068,9 +1068,9 @@ fn deriveDarkPalette(base: Color) PanelColorScheme {
}
/// Derive palette for light mode (light backgrounds, dark text)
/// Z-Design V2 + Liquid UI: Mayor contraste para transiciones perceptibles
/// - fondo_sin_focus: 1% base (casi blanco, punto de partida)
/// - fondo_con_focus: 6% base (brilla al ganar foco, destino)
/// Z-Design V2: Sincronizado con dark mode
/// - fondo_sin_focus: 1% base (casi blanco, sutil identidad)
/// - fondo_con_focus: 3% base (brilla al ganar foco)
fn deriveLightPalette(base: Color) PanelColorScheme {
// Reference colors for light mode
const white = Color.soft_white; // RGB(250, 250, 252) - slight cool tint
@ -1079,10 +1079,10 @@ fn deriveLightPalette(base: Color) PanelColorScheme {
const light_border = Color.rgb(220, 220, 225);
return .{
// Backgrounds: Liquid UI V2 - mayor recorrido para transición perceptible
// Focus: 6% base, 94% white (destino más tintado)
.fondo_con_focus = base.blendTowards(white, 94),
// Sin focus: 1% base, 99% white (punto de partida neutro)
// Backgrounds: Z-Design V2 - panel siempre tiene su "identidad"
// Focus: 3% base, 97% white
.fondo_con_focus = base.blendTowards(white, 97),
// Sin focus: 1% base, 99% white (sutil pero presente)
.fondo_sin_focus = base.blendTowards(white, 99),
// Text: high contrast
@ -1151,11 +1151,11 @@ test "derivePanelPalette dark mode" {
try std.testing.expectEqual(Color.laravel_red.g, palette.seleccion_fondo_con_focus.g);
try std.testing.expectEqual(Color.laravel_red.b, palette.seleccion_fondo_con_focus.b);
// Background should be dark with visible tint (Liquid UI V2: 20% base color)
// 80% blend towards black = dark with noticeable tint for transitions
try std.testing.expect(palette.fondo_con_focus.r < 65); // Red tint visible
try std.testing.expect(palette.fondo_con_focus.g < 35);
try std.testing.expect(palette.fondo_con_focus.b < 35);
// Background should be very dark (close to black, slight red tint)
// 95% blend towards black = mostly black with 5% of base color
try std.testing.expect(palette.fondo_con_focus.r < 35); // Red tint visible
try std.testing.expect(palette.fondo_con_focus.g < 25);
try std.testing.expect(palette.fondo_con_focus.b < 25);
// The red component should be slightly higher than G/B (tint visible)
try std.testing.expect(palette.fondo_con_focus.r >= palette.fondo_con_focus.g);
@ -1167,11 +1167,10 @@ test "derivePanelPalette light mode" {
// Selection should be the full base color
try std.testing.expectEqual(Color.laravel_blue.r, palette.seleccion_fondo_con_focus.r);
// Background should be light with visible tint (Liquid UI V2: 6% base color)
// 94% blend towards white = light with noticeable tint for transitions
try std.testing.expect(palette.fondo_con_focus.r > 230);
try std.testing.expect(palette.fondo_con_focus.g > 235);
try std.testing.expect(palette.fondo_con_focus.b > 240);
// Background should be very light (close to white, slight blue tint)
try std.testing.expect(palette.fondo_con_focus.r > 240);
try std.testing.expect(palette.fondo_con_focus.g > 245);
try std.testing.expect(palette.fondo_con_focus.b > 250);
}
test "contrastTextColor" {

View file

@ -72,8 +72,8 @@ pub const ActionButtonsOpts = struct {
can_delete: bool = false,
/// Ancho de cada boton
button_width: u32 = 80,
/// Alto de cada boton (Z-Design V2: reducido de 28 a 22px)
button_height: u32 = 22,
/// Alto de cada boton
button_height: u32 = 28,
/// Espacio entre botones
spacing: i32 = 8,
/// Offset X desde el borde izquierdo del panel
@ -96,8 +96,8 @@ pub const NavButtonsOpts = struct {
can_last: bool = false,
/// Ancho de cada boton
button_width: u32 = 40,
/// Alto de cada boton (Z-Design V2: reducido de 28 a 22px)
button_height: u32 = 22,
/// Alto de cada boton
button_height: u32 = 28,
/// Offset X desde el borde izquierdo del panel
x_offset: i32 = 8,
};

View file

@ -704,8 +704,8 @@ pub const ColorTransition = struct {
current: Style.Color = Style.Color.rgb(0, 0, 0),
/// Is initialized with a color?
initialized: bool = false,
/// Transition duration in milliseconds (500ms = Liquid UI feel)
duration_ms: f32 = 500.0,
/// Transition duration in milliseconds
duration_ms: f32 = 200.0,
const Self = @This();

View file

@ -170,16 +170,6 @@ pub fn advancedTableRect(
var memory_ds = MemoryDataSource.init(table_state, table_schema.columns);
const data_src = memory_ds.toDataSource();
// Z-Design: Pintar fondo del área de contenido ANTES de las filas
// Esto asegura que tablas vacías o con pocas filas no muestren negro
ctx.pushCommand(Command.rect(
bounds.x,
bounds.y + @as(i32, @intCast(header_h)),
bounds.w,
content_h,
colors.row_normal,
));
// Construir RowRenderColors manualmente (los dos TableColors son tipos diferentes)
const render_colors = table_core.RowRenderColors{
.row_normal = colors.row_normal,

View file

@ -130,7 +130,7 @@ pub fn drawScrollbar(
const total_rows = table_state.getRowCount();
if (total_rows == 0) return;
const scrollbar_w: u32 = 14; // Z-Design V2: más ancho para mejor visibilidad
const scrollbar_w: u32 = 12;
const header_h: u32 = if (config.show_headers) config.header_height else 0;
const scrollbar_h = bounds.h -| header_h;

View file

@ -346,7 +346,7 @@ pub fn gridRect(
}
fn drawScrollbar(ctx: *Context, bounds: Layout.Rect, scroll_y: i32, total_height: u32, visible_height: u32, colors: Colors) void {
const scrollbar_width: u32 = 14; // Z-Design V2: más ancho
const scrollbar_width: u32 = 8;
const scrollbar_x = bounds.x + @as(i32, @intCast(bounds.w)) - @as(i32, @intCast(scrollbar_width)) - 2;
const scrollbar_y = bounds.y + 2;
const scrollbar_h = bounds.h -| 4;

View file

@ -236,7 +236,7 @@ pub fn listRect(
// Draw scrollbar if needed
if (items.len > visible_count) {
const scrollbar_w: u32 = 14; // Z-Design V2: más ancho para mejor visibilidad
const scrollbar_w: u32 = 8;
const scrollbar_x = bounds.x + @as(i32, @intCast(bounds.w)) - @as(i32, @intCast(scrollbar_w + 1));
// Scrollbar track

View file

@ -367,7 +367,7 @@ pub fn drawScrollbar(
) void {
_ = config;
const scrollbar_w: u32 = 14; // Z-Design V2: más ancho
const scrollbar_w: u32 = 12;
const header_h: u32 = 28; // Assume header
const track_x = bounds.right() - @as(i32, @intCast(scrollbar_w));

View file

@ -36,7 +36,7 @@ pub fn drawFilterBar(
const chip_h: u32 = 22;
const chip_padding: i32 = 10;
const chip_spacing: i32 = 6;
const chip_radius: u8 = 4; // Z-Design V2: consistente con botones
const chip_radius: u8 = 11;
const clear_btn_w: u32 = 22;
// Background
@ -446,7 +446,7 @@ pub fn drawScrollbar(
total_rows: usize,
colors: *const VirtualAdvancedTableConfig.Colors,
) void {
const scrollbar_w: u32 = 14; // Z-Design V2: más ancho para mejor visibilidad
const scrollbar_w: u32 = 12;
const content_h = bounds.h -| header_h -| footer_h;
table_core.drawVerticalScrollbar(ctx, .{

View file

@ -129,8 +129,8 @@ pub const VirtualScrollState = struct {
pub const VirtualScrollConfig = struct {
/// Show scrollbar
show_scrollbar: bool = true,
/// Scrollbar width (Z-Design V2: 14px para mejor visibilidad)
scrollbar_width: u16 = 14,
/// Scrollbar width
scrollbar_width: u16 = 12,
/// Overscan (render extra items above/below viewport)
overscan: u16 = 2,
/// Enable smooth scrolling