diff --git a/src/app.rs b/src/app.rs index 86a247e..d322b50 100644 --- a/src/app.rs +++ b/src/app.rs @@ -236,9 +236,13 @@ impl App { } fn insert_text_node(&mut self, at: Pos2, ctx: &Context) { - let id = self - .view - .insert_node(&mut self.doc, NodeKind::Text { text: String::new() }, at); + let id = self.view.insert_node( + &mut self.doc, + NodeKind::Text { + text: String::new(), + }, + at, + ); self.view.edit_node(&id); self.set_status("Added a text node", ctx); } @@ -513,7 +517,11 @@ impl App { } // Arrow keys nudge the selection. - let step = if ctx.input(|i| i.modifiers.shift) { 10.0 } else { 1.0 }; + let step = if ctx.input(|i| i.modifiers.shift) { + 10.0 + } else { + 1.0 + }; let mut delta = egui::Vec2::ZERO; for (key, direction) in [ (Key::ArrowLeft, egui::vec2(-1.0, 0.0)), @@ -742,10 +750,18 @@ impl App { fn toolbar(&mut self, ui: &mut Ui, ctx: &Context) { ui.horizontal(|ui| { - if ui.button("New").on_hover_text("New canvas (Ctrl+N)").clicked() { + if ui + .button("New") + .on_hover_text("New canvas (Ctrl+N)") + .clicked() + { self.new_document(ctx); } - if ui.button("Open").on_hover_text("Open a canvas (Ctrl+O)").clicked() { + if ui + .button("Open") + .on_hover_text("Open a canvas (Ctrl+O)") + .clicked() + { self.open_dialog(ctx); } if ui.button("Save").on_hover_text("Save (Ctrl+S)").clicked() { @@ -798,7 +814,11 @@ impl App { ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { ui.toggle_value(&mut self.show_inspector, "Inspector"); ui.toggle_value(&mut self.view.snap_to_grid, "Snap"); - let icon = if self.dark_mode { "Light theme" } else { "Dark theme" }; + let icon = if self.dark_mode { + "Light theme" + } else { + "Dark theme" + }; if ui.button(icon).on_hover_text("Switch theme").clicked() { self.dark_mode = !self.dark_mode; } diff --git a/src/document.rs b/src/document.rs index d319cf1..3cf2798 100644 --- a/src/document.rs +++ b/src/document.rs @@ -231,9 +231,9 @@ mod tests { } #[test] - fn saving_and_loading_round_trips( - ) -> Result<(), Box> { - let dir = std::env::temp_dir().join(format!("jsoncanvas-test-{}", crate::model::random_id())); + fn saving_and_loading_round_trips() -> Result<(), Box> { + let dir = + std::env::temp_dir().join(format!("jsoncanvas-test-{}", crate::model::random_id())); fs::create_dir_all(&dir)?; let path = dir.join("test.canvas"); @@ -252,7 +252,8 @@ mod tests { #[test] fn opening_a_broken_file_reports_an_error() { - let dir = std::env::temp_dir().join(format!("jsoncanvas-test-{}", crate::model::random_id())); + let dir = + std::env::temp_dir().join(format!("jsoncanvas-test-{}", crate::model::random_id())); fs::create_dir_all(&dir).unwrap(); let path = dir.join("bad.canvas"); fs::write(&path, "{ not json").unwrap(); diff --git a/src/geometry.rs b/src/geometry.rs index 6cd8326..845c188 100644 --- a/src/geometry.rs +++ b/src/geometry.rs @@ -99,7 +99,9 @@ pub fn bezier_point(p: [Pos2; 4], t: f32) -> Pos2 { /// Derivative of a cubic Bézier at `t`; the direction the curve travels in. pub fn bezier_tangent(p: [Pos2; 4], t: f32) -> Vec2 { let u = 1.0 - t; - let d = (p[1] - p[0]) * (3.0 * u * u) + (p[2] - p[1]) * (6.0 * u * t) + (p[3] - p[2]) * (3.0 * t * t); + let d = (p[1] - p[0]) * (3.0 * u * u) + + (p[2] - p[1]) * (6.0 * u * t) + + (p[3] - p[2]) * (3.0 * t * t); if d.length() > f32::EPSILON { d.normalized() } else { @@ -126,7 +128,10 @@ pub fn distance_to_curve(p: [Pos2; 4], point: Pos2) -> f32 { pub fn curve_bounds(p: [Pos2; 4]) -> Rect { let mut rect = Rect::from_points(&[p[0], p[3]]); for i in 1..SAMPLES { - rect = rect.union(Rect::from_points(&[bezier_point(p, i as f32 / SAMPLES as f32)])); + rect = rect.union(Rect::from_points(&[bezier_point( + p, + i as f32 / SAMPLES as f32, + )])); } rect } diff --git a/src/inspector.rs b/src/inspector.rs index 023ff2c..af03ef2 100644 --- a/src/inspector.rs +++ b/src/inspector.rs @@ -53,7 +53,11 @@ fn node_section( let Some(node) = doc.canvas.node(id).cloned() else { return; }; - heading(ui, palette, &format!("{} node", capitalise(node.kind.type_name()))); + heading( + ui, + palette, + &format!("{} node", capitalise(node.kind.type_name())), + ); match &node.kind { NodeKind::Text { text } => { @@ -66,7 +70,8 @@ fn node_section( ); if response.changed() { doc.begin_change("Edit text"); - if let Some(NodeKind::Text { text }) = doc.canvas.node_mut(id).map(|n| &mut n.kind) { + if let Some(NodeKind::Text { text }) = doc.canvas.node_mut(id).map(|n| &mut n.kind) + { *text = buffer; } } @@ -89,7 +94,11 @@ fn node_section( doc.change("Set file"); set_file(doc, id, Some(path.clone()), None); } - if ui.button("Open").on_hover_text("Open with the system default application").clicked() { + if ui + .button("Open") + .on_hover_text("Open with the system default application") + .clicked() + { out.open_target = Some(file.clone()); } }); @@ -270,15 +279,29 @@ fn geometry_section(ui: &mut Ui, doc: &mut Document, palette: &Palette, id: &str ui.label(field_label(palette, "Position and size")); let mut changed = false; ui.horizontal(|ui| { - changed |= ui.add(DragValue::new(&mut x).speed(1).prefix("x ")).changed(); - changed |= ui.add(DragValue::new(&mut y).speed(1).prefix("y ")).changed(); + changed |= ui + .add(DragValue::new(&mut x).speed(1).prefix("x ")) + .changed(); + changed |= ui + .add(DragValue::new(&mut y).speed(1).prefix("y ")) + .changed(); }); ui.horizontal(|ui| { changed |= ui - .add(DragValue::new(&mut w).speed(1).range(1..=100_000).prefix("w ")) + .add( + DragValue::new(&mut w) + .speed(1) + .range(1..=100_000) + .prefix("w "), + ) .changed(); changed |= ui - .add(DragValue::new(&mut h).speed(1).range(1..=100_000).prefix("h ")) + .add( + DragValue::new(&mut h) + .speed(1) + .range(1..=100_000) + .prefix("h "), + ) .changed(); }); if changed { @@ -316,9 +339,13 @@ fn edge_section( .unwrap_or_else(|| format!("missing node {node_id}")) }; ui.label( - RichText::new(format!("{} → {}", name_of(&edge.from_node), name_of(&edge.to_node))) - .size(12.0) - .color(palette.text_muted), + RichText::new(format!( + "{} → {}", + name_of(&edge.from_node), + name_of(&edge.to_node) + )) + .size(12.0) + .color(palette.text_muted), ); ui.add_space(8.0); @@ -393,10 +420,7 @@ fn edge_section( fn side_combo(ui: &mut Ui, label: &str, side: &mut Option) { ComboBox::from_id_salt(label) - .selected_text(format!( - "{label}: {}", - side.map_or("auto", Side::label) - )) + .selected_text(format!("{label}: {}", side.map_or("auto", Side::label))) .width(110.0) .show_ui(ui, |ui| { ui.selectable_value(side, None, "auto"); @@ -592,7 +616,10 @@ fn canvas_section(ui: &mut Ui, doc: &mut Document, palette: &Palette, out: &mut if ui.button("Remove them").clicked() { doc.change("Remove dangling edges"); let removed = doc.canvas.drop_dangling_edges(); - out.status = Some(format!("Removed {removed} dangling edge{}", plural(removed))); + out.status = Some(format!( + "Removed {removed} dangling edge{}", + plural(removed) + )); } } @@ -604,7 +631,11 @@ fn canvas_section(ui: &mut Ui, doc: &mut Document, palette: &Palette, out: &mut "Middle- or right-drag to pan, Ctrl+wheel to zoom", "Hold Shift to add to the selection", ] { - ui.label(RichText::new(format!("• {tip}")).size(12.0).color(palette.text_muted)); + ui.label( + RichText::new(format!("• {tip}")) + .size(12.0) + .color(palette.text_muted), + ); } } @@ -758,10 +789,8 @@ fn pick_any_file(doc: &Document) -> Option { } fn pick_image_file(doc: &Document) -> Option { - let mut dialog = rfd::FileDialog::new().add_filter( - "Images", - &["png", "jpg", "jpeg", "gif", "bmp", "webp"], - ); + let mut dialog = + rfd::FileDialog::new().add_filter("Images", &["png", "jpg", "jpeg", "gif", "bmp", "webp"]); if let Some(dir) = doc.base_dir() { dialog = dialog.set_directory(dir); } @@ -787,7 +816,16 @@ mod tests { use crate::view::CanvasView; fn node(id: &str, x: i32, y: i32, w: i32, h: i32) -> Node { - Node::new(id, NodeKind::Text { text: String::new() }, x, y, w, h) + Node::new( + id, + NodeKind::Text { + text: String::new(), + }, + x, + y, + w, + h, + ) } #[test] diff --git a/src/markdown.rs b/src/markdown.rs index 1c45475..9b47cc8 100644 --- a/src/markdown.rs +++ b/src/markdown.rs @@ -44,12 +44,25 @@ pub type Cell = Vec; #[derive(Debug, Clone, PartialEq)] pub enum Block { - Heading { level: u8, spans: Vec }, + Heading { + level: u8, + spans: Vec, + }, Paragraph(Vec), - List { indent: usize, marker: Marker, spans: Vec }, + List { + indent: usize, + marker: Marker, + spans: Vec, + }, Quote(Vec), - Code { language: String, text: String }, - Table { header: Vec, rows: Vec> }, + Code { + language: String, + text: String, + }, + Table { + header: Vec, + rows: Vec>, + }, Rule, } @@ -129,9 +142,7 @@ pub fn parse(source: &str) -> Vec { } // A pipe table: a header row, a `| --- |` separator, then the body. - if trimmed.contains('|') - && lines.peek().is_some_and(|next| is_separator_row(next)) - { + if trimmed.contains('|') && lines.peek().is_some_and(|next| is_separator_row(next)) { flush(&mut paragraph, &mut blocks, false); flush(&mut quote, &mut blocks, true); let header = split_row(trimmed); @@ -218,7 +229,8 @@ fn heading_of(line: &str) -> Option<(u8, &str)> { if (1..=6).contains(&hashes) { let rest = &line[hashes..]; // ATX headings need a space after the hashes. - rest.strip_prefix(' ').map(|rest| (hashes as u8, rest.trim())) + rest.strip_prefix(' ') + .map(|rest| (hashes as u8, rest.trim())) } else { None } @@ -237,12 +249,18 @@ fn list_item_of(line: &str) -> Option<(usize, Marker, &str)> { let rest_trimmed = rest.trim_start(); let lowered = rest_trimmed.to_ascii_lowercase(); if let Some(task) = lowered.strip_prefix("[ ] ") { - (Marker::Task(false), &rest_trimmed[rest_trimmed.len() - task.len()..]) + ( + Marker::Task(false), + &rest_trimmed[rest_trimmed.len() - task.len()..], + ) } else if let Some(task) = lowered .strip_prefix("[x] ") .or_else(|| lowered.strip_prefix("[/] ")) { - (Marker::Task(true), &rest_trimmed[rest_trimmed.len() - task.len()..]) + ( + Marker::Task(true), + &rest_trimmed[rest_trimmed.len() - task.len()..], + ) } else { (Marker::Bullet, rest) } @@ -252,7 +270,9 @@ fn list_item_of(line: &str) -> Option<(usize, Marker, &str)> { return None; } let rest = &trimmed[digits.len()..]; - let rest = rest.strip_prefix(". ").or_else(|| rest.strip_prefix(") "))?; + let rest = rest + .strip_prefix(". ") + .or_else(|| rest.strip_prefix(") "))?; (Marker::Number(digits.parse().ok()?), rest) }; @@ -304,7 +324,10 @@ fn parse_inline_into(text: &str, style: Style, out: &mut Vec) { flush!(); out.push(Span { text: inner.to_owned(), - style: Style { code: true, ..style }, + style: Style { + code: true, + ..style + }, link: None, image: false, }); @@ -314,21 +337,42 @@ fn parse_inline_into(text: &str, style: Style, out: &mut Vec) { if let Some((inner, tail)) = delimited(rest, "**").or_else(|| delimited(rest, "__")) { flush!(); - parse_inline_into(inner, Style { bold: true, ..style }, out); + parse_inline_into( + inner, + Style { + bold: true, + ..style + }, + out, + ); rest = tail; continue; } if let Some((inner, tail)) = delimited(rest, "~~") { flush!(); - parse_inline_into(inner, Style { strike: true, ..style }, out); + parse_inline_into( + inner, + Style { + strike: true, + ..style + }, + out, + ); rest = tail; continue; } if let Some((inner, tail)) = delimited(rest, "*").or_else(|| delimited(rest, "_")) { flush!(); - parse_inline_into(inner, Style { italic: true, ..style }, out); + parse_inline_into( + inner, + Style { + italic: true, + ..style + }, + out, + ); rest = tail; continue; } @@ -540,10 +584,8 @@ pub fn render(ui: &mut Ui, blocks: &[Block], palette: &Palette, base_size: f32) Block::Rule => { let stroke = egui::Stroke::new(1.0, palette.border); let width = ui.available_width(); - let (rect, _) = ui.allocate_exact_size( - egui::vec2(width, base_size * 0.6), - egui::Sense::hover(), - ); + let (rect, _) = ui + .allocate_exact_size(egui::vec2(width, base_size * 0.6), egui::Sense::hover()); ui.painter().line_segment( [ egui::pos2(rect.left(), rect.center().y), @@ -712,7 +754,9 @@ mod tests { #[test] fn parses_pipe_tables() { - let blocks = parse("| Key | What it does |\n| --- | :---: |\n| `a` | first |\n| b | second |\n\nafter"); + let blocks = parse( + "| Key | What it does |\n| --- | :---: |\n| `a` | first |\n| b | second |\n\nafter", + ); let Block::Table { header, rows } = &blocks[0] else { panic!("expected a table, got {blocks:?}"); }; diff --git a/src/nodes.rs b/src/nodes.rs index 6ec8dc0..bbb4a18 100644 --- a/src/nodes.rs +++ b/src/nodes.rs @@ -46,8 +46,9 @@ impl PreviewCache { } fn text_preview(&mut self, path: &Path) -> &Result { - self.text.entry(path.to_path_buf()).or_insert_with(|| { - match std::fs::read(path) { + self.text + .entry(path.to_path_buf()) + .or_insert_with(|| match std::fs::read(path) { Ok(bytes) => { let truncated = bytes.len() > MAX_PREVIEW_BYTES; let head = &bytes[..bytes.len().min(MAX_PREVIEW_BYTES)]; @@ -58,8 +59,7 @@ impl PreviewCache { Ok(text) } Err(err) => Err(format!("{err}")), - } - }) + }) } } @@ -167,11 +167,7 @@ fn render_file( activated = Some(file.to_owned()); } if let Some(subpath) = subpath { - ui.label( - RichText::new(subpath) - .size(12.0) - .color(palette.text_muted), - ); + ui.label(RichText::new(subpath).size(12.0).color(palette.text_muted)); } }); @@ -299,10 +295,7 @@ fn render_link( /// The host part of a URL, for the link node header. fn host_of(url: &str) -> &str { - let rest = url - .split_once("://") - .map(|(_, rest)| rest) - .unwrap_or(url); + let rest = url.split_once("://").map(|(_, rest)| rest).unwrap_or(url); let host = rest.split(['/', '?', '#']).next().unwrap_or(rest); if host.is_empty() { url } else { host } } @@ -327,11 +320,12 @@ mod tests { PathBuf::from("/vault/boards/notes/a.md") ); // Absolute paths are left alone. - let absolute = if cfg!(windows) { "C:\\tmp\\a.md" } else { "/tmp/a.md" }; - assert_eq!( - resolve_path(Some(base), absolute), - PathBuf::from(absolute) - ); + let absolute = if cfg!(windows) { + "C:\\tmp\\a.md" + } else { + "/tmp/a.md" + }; + assert_eq!(resolve_path(Some(base), absolute), PathBuf::from(absolute)); } #[test] @@ -358,7 +352,10 @@ mod tests { #[test] fn shows_the_host_of_a_link() { - assert_eq!(host_of("https://jsoncanvas.org/spec/1.0/"), "jsoncanvas.org"); + assert_eq!( + host_of("https://jsoncanvas.org/spec/1.0/"), + "jsoncanvas.org" + ); assert_eq!(host_of("example.com"), "example.com"); } } diff --git a/src/theme.rs b/src/theme.rs index 343884e..00d15a6 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -129,7 +129,8 @@ pub fn blend(fg: Color32, bg: Color32, strength: f32) -> Color32 { /// Black or white, whichever stays readable on `background`. pub fn contrasting_text(background: Color32) -> Color32 { - let luma = 0.299 * background.r() as f32 + 0.587 * background.g() as f32 + let luma = 0.299 * background.r() as f32 + + 0.587 * background.g() as f32 + 0.114 * background.b() as f32; if luma > 150.0 { Color32::from_rgb(0x1e, 0x1e, 0x24) diff --git a/src/view.rs b/src/view.rs index fb4d1aa..408cb68 100644 --- a/src/view.rs +++ b/src/view.rs @@ -6,9 +6,7 @@ use std::collections::HashMap; use eframe::egui::{ self, Align, Atom, Button, Color32, CornerRadius, CursorIcon, Id, LayerId, Layout, PointerButton, Pos2, Rect, RichText, Sense, Shape, Stroke, StrokeKind, TextEdit, UiBuilder, - Vec2, - containers::scroll_area::ScrollSource, - epaint, pos2, vec2, + Vec2, containers::scroll_area::ScrollSource, epaint, pos2, vec2, }; use eframe::emath::TSTransform; @@ -17,7 +15,7 @@ use crate::geometry::{ anchor, auto_sides, bezier_point, bezier_tangent, bounds_of, curve_between, distance_to_curve, edge_curve, node_rect, }; -use crate::model::{Canvas, Color, Edge, EndShape, Extra, Node, NodeKind, Side, MIN_NODE_SIZE}; +use crate::model::{Canvas, Color, Edge, EndShape, Extra, MIN_NODE_SIZE, Node, NodeKind, Side}; use crate::nodes::{self, BodyContext, PreviewCache}; use crate::theme::{self, Palette}; @@ -215,9 +213,15 @@ enum Gesture { accumulated: Vec2, }, /// Rubber band selection from `start` (canvas coordinates). - Band { start: Pos2, add: bool }, + Band { + start: Pos2, + add: bool, + }, /// Dragging a new edge out of `from`/`side`. - Connect { from: String, side: Side }, + Connect { + from: String, + side: Side, + }, } /// A node the user asked for from the canvas context menu. Insertion goes @@ -480,7 +484,9 @@ impl CanvasView { let edges: Vec = canvas .edges .iter() - .filter(|e| self.selection.has_node(&e.from_node) && self.selection.has_node(&e.to_node)) + .filter(|e| { + self.selection.has_node(&e.from_node) && self.selection.has_node(&e.to_node) + }) .cloned() .collect(); Some(Clipboard { nodes, edges }) @@ -552,7 +558,11 @@ impl CanvasView { if self.selection.nodes.is_empty() { return; } - doc.change(if to_front { "Bring to front" } else { "Send to back" }); + doc.change(if to_front { + "Bring to front" + } else { + "Send to back" + }); let mut moved: Vec = Vec::new(); let mut rest: Vec = Vec::new(); for node in doc.canvas.nodes.drain(..) { @@ -588,19 +598,15 @@ impl CanvasView { // -- the frame -------------------------------------------------------- - pub fn show( - &mut self, - ui: &mut egui::Ui, - doc: &mut Document, - palette: &Palette, - ) -> ViewOutput { + pub fn show(&mut self, ui: &mut egui::Ui, doc: &mut Document, palette: &Palette) -> ViewOutput { let mut output = ViewOutput::default(); self.selection.prune(&doc.canvas); // Deleted nodes leave a scroll position behind. The map only ever holds // nodes that have been on screen, so it outgrows the canvas only once // one of them is gone. if self.body_scroll.len() > doc.canvas.nodes.len() { - self.body_scroll.retain(|id, _| doc.canvas.node(id).is_some()); + self.body_scroll + .retain(|id, _| doc.canvas.node(id).is_some()); } if self .editing @@ -753,7 +759,11 @@ impl CanvasView { // Zoomed out far enough the dots crowd into noise, so only every fifth // one is kept; below that they are dropped altogether. let scale = self.to_global.scaling; - let step = if GRID * scale < 10.0 { GRID * MAJOR } else { GRID }; + let step = if GRID * scale < 10.0 { + GRID * MAJOR + } else { + GRID + }; if step * scale < 6.0 { return; } @@ -803,12 +813,14 @@ impl CanvasView { continue; } - painter.add(Shape::CubicBezier(epaint::CubicBezierShape::from_points_stroke( - curve, - false, - Color32::TRANSPARENT, - Stroke::new(width, color), - ))); + painter.add(Shape::CubicBezier( + epaint::CubicBezierShape::from_points_stroke( + curve, + false, + Color32::TRANSPARENT, + Stroke::new(width, color), + ), + )); if edge.end_shape() == EndShape::Arrow { paint_arrow(painter, curve[3], bezier_tangent(curve, 1.0), width, color); @@ -937,7 +949,13 @@ impl CanvasView { // nodes here would pull the canvas out from under this very loop, // so `handle_interactions` carries the action out afterwards. response.context_menu(|ui| { - node_menu(ui, palette, color.as_ref(), selected, &mut interactions.menu); + node_menu( + ui, + palette, + color.as_ref(), + selected, + &mut interactions.menu, + ); }); } interactions @@ -985,7 +1003,12 @@ impl CanvasView { 1.5 }; let stroke_color = if selected { palette.accent } else { border }; - painter.rect_stroke(rect, corner, Stroke::new(width, stroke_color), StrokeKind::Inside); + painter.rect_stroke( + rect, + corner, + Stroke::new(width, stroke_color), + StrokeKind::Inside, + ); } } @@ -1039,8 +1062,7 @@ impl CanvasView { let (fill, _) = palette.group_colors(doc.canvas.nodes[index].color.as_ref()); let chip_fill = theme::blend(fill, palette.surface, 0.5); let text_color = theme::contrasting_text(chip_fill); - let galley = - painter.layout_no_wrap(text, egui::FontId::proportional(14.0), text_color); + let galley = painter.layout_no_wrap(text, egui::FontId::proportional(14.0), text_color); let chip = Rect::from_min_size(label_rect.min, galley.size() + vec2(12.0, 6.0)); painter.rect_filled(chip, CornerRadius::same(4), chip_fill); painter.galley(chip.min + vec2(6.0, 3.0), galley, text_color); @@ -1098,7 +1120,9 @@ impl CanvasView { fn paint_selection(&self, ui: &egui::Ui, canvas: &Canvas, palette: &Palette) { let painter = ui.painter(); for id in &self.selection.nodes { - let Some(node) = canvas.node(id) else { continue }; + let Some(node) = canvas.node(id) else { + continue; + }; painter.rect_stroke( node_rect(node).expand(2.5), CornerRadius::same((NODE_CORNER + 2.0) as u8), @@ -1214,7 +1238,10 @@ impl CanvasView { if response.dragged() { used = true; if let Gesture::Resize { - accumulated, origin, handle, .. + accumulated, + origin, + handle, + .. } = &mut self.gesture { *accumulated += response.drag_delta(); @@ -1264,7 +1291,9 @@ impl CanvasView { ); } Gesture::Connect { from, side } => { - let Some(node) = canvas.node(from) else { return }; + let Some(node) = canvas.node(from) else { + return; + }; let Some(pointer) = ui .ctx() .pointer_latest_pos() @@ -1281,13 +1310,21 @@ impl CanvasView { _ => (pointer, side.opposite()), }; let curve = curve_between(anchor(node_rect(node), *side), *side, end, to_side); - painter.add(Shape::CubicBezier(epaint::CubicBezierShape::from_points_stroke( - curve, - false, - Color32::TRANSPARENT, - Stroke::new(2.0, palette.accent), - ))); - paint_arrow(painter, curve[3], bezier_tangent(curve, 1.0), 2.0, palette.accent); + painter.add(Shape::CubicBezier( + epaint::CubicBezierShape::from_points_stroke( + curve, + false, + Color32::TRANSPARENT, + Stroke::new(2.0, palette.accent), + ), + )); + paint_arrow( + painter, + curve[3], + bezier_tangent(curve, 1.0), + 2.0, + palette.accent, + ); } _ => {} } @@ -1451,7 +1488,13 @@ impl CanvasView { && self.hovered_edge.is_none() && let Some(at) = pointer { - let id = self.insert_node(doc, NodeKind::Text { text: String::new() }, at); + let id = self.insert_node( + doc, + NodeKind::Text { + text: String::new(), + }, + at, + ); self.edit_node(&id); output.status = Some("Added a text node".to_owned()); } @@ -1472,7 +1515,9 @@ impl CanvasView { /// Opens a file/link node, or starts editing a text node. fn activate_node(&mut self, doc: &mut Document, id: &str, output: &mut ViewOutput) { - let Some(node) = doc.canvas.node(id) else { return }; + let Some(node) = doc.canvas.node(id) else { + return; + }; match &node.kind { NodeKind::Text { .. } | NodeKind::Group { .. } => self.edit_node(id), NodeKind::File { file, .. } => output.open_target = Some(file.clone()), @@ -1507,7 +1552,13 @@ impl CanvasView { } None => { // Dropping on empty canvas creates the node the edge points to. - let new_id = self.insert_node(doc, NodeKind::Text { text: String::new() }, pointer); + let new_id = self.insert_node( + doc, + NodeKind::Text { + text: String::new(), + }, + pointer, + ); let edge_id = doc.canvas.fresh_id(); doc.canvas .edges @@ -1885,8 +1936,7 @@ fn paint_group_background( ) } }; - ui.painter() - .image(texture.id, target, uv, Color32::WHITE); + ui.painter().image(texture.id, target, uv, Color32::WHITE); } fn paint_arrow(painter: &egui::Painter, tip: Pos2, direction: Vec2, width: f32, color: Color32) { @@ -2147,7 +2197,13 @@ mod tests { let at = pos2(600.0, 450.0); // Three passes: egui hit-tests against the rectangles of the previous // one, and a click is a press followed by a release. - pass(&ctx, &mut view, &mut doc, screen, vec![egui::Event::PointerMoved(at)]); + pass( + &ctx, + &mut view, + &mut doc, + screen, + vec![egui::Event::PointerMoved(at)], + ); pass(&ctx, &mut view, &mut doc, screen, vec![click(at)]); pass(&ctx, &mut view, &mut doc, screen, vec![release(at)]); @@ -2272,10 +2328,16 @@ mod tests { canvas .edges .push(Edge::new("e", "a", Side::Right, "b", Side::Left)); - assert_eq!(view.node_at(&canvas, pos2(50.0, 50.0)).as_deref(), Some("a")); + assert_eq!( + view.node_at(&canvas, pos2(50.0, 50.0)).as_deref(), + Some("a") + ); assert_eq!(view.node_at(&canvas, pos2(-5.0, 50.0)), None); // The edge runs from (100,50) to (300,50). - assert_eq!(view.edge_at(&canvas, pos2(200.0, 50.0)).as_deref(), Some("e")); + assert_eq!( + view.edge_at(&canvas, pos2(200.0, 50.0)).as_deref(), + Some("e") + ); assert_eq!(view.edge_at(&canvas, pos2(200.0, 200.0)), None); } @@ -2298,8 +2360,14 @@ mod tests { ), ); let view = CanvasView::new(); - assert_eq!(view.node_at(&canvas, pos2(50.0, 50.0)).as_deref(), Some("a")); - assert_eq!(view.node_at(&canvas, pos2(200.0, 200.0)).as_deref(), Some("g")); + assert_eq!( + view.node_at(&canvas, pos2(50.0, 50.0)).as_deref(), + Some("a") + ); + assert_eq!( + view.node_at(&canvas, pos2(200.0, 200.0)).as_deref(), + Some("g") + ); } #[test] @@ -2491,7 +2559,10 @@ mod tests { view.set_selection_color(&mut doc, Some(Color::Preset(4))); assert!( - doc.canvas.nodes.iter().all(|n| n.color == Some(Color::Preset(4))), + doc.canvas + .nodes + .iter() + .all(|n| n.color == Some(Color::Preset(4))), "some nodes kept their old colour" ); @@ -2505,7 +2576,10 @@ mod tests { #[test] fn counts_read_naturally() { assert_eq!(count_phrase(&[(1, "node"), (0, "edge")]), "1 node"); - assert_eq!(count_phrase(&[(2, "node"), (3, "edge")]), "2 nodes and 3 edges"); + assert_eq!( + count_phrase(&[(2, "node"), (3, "edge")]), + "2 nodes and 3 edges" + ); assert_eq!(count_phrase(&[(0, "node")]), "nothing"); } }