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