use std::rc::Rc;
use std::time::Instant;
use slate_renderer::Lpx;
use slate_renderer::scene::RectInstance;
use slate_text::MultilineLayout;
use crate::context::PaintCtx;
use crate::text_system::PlatformFont;
use crate::types::{Bounds, ElementId};
use super::TextAreaStyle;
pub(super) fn paint(
style: &TextAreaStyle,
font: &PlatformFont,
layout: &Rc<MultilineLayout>,
element_id: ElementId,
focused: bool,
bounds: Bounds,
cx: &mut PaintCtx,
) {
let (committed_text, caret_byte, caret_affinity, selection_anchor, preedit) =
match cx.ime_registry.borrow().get(element_id) {
Some(rc) => {
let s = rc.borrow();
(
s.text.clone(),
s.caret,
s.caret_affinity,
s.selection_anchor,
s.preedit.clone(),
)
}
None => (
String::new(),
layout.lines.last().map(|l| l.byte_end).unwrap_or(0),
slate_text::Affinity::Downstream,
None,
None,
),
};
let mut display_layout = layout.clone();
let mut display_caret = caret_byte;
let mut preedit_span: Option<(usize, Option<std::ops::Range<usize>>)> = None;
if let Some(ref p) = preedit {
let display = super::layout::compose_display(&committed_text, caret_byte, &p.text);
match cx.text.shape_document(font, &display) {
Ok(doc) => {
display_layout = Rc::new(slate_text::wrap_document(&doc, style.width));
let at = floor_char_boundary(&committed_text, caret_byte);
display_caret = crate::ime::caret_display_byte(at, Some(p));
preedit_span = Some((at, p.selection.clone()));
}
Err(e) => {
log::error!("TextArea: preedit shape_document failed: {e}");
}
}
}
let layout = &display_layout;
let has_preedit = preedit_span.is_some();
if let Some(bg) = style.background {
cx.scene.push_rect(RectInstance {
rect: [
Lpx(bounds.origin.x),
Lpx(bounds.origin.y),
Lpx(style.width),
Lpx(layout.total_height_lpx),
],
color: bg,
corner_radius: Lpx(0.0),
_pad: [0.0; 3],
});
}
if !has_preedit && let Some(anchor) = selection_anchor {
let (lo, hi) = if anchor <= caret_byte {
(anchor, caret_byte)
} else {
(caret_byte, anchor)
};
for r in super::layout::selection_rects(layout, lo, hi, style.width) {
cx.scene.push_rect(RectInstance {
rect: [
Lpx(bounds.origin.x + r.x_lpx),
Lpx(bounds.origin.y + r.y_lpx),
Lpx(r.width_lpx),
Lpx(layout.line_height_lpx),
],
color: style.selection_color,
corner_radius: Lpx(0.0),
_pad: [0.0; 3],
});
}
}
for vline in &layout.lines {
if vline.line.glyphs.is_empty() {
continue;
}
let baseline = [
bounds.origin.x,
bounds.origin.y + vline.line.y_offset_lpx + vline.line.ascent_lpx,
];
match cx.text.rasterize_text_run(
font,
&vline.line,
baseline,
style.color,
cx.glyph_cache,
cx.glyph_atlas,
cx.queue,
) {
Ok(glyphs) => {
for glyph in glyphs {
cx.scene.push_glyph(glyph);
}
}
Err(e) => {
log::error!("TextArea: rasterize_text_run failed: {e}");
}
}
}
if let (Some((preedit_start, sel)), Some(p)) = (preedit_span.as_ref(), preedit.as_ref()) {
let preedit_end = preedit_start + p.text.len();
for run in super::layout::preedit_runs(layout, *preedit_start, preedit_end) {
let vline = &layout.lines[run.line_idx];
let underline_y = vline.line.y_offset_lpx + vline.line.ascent_lpx + 1.0;
cx.scene.push_rect(RectInstance {
rect: [
Lpx(bounds.origin.x + run.x_lpx),
Lpx(bounds.origin.y + underline_y),
Lpx(run.width_lpx),
Lpx(1.0),
],
color: style.preedit_underline_color,
corner_radius: Lpx(0.0),
_pad: [0.0; 3],
});
}
if let Some(sel) = sel {
let sel_start = preedit_start + sel.start.min(p.text.len());
let sel_end = preedit_start + sel.end.min(p.text.len());
for run in super::layout::preedit_runs(layout, sel_start, sel_end) {
let vline = &layout.lines[run.line_idx];
cx.scene.push_rect(RectInstance {
rect: [
Lpx(bounds.origin.x + run.x_lpx),
Lpx(bounds.origin.y + vline.line.y_offset_lpx),
Lpx(run.width_lpx),
Lpx(layout.line_height_lpx),
],
color: style.preedit_selection_color,
corner_radius: Lpx(0.0),
_pad: [0.0; 3],
});
}
}
}
let effective_affinity = crate::ime::caret_affinity_for_display(has_preedit, caret_affinity);
let (_, caret_x, caret_y) =
layout.caret_position_with_affinity(display_caret, effective_affinity);
let mut caret_visible = false;
if let Some(state_rc) = cx.ime_registry.borrow().get(element_id)
&& let Ok(mut state) = state_rc.try_borrow_mut()
{
let (visible, next_deadline) = crate::elements::text_edit::blink::advance_blink(
&mut state.blink,
focused,
Instant::now(),
);
caret_visible = visible;
if let Some(deadline) = next_deadline {
cx.schedule_redraw_at(deadline);
}
}
if focused && caret_visible {
cx.scene.push_rect(RectInstance {
rect: [
Lpx(bounds.origin.x + caret_x),
Lpx(bounds.origin.y + caret_y),
Lpx(1.0),
Lpx(layout.line_height_lpx),
],
color: style.caret_color,
corner_radius: Lpx(0.0),
_pad: [0.0; 3],
});
}
if let Some(state_rc) = cx.ime_registry.borrow().get(element_id)
&& let Ok(mut state) = state_rc.try_borrow_mut()
{
let scale = cx.scale_factor as f32;
state.caret_client_rect = Some(slate_platform::PhysicalRect::from_lpx_rect(
bounds.origin.x + caret_x,
bounds.origin.y + caret_y,
1.0,
layout.line_height_lpx,
scale,
));
state.last_layout = Some(layout.clone());
state.paint_origin_x = bounds.origin.x;
state.paint_origin_y = bounds.origin.y;
}
}
fn floor_char_boundary(s: &str, byte: usize) -> usize {
let mut at = byte.min(s.len());
while at > 0 && !s.is_char_boundary(at) {
at -= 1;
}
at
}