1use std::path::Path;
9
10use yog_gfx::draw2d::Draw2D;
11use yog_gfx::{Color, GfxContext, Rect, Vec2};
12
13use super::assets::Resources;
14mod args;
15mod layer;
16
17pub use args::{AdvancementArgs, NodeArgs};
18
19use super::{gfx, images, raster};
20
21const WINDOW: (f32, f32) = (252.0, 140.0);
23const MARGIN: f32 = 8.0;
25const SCREEN: (f32, f32) = (WINDOW.0 + 2.0 * MARGIN, WINDOW.1 + 2.0 * MARGIN);
27const VIEW: Rect = Rect {
28 x: 9.0,
29 y: 18.0,
30 w: 234.0,
31 h: 113.0,
32};
33const TITLE_AT: Vec2 = Vec2 { x: 8.0, y: 6.0 };
35const STEP: (f32, f32) = (28.0, 27.0);
37const FRAME: f32 = 26.0;
38const FRAME_DX: f32 = 3.0;
39const ICON_AT: Vec2 = Vec2 { x: 8.0, y: 5.0 };
40const ICON: f32 = 16.0;
41const TILE: f32 = 16.0;
43const CONNECT_MID: f32 = 13.0;
45const CONNECT_OUT: f32 = FRAME + 4.0;
46const BAR_W: f32 = 200.0;
49const TITLE_MAX: f32 = 163.0;
50const BAR_TEXT: Vec2 = Vec2 { x: 32.0, y: 9.0 };
51const TEXT_PAD_LEFT: f32 = 3.0;
52const TEXT_PAD_RIGHT: f32 = 5.0;
53const TEXT_LEFT: f32 = 5.0;
54const LINE_H: f32 = 9.0;
55const TITLE_BAR_MIN: f32 = 29.0;
56const DESCRIPTION_TOP: f32 = 17.0;
57const DESCRIPTION_TOP_UP: f32 = 7.0;
59const DESCRIPTION_PAD: f32 = 32.0;
60const FLIP_ROOM: f32 = 6.0;
61const SPLIT_OFFSETS: [f32; 5] = [0.0, 10.0, -10.0, 25.0, -25.0];
64const SPLIT_GOOD_ENOUGH: f32 = 10.0;
65const HOVER_FADE: Color = Color(0x4C00_0000);
67const BACKDROP: (Color, Color) = (Color(0xC010_1010), Color(0xD010_1010));
69const TITLE_COLOR: Color = Color(0xFF40_4040);
70const WHITE: Color = Color(0xFFFF_FFFF);
71const BLACK: Color = Color(0xFF00_0000);
72const SCALE: u32 = 1;
74
75#[derive(Debug, Clone)]
77pub struct Node {
78 pub id: String,
79 pub parent: Option<String>,
80 pub icon: Option<String>,
81 pub frame: String,
83 pub title: String,
84 pub description: String,
85 pub obtained: bool,
87}
88
89#[derive(Debug, Clone)]
91pub struct Args {
92 pub path: String,
93 pub title: String,
94 pub background: Option<String>,
96 pub nodes: Vec<Node>,
97 pub drag: [f32; 2],
99 pub hover: Option<String>,
101}
102
103#[derive(Debug, Clone, PartialEq, yog_data::Schema)]
105pub struct Spot {
106 pub id: String,
107 pub rect: [f32; 4],
108}
109
110#[derive(Debug, Clone, yog_data::Schema)]
112pub struct Rendered {
113 pub image: Option<String>,
114 pub size: [f32; 2],
115 pub view: [f32; 4],
117 pub spots: Vec<Spot>,
118 pub drag: [f32; 2],
120 pub layer: Option<layer::Layer>,
123}
124
125struct Tab {
128 spots: Vec<(usize, Vec2, Option<Vec2>)>,
129 centre: Vec2,
131 range: [[f32; 2]; 2],
132 window: Vec2,
133 view: Vec2,
134 origin: Vec2,
135}
136
137impl Tab {
138 fn view_rect(&self) -> Rect {
139 Rect::new(self.view.x, self.view.y, VIEW.w, VIEW.h)
140 }
141}
142
143pub fn render(args: &Args) -> Rendered {
145 let root = super::mod_root(Path::new(&args.path));
146 images::set_document(root.as_deref());
147 let res = Resources::for_mod(root.as_deref());
148 let (tab, drag) = lay_out(args);
149 let ops = gfx::record(SCREEN.0 as i32, SCREEN.1 as i32, |ctx| {
150 draw(ctx, args, &tab)
151 });
152 let canvas = raster::rasterize(&ops, SCREEN.0 as u32, SCREEN.1 as u32, SCALE, &res);
153 let image = canvas.png_url();
154 let layer = args
155 .hover
156 .is_none()
157 .then(|| layer::render(args, &tab, drag, &res))
158 .flatten();
159 let o = tab.origin;
160 Rendered {
161 image,
162 size: [SCREEN.0, SCREEN.1],
163 view: [tab.view.x, tab.view.y, VIEW.w, VIEW.h],
164 spots: tab
165 .spots
166 .iter()
167 .map(|(i, p, _)| Spot {
168 id: args.nodes[*i].id.clone(),
169 rect: [o.x + p.x, o.y + p.y, FRAME + 1.0, FRAME + 1.0],
170 })
171 .collect(),
172 drag,
173 layer,
174 }
175}
176
177fn lay_out(args: &Args) -> (Tab, [f32; 2]) {
180 let at = layout(&args.nodes);
181 let place = |id: &str| {
182 at.iter()
183 .find(|(i, _)| i == id)
184 .map(|(_, p)| Vec2::new((p.0 * STEP.0).floor(), (p.1 * STEP.1).floor()))
185 };
186 let spots: Vec<(usize, Vec2, Option<Vec2>)> = args
187 .nodes
188 .iter()
189 .enumerate()
190 .filter_map(|(i, n)| Some((i, place(&n.id)?, n.parent.as_deref().and_then(place))))
191 .collect();
192 let min_x = spots.iter().map(|(_, p, _)| p.x).fold(f32::MAX, f32::min);
193 let max_x = spots
194 .iter()
195 .map(|(_, p, _)| p.x + STEP.0)
196 .fold(f32::MIN, f32::max);
197 let min_y = spots.iter().map(|(_, p, _)| p.y).fold(f32::MAX, f32::min);
198 let max_y = spots
199 .iter()
200 .map(|(_, p, _)| p.y + STEP.1)
201 .fold(f32::MIN, f32::max);
202 let centre = Vec2::new(
203 (VIEW.w / 2.0 - ((max_x + min_x) / 2.0).trunc()).trunc(),
204 ((VIEW.h / 2.0).floor() - ((max_y + min_y) / 2.0).trunc()).trunc(),
205 );
206 let range = |centre: f32, extent: f32, min: f32, view: f32| {
209 if extent - min > view {
210 [-(extent - view), 0.0]
211 } else {
212 [centre, centre]
213 }
214 };
215 let range = [
216 range(centre.x, max_x, min_x, VIEW.w),
217 range(centre.y, max_y, min_y, VIEW.h),
218 ];
219 let axis = |centre: f32, drag: f32, [lo, hi]: [f32; 2]| (centre + drag).clamp(lo, hi) - centre;
220 let drag = [
221 axis(centre.x, args.drag[0], range[0]),
222 axis(centre.y, args.drag[1], range[1]),
223 ];
224 let scroll = Vec2::new((centre.x + drag[0]).floor(), (centre.y + drag[1]).floor());
225 let window = Vec2::new(
226 ((SCREEN.0 - WINDOW.0) / 2.0).floor(),
227 ((SCREEN.1 - WINDOW.1) / 2.0).floor(),
228 );
229 let view = Vec2::new(window.x + VIEW.x, window.y + VIEW.y);
230 let origin = Vec2::new(view.x + scroll.x, view.y + scroll.y);
231 (
232 Tab {
233 spots,
234 centre,
235 range,
236 window,
237 view,
238 origin,
239 },
240 drag,
241 )
242}
243
244fn draw(ctx: &GfxContext, args: &Args, tab: &Tab) {
247 let d = &ctx.draw2d();
248 d.rect(Rect::new(0.0, 0.0, SCREEN.0, SCREEN.1), BLACK);
250 d.gradient(
251 Rect::new(0.0, 0.0, SCREEN.0, SCREEN.1),
252 BACKDROP.0,
253 BACKDROP.1,
254 );
255 let v = tab.view_rect();
258 ctx.set_scissor(v.x as i32, v.y as i32, v.w as i32, v.h as i32);
259 draw_background(d, args, v, tab.origin);
260 draw_tree(d, args, &tab.spots, tab.origin);
261 ctx.clear_scissor();
262 draw_window(d, args, tab);
263 let w = tab.window;
264 if let Some((i, p, _)) = args
266 .hover
267 .as_deref()
268 .and_then(|h| tab.spots.iter().find(|(i, _, _)| args.nodes[*i].id == h))
269 {
270 d.rect(tab.view_rect(), HOVER_FADE);
271 hover(d, &args.nodes[*i], tab.origin, *p, w);
272 }
273}
274
275fn draw_window(d: &Draw2D, args: &Args, tab: &Tab) {
277 let w = tab.window;
278 d.image(WINDOW_SPRITE, Rect::new(w.x, w.y, WINDOW.0, WINDOW.1));
279 d.text(
280 &args.title,
281 Vec2::new(w.x + TITLE_AT.x, w.y + TITLE_AT.y),
282 TITLE_COLOR,
283 false,
284 );
285}
286
287fn draw_background(d: &Draw2D, args: &Args, area: Rect, origin: Vec2) {
290 let background = args.background.as_deref().map_or_else(
291 || yog_ids::game::MISSING_TEXTURE.to_string(),
292 yog_gfx::image::short_source,
293 );
294 let first = |at: f32, o: f32| o + ((at - o) / TILE).floor() * TILE;
295 let mut y = first(area.y, origin.y);
296 while y < area.y + area.h {
297 let mut x = first(area.x, origin.x);
298 while x < area.x + area.w {
299 d.image(&background, Rect::new(x, y, TILE, TILE));
300 x += TILE;
301 }
302 y += TILE;
303 }
304}
305
306fn draw_tree(d: &Draw2D, args: &Args, spots: &[(usize, Vec2, Option<Vec2>)], o: Vec2) {
308 let fill = |r: Rect, c: Color| d.rect(r, c);
309 for shadow in [true, false] {
310 for (_, p, parent) in spots {
311 if let Some(pp) = parent {
312 connector(&fill, o, *pp, *p, shadow);
313 }
314 }
315 }
316 for (i, p, _) in spots {
317 let n = &args.nodes[*i];
318 let frame = Rect::new(o.x + p.x + FRAME_DX, o.y + p.y, FRAME, FRAME);
319 d.image(&frame_sprite(&n.frame, n.obtained), frame);
320 if let Some(icon) = &n.icon {
321 d.item(
322 icon,
323 Vec2::new(o.x + p.x + ICON_AT.x, o.y + p.y + ICON_AT.y),
324 ICON,
325 );
326 }
327 }
328}
329
330const WINDOW_SPRITE: &str = "sprite:advancement_window";
332fn frame_sprite(frame: &str, obtained: bool) -> String {
333 format!(
334 "sprite:advancement/{frame}{}",
335 if obtained { "_obtained" } else { "" }
336 )
337}
338fn bar_sprite(obtained: bool) -> &'static str {
339 if obtained {
340 "sprite:advancement/box_obtained"
341 } else {
342 "sprite:advancement/box"
343 }
344}
345const DESCRIPTION_SPRITE: &str = "sprite:advancement/description";
346
347const CHALLENGE: &str = "challenge";
349const DARK_PURPLE: char = '5';
350const GREEN: char = 'a';
351
352fn description_color(frame: &str) -> Color {
354 let code = if frame == CHALLENGE {
355 DARK_PURPLE
356 } else {
357 GREEN
358 };
359 Color(super::font::chat_color(code).unwrap_or(Color::WHITE.0))
360}
361
362fn connector(fill: &impl Fn(Rect, Color), o: Vec2, parent: Vec2, child: Vec2, shadow: bool) {
364 let (i, j, k) = (
365 o.x + parent.x + CONNECT_MID,
366 o.x + parent.x + CONNECT_OUT,
367 o.y + parent.y + CONNECT_MID,
368 );
369 let (l, m) = (o.x + child.x + CONNECT_MID, o.y + child.y + CONNECT_MID);
370 let h = |a: f32, b: f32, y: f32, c: Color| {
371 fill(Rect::new(a.min(b), y, (a - b).abs() + 1.0, 1.0), c)
372 };
373 let v = |x: f32, a: f32, b: f32, c: Color| {
374 fill(
375 Rect::new(x, a.min(b) + 1.0, 1.0, ((a - b).abs() - 1.0).max(0.0)),
376 c,
377 )
378 };
379 if shadow {
380 h(j, i, k - 1.0, BLACK);
381 h(j + 1.0, i, k, BLACK);
382 h(j, i, k + 1.0, BLACK);
383 h(l, j - 1.0, m - 1.0, BLACK);
384 h(l, j - 1.0, m, BLACK);
385 h(l, j - 1.0, m + 1.0, BLACK);
386 v(j - 1.0, m, k, BLACK);
387 v(j + 1.0, m, k, BLACK);
388 } else {
389 h(j, i, k, WHITE);
390 h(l, j, m, WHITE);
391 v(j, m, k, WHITE);
392 }
393}
394
395fn hover(d: &yog_gfx::draw2d::Draw2D<'_>, n: &Node, o: Vec2, p: Vec2, window: Vec2) {
397 let width_of = |s: &str| d.text_width(s, "");
398 let title = cut_to_width(&n.title, TITLE_MAX, &width_of);
399 let mut inner = TITLE_BAR_MIN + width_of(&title);
400 let lines = optimal_lines(&n.description, inner, &width_of);
401 for l in &lines {
402 inner = inner.max(width_of(l));
403 }
404 let width = inner + TEXT_PAD_LEFT + TEXT_PAD_RIGHT;
405 let y = o.y + p.y;
406 let flip = o.x + p.x + width + FRAME >= SCREEN.0;
408 let up =
409 VIEW.h - (o.y - window.y - VIEW.y) - p.y - FRAME <= FLIP_ROOM + lines.len() as f32 * LINE_H;
410 let x = if flip {
411 o.x + p.x - width + FRAME + FLIP_ROOM
412 } else {
413 o.x + p.x
414 };
415 let box_h = DESCRIPTION_PAD + lines.len() as f32 * LINE_H;
416 if !lines.is_empty() {
417 let top = if up { y + FRAME - box_h } else { y };
418 d.image(DESCRIPTION_SPRITE, Rect::new(x, top, width, box_h));
419 }
420 let half = (width / 2.0).floor();
421 let rest = width - half;
422 d.image_region(
423 bar_sprite(n.obtained),
424 Rect::new(x, y, half, FRAME),
425 Some(Rect::new(0.0, 0.0, half, FRAME)),
426 WHITE,
427 );
428 d.image_region(
429 bar_sprite(n.obtained),
430 Rect::new(x + half, y, rest, FRAME),
431 Some(Rect::new(BAR_W - rest, 0.0, rest, FRAME)),
432 WHITE,
433 );
434 d.image(
435 &frame_sprite(&n.frame, n.obtained),
436 Rect::new(o.x + p.x + FRAME_DX, y, FRAME, FRAME),
437 );
438 let title_x = if flip {
439 x + TEXT_LEFT
440 } else {
441 o.x + p.x + BAR_TEXT.x
442 };
443 d.text(&title, Vec2::new(title_x, y + BAR_TEXT.y), WHITE, true);
444 let color = description_color(&n.frame);
445 for (i, line) in lines.iter().enumerate() {
446 let ly = if up {
447 y + FRAME - box_h + DESCRIPTION_TOP_UP + i as f32 * LINE_H
448 } else {
449 y + BAR_TEXT.y + DESCRIPTION_TOP + i as f32 * LINE_H
450 };
451 d.text(line, Vec2::new(x + TEXT_LEFT, ly), color, false);
452 }
453 if let Some(icon) = &n.icon {
454 d.item(icon, Vec2::new(o.x + p.x + ICON_AT.x, y + ICON_AT.y), ICON);
455 }
456}
457
458fn cut_to_width(s: &str, max: f32, width: &impl Fn(&str) -> f32) -> String {
460 let mut end = s.len();
461 while end > 0 && width(&s[..end]) > max {
462 end = s[..end].char_indices().last().map_or(0, |(i, _)| i);
463 }
464 s[..end].to_string()
465}
466
467fn optimal_lines(text: &str, target: f32, width: &impl Fn(&str) -> f32) -> Vec<String> {
470 if text.is_empty() {
471 return Vec::new();
472 }
473 let mut best: Option<(f32, Vec<String>)> = None;
474 for off in SPLIT_OFFSETS {
475 let lines = split_lines(text, target - off, width);
476 let gap = (lines.iter().map(|l| width(l)).fold(0.0, f32::max) - target).abs();
477 if gap <= SPLIT_GOOD_ENOUGH {
478 return lines;
479 }
480 if best.as_ref().is_none_or(|(g, _)| gap < *g) {
481 best = Some((gap, lines));
482 }
483 }
484 best.map(|(_, l)| l).unwrap_or_default()
485}
486
487fn split_lines(text: &str, max: f32, width: &impl Fn(&str) -> f32) -> Vec<String> {
490 let mut out = Vec::new();
491 for para in text.split('\n') {
492 let mut line = String::new();
493 for word in para.split(' ') {
494 let candidate = if line.is_empty() {
495 word.to_string()
496 } else {
497 format!("{line} {word}")
498 };
499 if width(&candidate) <= max || line.is_empty() && width(word) <= max {
500 line = candidate;
501 continue;
502 }
503 if !line.is_empty() {
504 out.push(std::mem::take(&mut line));
505 }
506 let mut rest = word;
507 while width(rest) > max {
508 let cut = cut_to_width(rest, max, width);
509 if cut.is_empty() {
510 break;
511 }
512 out.push(cut.clone());
513 rest = &rest[cut.len()..];
514 }
515 line = rest.to_string();
516 }
517 out.push(line);
518 }
519 out
520}
521
522pub fn layout(nodes: &[Node]) -> Vec<(String, (f32, f32))> {
526 let ids: Vec<&str> = nodes.iter().map(|n| n.id.as_str()).collect();
527 let mut tree = Tree::default();
528 let mut out = Vec::new();
529 let mut offset = 0.0;
530 for root in nodes.iter().filter(|n| parent_in(n, &ids).is_none()) {
531 let r = tree.build(&root.id, None, None, 1, &Source { nodes, ids: &ids });
532 tree.first_walk(r);
533 let top = tree.second_walk(r, 0.0, 0, tree.n[r].y);
534 if top < 0.0 {
535 tree.third_walk(r, -top);
536 }
537 let mut bottom: f32 = 0.0;
538 tree.each(r, &mut |n| {
539 out.push((n.id.clone(), (n.x as f32, n.y + offset)));
540 bottom = bottom.max(n.y);
541 });
542 offset += bottom + 1.0;
543 }
544 for n in nodes {
546 if !out.iter().any(|(id, _)| *id == n.id) {
547 out.push((n.id.clone(), (0.0, offset)));
548 offset += 1.0;
549 }
550 }
551 out
552}
553
554fn parent_in<'a>(n: &'a Node, ids: &[&str]) -> Option<&'a str> {
556 n.parent
557 .as_deref()
558 .filter(|p| ids.contains(p) && *p != n.id)
559}
560
561struct Source<'a> {
563 nodes: &'a [Node],
564 ids: &'a [&'a str],
565}
566
567#[derive(Default)]
568struct Tree {
569 n: Vec<TreeNode>,
570}
571
572struct TreeNode {
573 id: String,
574 parent: Option<usize>,
575 previous: Option<usize>,
576 child_index: usize,
577 children: Vec<usize>,
578 ancestor: usize,
579 thread: Option<usize>,
580 x: i32,
581 y: f32,
582 modifier: f32,
583 change: f32,
584 shift: f32,
585}
586
587impl Tree {
588 fn build(
589 &mut self,
590 id: &str,
591 parent: Option<usize>,
592 previous: Option<usize>,
593 child_index: usize,
594 src: &Source,
595 ) -> usize {
596 let me = self.n.len();
597 let x = parent.map_or(0, |p| self.n[p].x + 1);
598 self.n.push(TreeNode {
599 id: id.to_string(),
600 parent,
601 previous,
602 child_index,
603 children: Vec::new(),
604 ancestor: me,
605 thread: None,
606 x,
607 y: -1.0,
608 modifier: 0.0,
609 change: 0.0,
610 shift: 0.0,
611 });
612 let mut prev = None;
613 for child in src
614 .nodes
615 .iter()
616 .filter(|c| parent_in(c, src.ids) == Some(id))
617 {
618 if self.n.iter().any(|t| t.id == child.id) {
619 continue;
620 }
621 let index = self.n[me].children.len() + 1;
622 let c = self.build(&child.id, Some(me), prev, index, src);
623 self.n[me].children.push(c);
624 prev = Some(c);
625 }
626 me
627 }
628
629 fn each(&self, at: usize, f: &mut impl FnMut(&TreeNode)) {
630 f(&self.n[at]);
631 for &c in &self.n[at].children.clone() {
632 self.each(c, f);
633 }
634 }
635
636 fn first_walk(&mut self, v: usize) {
637 let children = self.n[v].children.clone();
638 if children.is_empty() {
639 self.n[v].y = self.n[v].previous.map_or(0.0, |p| self.n[p].y + 1.0);
640 return;
641 }
642 let mut default = None;
643 for &c in &children {
644 self.first_walk(c);
645 default = Some(self.apportion(c, default.unwrap_or(c)));
646 }
647 self.execute_shifts(v);
648 let (Some(&first), Some(&last)) = (children.first(), children.last()) else {
649 return;
650 };
651 let mid = (self.n[first].y + self.n[last].y) / 2.0;
652 match self.n[v].previous {
653 Some(p) => {
654 self.n[v].y = self.n[p].y + 1.0;
655 self.n[v].modifier = self.n[v].y - mid;
656 }
657 None => self.n[v].y = mid,
658 }
659 }
660
661 fn second_walk(&mut self, v: usize, offset: f32, column: i32, mut top: f32) -> f32 {
662 self.n[v].y += offset;
663 self.n[v].x = column;
664 top = top.min(self.n[v].y);
665 let m = self.n[v].modifier;
666 for c in self.n[v].children.clone() {
667 top = self.second_walk(c, offset + m, column + 1, top);
668 }
669 top
670 }
671
672 fn third_walk(&mut self, v: usize, y: f32) {
673 self.n[v].y += y;
674 for c in self.n[v].children.clone() {
675 self.third_walk(c, y);
676 }
677 }
678
679 fn execute_shifts(&mut self, v: usize) {
680 let (mut shift, mut change) = (0.0, 0.0);
681 for &c in self.n[v].children.clone().iter().rev() {
682 self.n[c].y += shift;
683 self.n[c].modifier += shift;
684 change += self.n[c].change;
685 shift += self.n[c].shift + change;
686 }
687 }
688
689 fn previous_or_thread(&self, v: usize) -> Option<usize> {
690 self.n[v]
691 .thread
692 .or_else(|| self.n[v].children.first().copied())
693 }
694
695 fn next_or_thread(&self, v: usize) -> Option<usize> {
696 self.n[v]
697 .thread
698 .or_else(|| self.n[v].children.last().copied())
699 }
700
701 fn apportion(&mut self, v: usize, mut default: usize) -> usize {
702 let (Some(previous), Some(parent)) = (self.n[v].previous, self.n[v].parent) else {
703 return default;
704 };
705 let (mut in_right, mut out_right, mut in_left, mut out_left) =
706 (v, v, previous, self.n[parent].children[0]);
707 let (mut s_in_right, mut s_out_right) = (self.n[v].modifier, self.n[v].modifier);
708 let (mut s_in_left, mut s_out_left) = (self.n[in_left].modifier, self.n[out_left].modifier);
709 while let (Some(nl), Some(pr)) = (
710 self.next_or_thread(in_left),
711 self.previous_or_thread(in_right),
712 ) {
713 in_left = nl;
714 in_right = pr;
715 let (Some(ol), Some(or)) = (
717 self.previous_or_thread(out_left),
718 self.next_or_thread(out_right),
719 ) else {
720 break;
721 };
722 (out_left, out_right) = (ol, or);
723 self.n[out_right].ancestor = v;
724 let gap = self.n[in_left].y + s_in_left - (self.n[in_right].y + s_in_right) + 1.0;
725 if gap > 0.0 {
726 let a = self.ancestor_of(in_left, v, default);
727 self.move_subtree(a, v, gap);
728 s_in_right += gap;
729 s_out_right += gap;
730 }
731 s_in_left += self.n[in_left].modifier;
732 s_in_right += self.n[in_right].modifier;
733 s_out_left += self.n[out_left].modifier;
734 s_out_right += self.n[out_right].modifier;
735 }
736 if self.next_or_thread(in_left).is_some() && self.next_or_thread(out_right).is_none() {
737 self.n[out_right].thread = self.next_or_thread(in_left);
738 self.n[out_right].modifier += s_in_left - s_out_right;
739 } else {
740 if self.previous_or_thread(in_right).is_some()
741 && self.previous_or_thread(out_left).is_none()
742 {
743 self.n[out_left].thread = self.previous_or_thread(in_right);
744 self.n[out_left].modifier += s_in_right - s_out_left;
745 }
746 default = v;
747 }
748 default
749 }
750
751 fn ancestor_of(&self, of: usize, v: usize, other: usize) -> usize {
752 let a = self.n[of].ancestor;
753 let siblings = self.n[v].parent.map(|p| &self.n[p].children);
754 if siblings.is_some_and(|s| s.contains(&a)) {
755 a
756 } else {
757 other
758 }
759 }
760
761 fn move_subtree(&mut self, from: usize, to: usize, shift: f32) {
762 let subtrees = self.n[to].child_index as f32 - self.n[from].child_index as f32;
763 if subtrees != 0.0 {
764 self.n[to].change -= shift / subtrees;
765 self.n[from].change += shift / subtrees;
766 }
767 self.n[to].shift += shift;
768 self.n[to].y += shift;
769 self.n[to].modifier += shift;
770 }
771}
772
773#[cfg(test)]
774mod tests;