ffxii-tza-auto-notes/src/widget/notes_viewer.rs

126 lines
4.1 KiB
Rust
Executable File

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<App, Box<dyn Widget<App>>>)>,
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<Rect> {
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 = &notes.steps[step_idx];
for area_idx in 0..step.areas.len() {
let area = &step.areas[area_idx];
let mut flex: Flex<App> = 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<Box<dyn Widget<App>>> {
MarkdownRenderer::new(src, path, pos).render()
}
}
impl Widget<App> 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);
}
}
}