First commit. Version 0.1.0

This commit is contained in:
2026-05-10 09:58:23 +02:00
parent af105b7f7d
commit bbab5e238d
635 changed files with 53627 additions and 175 deletions

227
tests/core_surface.rs Normal file
View File

@@ -0,0 +1,227 @@
use ltk::core::{ Canvas, RenderOptions, UiSurface };
use ltk::{ button, column, window_controls, Color, Element, Point, WindowButtonKind };
#[ derive( Clone, Debug, PartialEq, Eq ) ]
enum Msg
{
Pressed,
Close,
Maximize,
Minimize,
}
fn view() -> Element<Msg>
{
column::<Msg>()
.padding( 16.0 )
.push( button( "Press" ).on_press( Msg::Pressed ) )
.into()
}
fn decoration_view() -> Element<Msg>
{
window_controls(
Some( Msg::Minimize ),
WindowButtonKind::Maximize,
Some( Msg::Maximize ),
Some( Msg::Close ),
)
.into()
}
#[ test ]
fn core_surface_new_is_software_by_default()
{
let surface = UiSurface::<Msg>::new( 240, 120 );
assert!( matches!( surface.canvas(), Canvas::Software( _ ) ) );
assert!( surface.canvas().borrowed_gles_texture().is_none() );
}
#[ test ]
fn core_surface_renders_and_exposes_handlers_without_run()
{
let mut surface = UiSurface::<Msg>::new( 240, 120 );
let out = surface.render(
&view(),
RenderOptions::full_canvas( 240, 120 ).background( Color::rgb( 0.1, 0.1, 0.1 ) ),
);
assert!( out.full_redraw );
assert_eq!( surface.widget_rects().len(), 1 );
let first = &surface.widget_rects()[ 0 ];
let hit = surface.hit_test( Point { x: first.rect.x + 1.0, y: first.rect.y + 1.0 } );
assert_eq!( hit, Some( first.flat_idx ) );
let msg = surface.handlers( first.flat_idx ).and_then( |h| h.press_msg() );
assert_eq!( msg, Some( Msg::Pressed ) );
}
#[ test ]
fn core_surface_can_drive_window_controls()
{
let mut surface = UiSurface::<Msg>::new( 180, 48 );
let _ = surface.render(
&decoration_view(),
RenderOptions::full_canvas( 180, 48 ).background( Color::rgb( 0.08, 0.08, 0.09 ) ),
);
assert_eq!( surface.widget_rects().len(), 3 );
let close = surface.widget_rects()[ 2 ].flat_idx;
let msg = surface.handlers( close ).and_then( |h| h.press_msg() );
assert_eq!( msg, Some( Msg::Close ) );
}
#[ test ]
fn core_surface_reports_partial_damage_for_focus_change()
{
let mut surface = UiSurface::<Msg>::new( 240, 120 );
let opts = RenderOptions::full_canvas( 240, 120 ).background( Color::rgb( 0.1, 0.1, 0.1 ) );
let _ = surface.render( &view(), opts );
let idx = surface.widget_rects()[ 0 ].flat_idx;
surface.set_focused( Some( idx ) );
let out = surface.render( &view(), opts );
assert!( !out.full_redraw, "focus-only transition should be partial damage" );
assert!( !out.damage_rects.is_empty() );
}
// ── interaction-only damage paths ─────────────────────────────────────────────
#[ test ]
fn hover_only_change_produces_partial_damage()
{
let mut surface = UiSurface::<Msg>::new( 240, 120 );
let opts = RenderOptions::full_canvas( 240, 120 ).background( Color::rgb( 0.1, 0.1, 0.1 ) );
let _ = surface.render( &view(), opts );
let idx = surface.widget_rects()[ 0 ].flat_idx;
surface.set_hovered( Some( idx ) );
let out = surface.render( &view(), opts );
assert!( !out.full_redraw, "hover-only transition should be partial damage" );
assert!( !out.damage_rects.is_empty() );
}
#[ test ]
fn pressed_only_change_produces_partial_damage()
{
let mut surface = UiSurface::<Msg>::new( 240, 120 );
let opts = RenderOptions::full_canvas( 240, 120 ).background( Color::rgb( 0.1, 0.1, 0.1 ) );
let _ = surface.render( &view(), opts );
let idx = surface.widget_rects()[ 0 ].flat_idx;
surface.set_pressed( Some( idx ) );
let out = surface.render( &view(), opts );
assert!( !out.full_redraw );
}
#[ test ]
fn focused_hovered_pressed_setters_round_trip()
{
let mut surface = UiSurface::<Msg>::new( 240, 120 );
let opts = RenderOptions::full_canvas( 240, 120 );
let _ = surface.render( &view(), opts );
let idx = surface.widget_rects()[ 0 ].flat_idx;
surface.set_focused( Some( idx ) );
surface.set_hovered( Some( idx ) );
surface.set_pressed( Some( idx ) );
assert_eq!( surface.focused(), Some( idx ) );
assert_eq!( surface.hovered(), Some( idx ) );
assert_eq!( surface.pressed(), Some( idx ) );
surface.set_focused( None );
surface.set_hovered( None );
surface.set_pressed( None );
assert_eq!( surface.focused(), None );
assert_eq!( surface.hovered(), None );
assert_eq!( surface.pressed(), None );
}
// ── full-redraw paths ─────────────────────────────────────────────────────────
#[ test ]
fn first_render_is_a_full_redraw()
{
let mut surface = UiSurface::<Msg>::new( 240, 120 );
let out = surface.render(
&view(),
RenderOptions::full_canvas( 240, 120 ).background( Color::rgb( 0.1, 0.1, 0.1 ) ),
);
assert!( out.full_redraw, "the first render of a fresh surface is always a full redraw" );
}
#[ test ]
fn mark_content_dirty_forces_next_render_to_full_redraw()
{
let mut surface = UiSurface::<Msg>::new( 240, 120 );
let opts = RenderOptions::full_canvas( 240, 120 ).background( Color::rgb( 0.1, 0.1, 0.1 ) );
let _ = surface.render( &view(), opts );
// No interaction change — the next render would normally be partial.
surface.mark_content_dirty();
let out = surface.render( &view(), opts );
assert!( out.full_redraw );
}
#[ test ]
fn resize_forces_next_render_to_full_redraw()
{
let mut surface = UiSurface::<Msg>::new( 240, 120 );
let _ = surface.render( &view(), RenderOptions::full_canvas( 240, 120 ) );
surface.resize( 320, 200 );
assert_eq!( surface.size(), ( 320, 200 ) );
let out = surface.render( &view(), RenderOptions::full_canvas( 320, 200 ) );
assert!( out.full_redraw );
}
#[ test ]
fn tree_size_change_forces_full_redraw()
{
let mut surface = UiSurface::<Msg>::new( 320, 200 );
let opts = RenderOptions::full_canvas( 320, 200 );
let _ = surface.render( &view(), opts );
let initial_count = surface.widget_rects().len();
// Render a different tree (decoration_view has 3 buttons; view has 1).
let out = surface.render( &decoration_view(), opts );
assert!( out.full_redraw );
assert_ne!( surface.widget_rects().len(), initial_count );
}
// ── lookups ───────────────────────────────────────────────────────────────────
#[ test ]
fn hit_test_outside_any_widget_returns_none()
{
let mut surface = UiSurface::<Msg>::new( 320, 200 );
let _ = surface.render( &view(), RenderOptions::full_canvas( 320, 200 ) );
// (0, 0) is the top-left corner — far from the centred button.
assert_eq!( surface.hit_test( Point { x: 0.0, y: 0.0 } ), None );
}
#[ test ]
fn handlers_lookup_for_unknown_idx_returns_none()
{
let mut surface = UiSurface::<Msg>::new( 240, 120 );
let _ = surface.render( &view(), RenderOptions::full_canvas( 240, 120 ) );
assert!( surface.handlers( 999_999 ).is_none() );
}
#[ test ]
fn widget_lookup_for_known_idx_returns_some()
{
let mut surface = UiSurface::<Msg>::new( 240, 120 );
let _ = surface.render( &view(), RenderOptions::full_canvas( 240, 120 ) );
let idx = surface.widget_rects()[ 0 ].flat_idx;
assert!( surface.widget( idx ).is_some() );
}