use std::cell::RefCell;
use std::rc::Rc;
use std::sync::Arc;
use super::layout::{layout_doc, DocLayout, FontResolver};
use super::model::{InlineStyle, RichDoc};
use crate::draw_ctx::DrawCtx;
use crate::event::{Event, EventResult};
use crate::geometry::{Point, Rect, Size};
use crate::layout_props::{HAnchor, Insets, VAnchor, WidgetBase};
use crate::text::Font;
use crate::widget::Widget;
pub type SharedResolver = Rc<dyn Fn(&InlineStyle) -> Arc<Font>>;
pub struct RichTextView {
bounds: Rect,
children: Vec<Box<dyn Widget>>, base: WidgetBase,
doc: Rc<RefCell<RichDoc>>,
resolver: SharedResolver,
default_font_size: f64,
layout: Option<DocLayout>,
layout_width: f64,
}
impl RichTextView {
pub fn new(doc: Rc<RefCell<RichDoc>>, resolver: SharedResolver) -> Self {
Self {
bounds: Rect::default(),
children: Vec::new(),
base: WidgetBase::new(),
doc,
resolver,
default_font_size: 16.0,
layout: None,
layout_width: -1.0,
}
}
pub fn with_default_font_size(mut self, size: f64) -> Self {
self.default_font_size = size;
self
}
pub fn with_margin(mut self, m: Insets) -> Self {
self.base.margin = m;
self
}
pub fn invalidate(&mut self) {
self.layout = None;
self.layout_width = -1.0;
}
pub fn content_height(&self) -> f64 {
self.layout.as_ref().map(|l| l.height).unwrap_or(0.0)
}
pub fn content_width(&self) -> f64 {
self.layout.as_ref().map(|l| l.width).unwrap_or(0.0)
}
fn ensure_layout(&mut self, width: f64) {
if self.layout.is_some() && (self.layout_width - width).abs() < 0.5 {
return;
}
let resolver: &FontResolver = &*self.resolver;
let doc = self.doc.borrow();
self.layout = Some(layout_doc(
&doc,
width,
self.default_font_size,
resolver,
));
self.layout_width = width;
}
}
impl Widget for RichTextView {
fn type_name(&self) -> &'static str {
"RichTextView"
}
fn bounds(&self) -> Rect {
self.bounds
}
fn set_bounds(&mut self, b: Rect) {
self.bounds = b;
}
fn children(&self) -> &[Box<dyn Widget>] {
&self.children
}
fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> {
&mut self.children
}
fn hit_test(&self, _: Point) -> bool {
false
}
fn layout(&mut self, available: Size) -> Size {
let width = available.width.max(1.0);
self.ensure_layout(width);
Size::new(self.content_width().max(width), self.content_height())
}
fn measure_min_height(&self, available_w: f64) -> f64 {
let resolver: &FontResolver = &*self.resolver;
let doc = self.doc.borrow();
layout_doc(&doc, available_w.max(1.0), self.default_font_size, resolver).height
}
fn paint(&mut self, ctx: &mut dyn DrawCtx) {
self.ensure_layout(self.bounds.width.max(1.0));
let Some(layout) = &self.layout else {
return;
};
let h = self.bounds.height;
let default_color = ctx.visuals().text_color;
let mut y_top = 0.0f64;
for block in &layout.blocks {
if let (Some(marker), Some(font), Some(first)) =
(&block.marker, &block.marker_font, block.lines.first())
{
let baseline_up = h - (y_top + first.baseline_from_top);
let color = block
.lines
.first()
.and_then(|l| l.fragments.first())
.and_then(|f| f.style.text_color)
.unwrap_or(default_color);
ctx.set_font(Arc::clone(font));
ctx.set_font_size(block.marker_font_size);
ctx.set_fill_color(color);
ctx.fill_text(marker, block.marker_x, baseline_up);
}
for line in &block.lines {
let line_top = y_top;
let baseline_up = h - (line_top + line.baseline_from_top);
let line_bottom_up = h - (line_top + line.height);
let text_x0 = block.text_left + line.align_dx;
for frag in &line.fragments {
let fx = text_x0 + frag.x;
if let Some(hl) = frag.style.highlight {
ctx.set_fill_color(hl);
ctx.begin_path();
ctx.rect(fx, line_bottom_up, frag.width, line.height);
ctx.fill();
}
let color = frag.style.text_color.unwrap_or(default_color);
ctx.set_font(Arc::clone(&frag.font));
ctx.set_font_size(frag.font_size);
ctx.set_fill_color(color);
ctx.fill_text(&frag.text, fx, baseline_up);
if frag.style.underline || frag.style.strikethrough {
ctx.set_stroke_color(color);
ctx.set_line_width(1.0_f64.max(frag.font_size * 0.06));
}
if frag.style.underline {
let uy = baseline_up - (frag.font_size * 0.12).max(1.5);
stroke_hline(ctx, fx, fx + frag.width, uy);
}
if frag.style.strikethrough {
let sy = baseline_up + frag.font_size * 0.28;
stroke_hline(ctx, fx, fx + frag.width, sy);
}
}
y_top += line.height;
}
}
}
fn margin(&self) -> Insets {
self.base.margin
}
fn widget_base(&self) -> Option<&WidgetBase> {
Some(&self.base)
}
fn widget_base_mut(&mut self) -> Option<&mut WidgetBase> {
Some(&mut self.base)
}
fn h_anchor(&self) -> HAnchor {
self.base.h_anchor
}
fn v_anchor(&self) -> VAnchor {
self.base.v_anchor
}
fn on_event(&mut self, _: &Event) -> EventResult {
EventResult::Ignored
}
}
fn stroke_hline(ctx: &mut dyn DrawCtx, x0: f64, x1: f64, y: f64) {
ctx.begin_path();
ctx.move_to(x0, y);
ctx.line_to(x1, y);
ctx.stroke();
}
pub fn single_font_resolver(font: Arc<Font>) -> SharedResolver {
Rc::new(move |_: &InlineStyle| Arc::clone(&font))
}
pub fn uniform_resolver(font: Arc<Font>) -> SharedResolver {
single_font_resolver(font)
}