use crate::render::Canvas;
use crate::types::Rect;
use crate::widget::{ Element, LaidOutWidget, WidgetHandlers };
use super::DrawCtx;
use super::damage::clamp_rect_to;
pub( crate ) fn layout_and_draw<Msg: Clone>(
element: &Element<Msg>,
canvas: &mut Canvas,
rect: Rect,
ctx: &mut DrawCtx<Msg>,
flat_idx: usize,
) -> usize
{
match element
{
Element::Column( col ) =>
{
let child_rects = col.layout( rect, canvas );
let mut idx = flat_idx;
for ( child_rect, child_i ) in child_rects
{
idx = layout_and_draw::<Msg>( &col.children[child_i], canvas, child_rect, ctx, idx );
}
idx
}
Element::Row( r ) =>
{
let child_rects = r.layout( rect, canvas );
let mut idx = flat_idx;
for ( child_rect, child_i ) in child_rects
{
idx = layout_and_draw::<Msg>( &r.children[child_i], canvas, child_rect, ctx, idx );
}
idx
}
Element::Stack( s ) =>
{
let child_rects = s.layout( rect, canvas );
let mut idx = flat_idx;
for ( child_rect, child_i ) in child_rects
{
idx = layout_and_draw::<Msg>( &s.children[ child_i ].0, canvas, child_rect, ctx, idx );
}
idx
}
Element::WrapGrid( g ) =>
{
let child_rects = g.layout( rect, canvas );
let mut idx = flat_idx;
for ( child_rect, child_i ) in child_rects
{
idx = layout_and_draw::<Msg>( &g.children[child_i], canvas, child_rect, ctx, idx );
}
idx
}
Element::Carousel( car ) =>
{
let child_rects = car.layout( rect, canvas );
let mut idx = flat_idx;
for ( child_rect, child_i ) in child_rects
{
idx = layout_and_draw::<Msg>( &car.children[child_i], canvas, child_rect, ctx, idx );
}
idx
}
Element::Flex( f ) =>
{
layout_and_draw::<Msg>( f.child.as_ref(), canvas, rect, ctx, flat_idx )
}
Element::AnchoredOverlay( a ) =>
{
let anchor_rect = ctx.previous_widget_rects.iter()
.find( |w| w.id == Some( a.anchor_id ) )
.map( |w| w.rect );
let target = match anchor_rect
{
Some( anchor ) =>
{
let ( w, h ) = a.child.preferred_size( rect.width, canvas );
Rect
{
x: anchor.x,
y: anchor.y + anchor.height + a.gap,
width: w,
height: h,
}
}
None => rect,
};
layout_and_draw::<Msg>( a.child.as_ref(), canvas, target, ctx, flat_idx )
}
Element::Pressable( p ) =>
{
let my_idx = flat_idx;
if p.has_handler()
{
ctx.widget_rects.push( LaidOutWidget
{
rect,
flat_idx: my_idx,
id: p.id,
paint_rect: rect,
handlers: WidgetHandlers::Button
{
on_press: p.on_press.clone(),
on_long_press: p.on_long_press.clone(),
on_drag_start: p.on_drag_start.clone(),
on_escape: p.on_escape.clone(),
repeating: false,
},
keyboard_focusable: false,
cursor: p.cursor.unwrap_or( crate::types::CursorShape::Pointer ),
tooltip: None,
accessible_label: None,
is_live_region: ctx.live_depth > 0,
} );
}
layout_and_draw::<Msg>( p.child.as_ref(), canvas, rect, ctx, my_idx + 1 )
}
Element::Container( c ) =>
{
let saved_alpha = canvas.global_alpha();
canvas.set_global_alpha( saved_alpha * c.opacity );
let live_inc = if c.a11y_live { 1 } else { 0 };
ctx.live_depth += live_inc;
let rect = if let Some( mw ) = c.max_width
{
crate::types::Rect { width: rect.width.min( mw ), ..rect }
} else {
rect
};
let painted = match c.surface.as_deref()
{
Some( slot ) => match crate::theme::resolve_surface( slot )
{
Some( ( surf, outer ) ) =>
{
canvas.fill_surface
(
rect,
&surf.fill,
&outer,
&surf.inset_shadows,
c.corners,
);
true
}
None => false,
},
None => false,
};
if !painted
{
if let Some( ref bg ) = c.background
{
canvas.fill_paint_rect( rect, bg, c.corners );
}
}
if let Some( ( color, width ) ) = c.border
{
canvas.stroke_rect( rect, color, width, c.corners );
}
let vp = canvas.viewport_layout();
let em = crate::types::Length::EM_BASE_DEFAULT;
let pad_l = c.pad_left.resolve( vp, em );
let pad_r = c.pad_right.resolve( vp, em );
let pad_t = c.pad_top.resolve( vp, em );
let pad_b = c.pad_bottom.resolve( vp, em );
let inner = crate::types::Rect
{
x: rect.x + pad_l,
y: rect.y + pad_t,
width: ( rect.width - pad_l - pad_r ).max( 0.0 ),
height: ( rect.height - pad_t - pad_b ).max( 0.0 ),
};
let result = layout_and_draw::<Msg>( c.child.as_ref(), canvas, inner, ctx, flat_idx );
ctx.live_depth -= live_inc;
canvas.set_global_alpha( saved_alpha );
result
}
Element::Scroll( s ) =>
{
let my_idx = flat_idx;
let axis = s.axis;
let ( ox_raw, oy_raw ) = ctx.scroll_offsets.get( &my_idx ).copied().unwrap_or( ( 0.0, 0.0 ) );
let child_w_max = if axis.allows_x() { f32::INFINITY } else { rect.width };
let ( child_w, child_h ) = s.child.preferred_size( child_w_max, canvas );
let ox = if axis.allows_x() { crate::widget::scroll::clamp_offset( ox_raw, child_w, rect.width ) } else { 0.0 };
let oy = if axis.allows_y() { crate::widget::scroll::clamp_offset( oy_raw, child_h, rect.height ) } else { 0.0 };
if ox != ox_raw || oy != oy_raw
{
ctx.scroll_offsets.insert( my_idx, ( ox, oy ) );
}
let sw = (rect.width.ceil() as u32).max( 1 );
let sh = (rect.height.ceil() as u32).max( 1 );
let mut sub = ctx.scroll_canvases.remove( &my_idx )
.filter( |c| c.size() == ( sw, sh ) )
.unwrap_or_else( || canvas.sub_canvas( sw, sh ) );
sub.clear();
let effective_w = if axis.allows_x() { child_w.max( rect.width ) } else { rect.width };
let effective_h = if axis.allows_y() { child_h.max( rect.height ) } else { rect.height };
let child_rect = Rect { x: -ox, y: -oy, width: effective_w, height: effective_h };
let rects_before = ctx.widget_rects.len();
let scroll_rects_before = ctx.scroll_rects.len();
let next_idx = layout_and_draw::<Msg>( s.child.as_ref(), &mut sub, child_rect, ctx, my_idx + 1 );
let new_rects: Vec<LaidOutWidget<Msg>> = ctx.widget_rects.drain( rects_before.. ).collect();
let navigable: Vec<( usize, f32, f32 )> = new_rects.iter()
.filter( |w| w.handlers.is_navigable_list_item() )
.map( |w| ( w.flat_idx, w.rect.y + oy, w.rect.height ) )
.collect();
if !navigable.is_empty()
{
ctx.scroll_navigable_items.insert( my_idx, navigable );
}
for mut w in new_rects
{
w.rect.x += rect.x;
w.rect.y += rect.y;
w.paint_rect.x += rect.x;
w.paint_rect.y += rect.y;
w.paint_rect = clamp_rect_to( w.paint_rect, rect );
let visible_y = w.rect.y + w.rect.height > rect.y && w.rect.y < rect.y + rect.height;
let visible_x = w.rect.x + w.rect.width > rect.x && w.rect.x < rect.x + rect.width;
if visible_x && visible_y
{
ctx.widget_rects.push( w );
}
}
let new_scroll_rects: Vec<( Rect, usize, crate::widget::scroll::ScrollAxis )> =
ctx.scroll_rects.drain( scroll_rects_before.. ).collect();
for ( mut r, idx, ax ) in new_scroll_rects
{
r.x += rect.x;
r.y += rect.y;
let clamped = clamp_rect_to( r, rect );
if clamped.width > 0.0 && clamped.height > 0.0
{
ctx.scroll_rects.push( ( clamped, idx, ax ) );
}
}
ctx.scroll_rects.push( ( rect, my_idx, axis ) );
canvas.blit( &sub, rect.x as i32, rect.y as i32 );
ctx.scroll_canvases.insert( my_idx, sub );
next_idx
}
Element::Viewport( v ) =>
{
let child_h = v.child.preferred_size( rect.width, canvas ).1;
let effective_h = child_h.max( rect.height );
let vw = ( rect.width.ceil() as u32 ).max( 1 );
let vh = ( rect.height.ceil() as u32 ).max( 1 );
let mut sub = canvas.sub_canvas( vw, vh );
sub.clear();
let child_rect = Rect { x: 0.0, y: 0.0, width: rect.width, height: effective_h };
let rects_before = ctx.widget_rects.len();
let scroll_rects_before = ctx.scroll_rects.len();
let next_idx = layout_and_draw::<Msg>( v.child.as_ref(), &mut sub, child_rect, ctx, flat_idx );
let new_rects: Vec<LaidOutWidget<Msg>> = ctx.widget_rects.drain( rects_before.. ).collect();
for mut w in new_rects
{
w.rect.x += rect.x;
w.rect.y += rect.y;
w.paint_rect.x += rect.x;
w.paint_rect.y += rect.y;
w.paint_rect = clamp_rect_to( w.paint_rect, rect );
if w.rect.y + w.rect.height > rect.y && w.rect.y < rect.y + rect.height
{
ctx.widget_rects.push( w );
}
}
let new_scroll_rects: Vec<( Rect, usize, crate::widget::scroll::ScrollAxis )> =
ctx.scroll_rects.drain( scroll_rects_before.. ).collect();
for ( mut r, idx, ax ) in new_scroll_rects
{
r.x += rect.x;
r.y += rect.y;
let clamped = clamp_rect_to( r, rect );
if clamped.width > 0.0 && clamped.height > 0.0
{
ctx.scroll_rects.push( ( clamped, idx, ax ) );
}
}
canvas.blit_fade_bottom( &sub, rect.x as i32, rect.y as i32, v.fade_bottom );
next_idx
}
other =>
{
let is_focused = ctx.focused_idx == Some( flat_idx ) && other.is_focusable();
let is_hovered = ctx.hovered_idx == Some( flat_idx );
let is_pressed = ctx.pressed_idx == Some( flat_idx );
let cursor_pos = ctx.cursor_state.get( &flat_idx ).copied().unwrap_or( 0 );
let sel_anchor = ctx.selection_anchor.get( &flat_idx ).copied().unwrap_or( cursor_pos );
let widget_id = match other
{
Element::Button( b ) => b.id,
Element::TextEdit( t ) => t.id,
Element::Toggle( t ) => t.id,
Element::Checkbox( c ) => c.id,
Element::Radio( r ) => r.id,
Element::ListItem( l ) => l.id,
Element::WindowButton( b ) => b.id,
_ => None,
};
other.draw( canvas, rect, is_focused, is_hovered, is_pressed, cursor_pos, sel_anchor );
if other.is_interactive()
{
ctx.widget_rects.push( LaidOutWidget
{
rect,
flat_idx,
id: widget_id,
paint_rect: other.paint_bounds( rect ),
handlers: other.handlers(),
keyboard_focusable: other.is_focusable(),
cursor: other.cursor_shape(),
tooltip: other.tooltip().map( str::to_string ),
accessible_label: other.accessible_label(),
is_live_region: ctx.live_depth > 0,
} );
}
else
{
let live = ctx.live_depth > 0;
match other
{
Element::Text( t ) if !t.content.is_empty() =>
{
ctx.accessible_extras.push( crate::a11y::tree::AccessibleExtra
{
rect, label: Some( t.content.clone() ), live,
kind: crate::a11y::tree::AccessibleExtraKind::Label,
} );
}
Element::Image( _ ) =>
{
ctx.accessible_extras.push( crate::a11y::tree::AccessibleExtra
{
rect, label: None, live,
kind: crate::a11y::tree::AccessibleExtraKind::Image,
} );
}
Element::Separator( _ ) =>
{
ctx.accessible_extras.push( crate::a11y::tree::AccessibleExtra
{
rect, label: None, live,
kind: crate::a11y::tree::AccessibleExtraKind::Separator,
} );
}
Element::ProgressBar( p ) =>
{
ctx.accessible_extras.push( crate::a11y::tree::AccessibleExtra
{
rect, label: None, live,
kind: crate::a11y::tree::AccessibleExtraKind::Progress( p.value ),
} );
}
_ => {}
}
}
flat_idx + 1
}
}
}