feat(animation): Liquid UI V2 - Mayor fluidez y contraste
- ColorTransition: 200ms → 500ms (transiciones más perceptibles) - deriveDarkPalette: 4%/20% base color (mayor contraste focus/unfocus) - deriveLightPalette: 1%/6% base color (proporcional) - Context: añadir requestAnimationFrame/needsAnimationFrame - Tests actualizados para nuevos umbrales 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
0e913cda55
commit
ed2701fbd8
3 changed files with 47 additions and 26 deletions
|
|
@ -102,6 +102,10 @@ 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,
|
||||
|
|
@ -204,6 +208,9 @@ 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;
|
||||
}
|
||||
|
||||
|
|
@ -363,6 +370,19 @@ 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)
|
||||
|
|
|
|||
|
|
@ -1031,9 +1031,9 @@ pub fn derivePanelPalette(base: Color, mode: ThemeMode) PanelColorScheme {
|
|||
}
|
||||
|
||||
/// Derive palette for dark mode (dark backgrounds, light text)
|
||||
/// 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)
|
||||
/// 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)
|
||||
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: 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),
|
||||
// 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),
|
||||
|
||||
// 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: Sincronizado con dark mode
|
||||
/// - fondo_sin_focus: 1% base (casi blanco, sutil identidad)
|
||||
/// - fondo_con_focus: 3% base (brilla al ganar foco)
|
||||
/// 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)
|
||||
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: 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)
|
||||
// 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)
|
||||
.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 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);
|
||||
// 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);
|
||||
|
||||
// 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,10 +1167,11 @@ 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 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);
|
||||
// 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);
|
||||
}
|
||||
|
||||
test "contrastTextColor" {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
duration_ms: f32 = 200.0,
|
||||
/// Transition duration in milliseconds (500ms = Liquid UI feel)
|
||||
duration_ms: f32 = 500.0,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue