use std::collections::HashMap; use druid::{BoxConstraints, Data, Env, Event, EventCtx, LayoutCtx, LensExt, LifeCycle, LifeCycleCtx, PaintCtx, Point, Rect, RenderContext, Size, UpdateCtx, Widget, WidgetExt, WidgetPod}; use druid::widget::{CrossAxisAlignment, Flex, MainAxisAlignment}; use crate::app::App; use crate::Notes; use crate::util::markdown_renderer::MarkdownRenderer; #[derive(Default)] pub struct NotesViewer { children: Vec<((usize, usize), WidgetPod>>)>, rects: HashMap<(usize, usize), Rect>, highlighted: Option<(usize, usize)>, } impl NotesViewer { pub fn new() -> Self { Self::default() } pub fn rect_for_step(&self, step: usize, area: usize) -> Option { self.rects.get(&(step, area)).cloned() } fn rerender(&mut self, notes: &Option<(Notes, String)>) { self.children.clear(); let (notes, path) = match notes { Some(notes) => notes, None => return, }; for step_idx in 0..notes.steps.len() { let step = ¬es.steps[step_idx]; for area_idx in 0..step.areas.len() { let area = &step.areas[area_idx]; let mut flex: Flex = Flex::column() .main_axis_alignment(MainAxisAlignment::Start) .cross_axis_alignment(CrossAxisAlignment::Start); let location_name = crate::util::location_name(area.area); for child in Self::render_markdown(&format!("#### {}\n{}", location_name, area.steps), path, (step_idx, area_idx)) { flex.add_child(child); } self.children.push(( (step_idx, area_idx), WidgetPod::new(flex).boxed(), )); } } } fn render_markdown(src: &str, path: &str, pos: (usize, usize)) -> Vec>> { MarkdownRenderer::new(src, path, pos).render() } } impl Widget for NotesViewer { fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut App, env: &Env) { for (_, child) in &mut self.children { child.event(ctx, event, data, env); } } fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle, data: &App, env: &Env) { for (_, child) in &mut self.children { child.lifecycle(ctx, event, data, env); } match event { LifeCycle::WidgetAdded => { self.rerender(&data.notes); ctx.children_changed(); } _ => {} } } fn update(&mut self, ctx: &mut UpdateCtx, old_data: &App, data: &App, env: &Env) { for (_, child) in &mut self.children { child.update(ctx, data, env); } // if the notes have changed, we need to rerender them if !old_data.notes.same(&data.notes) { self.rerender(&data.notes); ctx.children_changed(); } // if the note state has changed, a new section needs to be highlighted if !old_data.notes_state.same(&data.notes_state) { self.highlighted = data.notes_state .as_ref() .and_then(|state| state.step_idx.map(|step_idx| (step_idx, state.area_idx))); ctx.request_paint(); } } fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints, data: &App, env: &Env) -> Size { self.rects.clear(); let mut pos = Point::ZERO; let mut size = bc.min(); for ((step, area), child) in &mut self.children { let child_size = child.layout(ctx, bc, data, env); child.set_origin(ctx, data, env, pos); self.rects.insert((*step, *area), Rect::from_origin_size(pos, child_size)); pos.y += child_size.height; size.height += child_size.height; size.width = child_size.width.max(size.width); } size } fn paint(&mut self, ctx: &mut PaintCtx, data: &App, env: &Env) { for (_, child) in &mut self.children { child.paint(ctx, data, env); } } }