use crate::render::Canvas;
use crate::types::Rect;
use super::TextEdit;
use super::hit_test::{ single_line_align_offset, single_line_scroll_x };
use super::theme;
use super::wrapping::compute_visual_lines;
fn rect_intersect( a: Rect, b: Rect ) -> Option<Rect>
{
let x0 = a.x.max( b.x );
let y0 = a.y.max( b.y );
let x1 = ( a.x + a.width ).min( b.x + b.width );
let y1 = ( a.y + a.height ).min( b.y + b.height );
if x1 > x0 && y1 > y0
{
Some( Rect { x: x0, y: y0, width: x1 - x0, height: y1 - y0 } )
} else {
None
}
}
pub fn password_toggle_hit_zone( rect: Rect ) -> Rect
{
let zone_w = ( theme::PASSWORD_TOGGLE_SIZE
+ theme::PASSWORD_TOGGLE_SLOP * 2.0
+ theme::PAD_H * 0.5 ).min( rect.width );
Rect
{
x: rect.x + rect.width - zone_w,
y: rect.y,
width: zone_w,
height: rect.height,
}
}
impl<Msg: Clone> TextEdit<Msg>
{
pub fn draw(
&self,
canvas: &mut Canvas,
rect: Rect,
focused: bool,
cursor_pos: usize,
selection_anchor: usize,
)
{
if self.is_multiline()
{
self.draw_multiline( canvas, rect, focused, cursor_pos, selection_anchor );
return;
}
if !self.borderless
{
let border_c = if focused { theme::focus_border() } else { theme::border() };
let border_w = if focused { theme::FOCUS_BORDER_W } else { theme::BORDER_W };
canvas.fill_rect( rect, theme::bg(), theme::RADIUS );
canvas.stroke_rect( rect, border_c, border_w, theme::RADIUS );
}
let font_size = self.font_size;
let text_y = rect.y + (rect.height + font_size) / 2.0 - 2.0;
let text = self.display_text();
let secure = self.effective_secure();
let toggle_reserve = if self.password_toggle.is_some()
{
theme::PASSWORD_TOGGLE_SIZE + theme::PASSWORD_TOGGLE_SLOP * 2.0
}
else
{
0.0
};
let text_rect = Rect
{
width: ( rect.width - toggle_reserve ).max( theme::PAD_H * 2.0 ),
..rect
};
let scroll_x = single_line_scroll_x( canvas, text_rect, &self.value, cursor_pos, secure, font_size );
let align_x = single_line_align_offset( canvas, text_rect, &self.value, secure, self.align, font_size );
let outer_clip = canvas.clip_bounds();
let inner_rect = Rect
{
x: rect.x + theme::PAD_H * 0.5,
y: rect.y,
width: ( rect.width - theme::PAD_H - toggle_reserve ).max( 0.0 ),
height: rect.height,
};
let composed_clip: Vec<Rect> = if outer_clip.is_empty()
{
vec![ inner_rect ]
} else {
outer_clip.iter()
.filter_map( |o| rect_intersect( *o, inner_rect ) )
.collect()
};
canvas.set_clip_rects( &composed_clip );
if focused && selection_anchor != cursor_pos
{
let ( s, e ) = (
cursor_pos.min( selection_anchor ).min( self.value.len() ),
cursor_pos.max( selection_anchor ).min( self.value.len() ),
);
let prefix_text = if secure
{
"\u{2022}".repeat( self.value[..s].chars().count() )
} else {
self.value[..s].to_string()
};
let span_text = if secure
{
"\u{2022}".repeat( self.value[s..e].chars().count() )
} else {
self.value[s..e].to_string()
};
let x0 = rect.x + theme::PAD_H + align_x - scroll_x
+ canvas.measure_text( &prefix_text, font_size );
let w = canvas.measure_text( &span_text, font_size );
let sel_rect = Rect
{
x: x0, y: rect.y + 6.0,
width: w, height: rect.height - 12.0,
};
canvas.fill_rect( sel_rect, theme::selection(), 2.0 );
}
if text.is_empty()
{
let placeholder_align_x = single_line_align_offset(
canvas, rect, &self.placeholder, false, self.align, font_size,
);
canvas.draw_text(
&self.placeholder,
rect.x + theme::PAD_H + placeholder_align_x,
text_y,
font_size,
theme::placeholder(),
);
} else {
canvas.draw_text(
&text,
rect.x + theme::PAD_H + align_x - scroll_x,
text_y,
font_size,
theme::text(),
);
}
if focused
{
let safe_cursor = cursor_pos.min( self.value.len() );
let cursor_text = if secure
{
"\u{2022}".repeat( self.value[..safe_cursor].chars().count() )
} else {
self.value[..safe_cursor].to_string()
};
let cursor_x = rect.x + theme::PAD_H + align_x - scroll_x
+ canvas.measure_text( &cursor_text, font_size );
let cursor_rect = Rect
{
x: cursor_x,
y: rect.y + 8.0,
width: 2.0,
height: rect.height - 16.0,
};
canvas.fill_rect( cursor_rect, theme::cursor(), 0.0 );
}
if outer_clip.is_empty()
{
canvas.clear_clip();
} else {
canvas.set_clip_rects( &outer_clip );
}
if let Some( ( visible, _ ) ) = self.password_toggle.as_ref()
{
let icon_name = if *visible { "actions/invisible" } else { "actions/visible" };
let icon_px = theme::PASSWORD_TOGGLE_SIZE.round() as u32;
if let Some( ( rgba, iw, ih ) ) = crate::theme::icon_rgba( icon_name, icon_px )
{
let tinted = crate::theme::tint_symbolic( &rgba, theme::placeholder() );
let zone = password_toggle_hit_zone( rect );
let dest = Rect
{
x: ( zone.x + ( zone.width - iw as f32 ) / 2.0 ).round(),
y: ( rect.y + ( rect.height - ih as f32 ) / 2.0 ).round(),
width: iw as f32,
height: ih as f32,
};
canvas.draw_image_data( &tinted, iw, ih, dest, 1.0 );
}
}
}
fn draw_multiline(
&self,
canvas: &mut Canvas,
rect: Rect,
focused: bool,
cursor_pos: usize,
selection_anchor: usize,
)
{
let border_c = if focused { theme::focus_border() } else { theme::border() };
let border_w = if focused { theme::FOCUS_BORDER_W } else { theme::BORDER_W };
canvas.fill_rect( rect, theme::bg(), theme::RADIUS_MULTI );
canvas.stroke_rect( rect, border_c, border_w, theme::RADIUS_MULTI );
let line_h = theme::FONT_SIZE * theme::LINE_H_MULT;
let inner_h = ( rect.height - theme::PAD_V_MULTI * 2.0 ).max( line_h );
let visible_lines = ( inner_h / line_h ).floor().max( 1.0 ) as usize;
let inner_width = ( rect.width - theme::PAD_H * 2.0 ).max( theme::FONT_SIZE );
let visual_lines = compute_visual_lines( canvas, &self.value, inner_width, theme::FONT_SIZE );
let safe_cursor = cursor_pos.min( self.value.len() );
let cursor_visual_idx = visual_lines.iter()
.position( |vl| safe_cursor >= vl.start && safe_cursor <= vl.end )
.unwrap_or( visual_lines.len().saturating_sub( 1 ) );
let total_lines = visual_lines.len();
let max_first = total_lines.saturating_sub( visible_lines );
let first_line = if cursor_visual_idx + 1 > visible_lines
{
( cursor_visual_idx + 1 - visible_lines ).min( max_first )
} else { 0 };
let baseline_0 = rect.y + theme::PAD_V_MULTI + theme::FONT_SIZE;
let outer_clip = canvas.clip_bounds();
let inner_rect = Rect
{
x: rect.x + theme::PAD_H * 0.5,
y: rect.y + theme::PAD_V_MULTI * 0.5,
width: ( rect.width - theme::PAD_H ).max( 0.0 ),
height: ( rect.height - theme::PAD_V_MULTI ).max( 0.0 ),
};
let composed_clip: Vec<Rect> = if outer_clip.is_empty()
{
vec![ inner_rect ]
} else {
outer_clip.iter()
.filter_map( |o| rect_intersect( *o, inner_rect ) )
.collect()
};
canvas.set_clip_rects( &composed_clip );
if focused && selection_anchor != cursor_pos
{
let s = cursor_pos.min( selection_anchor ).min( self.value.len() );
let e = cursor_pos.max( selection_anchor ).min( self.value.len() );
for ( i, vl ) in visual_lines.iter().enumerate().skip( first_line )
{
let visual_idx = i - first_line;
let y = rect.y + theme::PAD_V_MULTI + visual_idx as f32 * line_h;
if y > rect.y + rect.height - theme::PAD_V_MULTI { break; }
let span_start = s.max( vl.start );
let span_end = e.min( vl.end );
if span_start >= span_end { continue; }
let prefix_text = &self.value[ vl.start..span_start ];
let span_text = &self.value[ span_start..span_end ];
let x0 = rect.x + theme::PAD_H + canvas.measure_text( prefix_text, theme::FONT_SIZE );
let w = canvas.measure_text( span_text, theme::FONT_SIZE ).max( 4.0 );
let sel_rect = Rect
{
x: x0, y,
width: w, height: line_h,
};
canvas.fill_rect( sel_rect, theme::selection(), 2.0 );
}
}
if self.value.is_empty()
{
canvas.draw_text(
&self.placeholder,
rect.x + theme::PAD_H,
baseline_0,
theme::FONT_SIZE,
theme::placeholder(),
);
} else {
for ( i, vl ) in visual_lines.iter().enumerate().skip( first_line )
{
let visual_idx = i - first_line;
let y = baseline_0 + visual_idx as f32 * line_h;
if y > rect.y + rect.height - theme::PAD_V_MULTI + line_h
{
break;
}
let line = &self.value[ vl.start..vl.end ];
canvas.draw_text(
line,
rect.x + theme::PAD_H,
y,
theme::FONT_SIZE,
theme::text(),
);
}
}
if focused
{
let vl = visual_lines[ cursor_visual_idx ];
let line_prefix = &self.value[ vl.start..safe_cursor ];
let cursor_x = rect.x + theme::PAD_H
+ canvas.measure_text( line_prefix, theme::FONT_SIZE );
let visual_y_idx = cursor_visual_idx.saturating_sub( first_line );
let cursor_y = rect.y + theme::PAD_V_MULTI + visual_y_idx as f32 * line_h;
let cursor_rect = Rect
{
x: cursor_x,
y: cursor_y + 2.0,
width: 2.0,
height: theme::FONT_SIZE + 4.0,
};
canvas.fill_rect( cursor_rect, theme::cursor(), 0.0 );
}
if outer_clip.is_empty()
{
canvas.clear_clip();
} else {
canvas.set_clip_rects( &outer_clip );
}
}
}