Svg output
This commit is contained in:
@ -14,6 +14,8 @@ serde = { version = "1", optional = true }
|
|||||||
colored = "*"
|
colored = "*"
|
||||||
lalrpop-util = "*"
|
lalrpop-util = "*"
|
||||||
petgraph = ">=0.8"
|
petgraph = ">=0.8"
|
||||||
|
nsvg = "0.5.1"
|
||||||
|
dyn-clone = "*"
|
||||||
petgraph-graphml = "*"
|
petgraph-graphml = "*"
|
||||||
egui_node_graph2 = { path = "../egui_node_graph2" }
|
egui_node_graph2 = { path = "../egui_node_graph2" }
|
||||||
getrandom = "0.3" # dependency that has to be specified correctly for wasm
|
getrandom = "0.3" # dependency that has to be specified correctly for wasm
|
||||||
|
|||||||
@ -63,6 +63,7 @@ pub enum BasicDataType {
|
|||||||
PositiveGraph,
|
PositiveGraph,
|
||||||
PositiveAssertFunction,
|
PositiveAssertFunction,
|
||||||
PositiveGroupFunction,
|
PositiveGroupFunction,
|
||||||
|
Svg,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Should reflect `BasicDataType`'s values, holding the data that will be
|
/// Should reflect `BasicDataType`'s values, holding the data that will be
|
||||||
@ -175,6 +176,9 @@ pub enum BasicValue {
|
|||||||
PositiveGroupFunction {
|
PositiveGroupFunction {
|
||||||
value: assert::positive_grouping::PositiveAssert,
|
value: assert::positive_grouping::PositiveAssert,
|
||||||
},
|
},
|
||||||
|
Svg {
|
||||||
|
value: super::svg::Svg,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hash for BasicValue {
|
impl Hash for BasicValue {
|
||||||
@ -216,7 +220,8 @@ impl Hash for BasicValue {
|
|||||||
PositiveContext,
|
PositiveContext,
|
||||||
PositiveReactions,
|
PositiveReactions,
|
||||||
PositiveAssertFunction,
|
PositiveAssertFunction,
|
||||||
PositiveGroupFunction
|
PositiveGroupFunction,
|
||||||
|
Svg
|
||||||
);
|
);
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
@ -278,6 +283,7 @@ pub enum NodeInstruction {
|
|||||||
DisplayEdge,
|
DisplayEdge,
|
||||||
ColorNode,
|
ColorNode,
|
||||||
ColorEdge,
|
ColorEdge,
|
||||||
|
StringToSvg,
|
||||||
|
|
||||||
// convert basic data types
|
// convert basic data types
|
||||||
ToPositiveSet,
|
ToPositiveSet,
|
||||||
@ -486,6 +492,7 @@ impl NodeInstruction {
|
|||||||
("display edge", DisplayEdge),
|
("display edge", DisplayEdge),
|
||||||
],
|
],
|
||||||
| Self::Sleep => vec![("seconds", PositiveInt)],
|
| Self::Sleep => vec![("seconds", PositiveInt)],
|
||||||
|
| Self::StringToSvg => vec![("value", String)],
|
||||||
}
|
}
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|e| (e.0.to_string(), e.1))
|
.map(|e| (e.0.to_string(), e.1))
|
||||||
@ -581,6 +588,7 @@ impl NodeInstruction {
|
|||||||
vec![("out", String)],
|
vec![("out", String)],
|
||||||
| Self::PositiveBisimilarityPaigeTarjan => vec![("out", String)],
|
| Self::PositiveBisimilarityPaigeTarjan => vec![("out", String)],
|
||||||
| Self::Sleep => vec![("out", PositiveInt)],
|
| Self::Sleep => vec![("out", PositiveInt)],
|
||||||
|
| Self::StringToSvg => vec![("out", Svg)],
|
||||||
};
|
};
|
||||||
res.into_iter()
|
res.into_iter()
|
||||||
.map(|res| (res.0.to_string(), res.1))
|
.map(|res| (res.0.to_string(), res.1))
|
||||||
@ -684,6 +692,10 @@ impl NodeInstruction {
|
|||||||
PositiveGroupFunction,
|
PositiveGroupFunction,
|
||||||
assert::positive_grouping::PositiveAssert::default()
|
assert::positive_grouping::PositiveAssert::default()
|
||||||
),
|
),
|
||||||
|
| BasicDataType::Svg => helper!(
|
||||||
|
Svg,
|
||||||
|
super::svg::Svg::default()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -739,6 +751,8 @@ impl NodeInstruction {
|
|||||||
helper!(PositiveAssertFunction),
|
helper!(PositiveAssertFunction),
|
||||||
| BasicDataType::PositiveGroupFunction =>
|
| BasicDataType::PositiveGroupFunction =>
|
||||||
helper!(PositiveGroupFunction),
|
helper!(PositiveGroupFunction),
|
||||||
|
| BasicDataType::Svg =>
|
||||||
|
helper!(Svg),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -935,6 +949,8 @@ impl DataTypeTrait<GlobalState> for BasicDataType {
|
|||||||
egui::Color32::from_rgb(200, 150, 120),
|
egui::Color32::from_rgb(200, 150, 120),
|
||||||
| Self::PositiveGroupFunction =>
|
| Self::PositiveGroupFunction =>
|
||||||
egui::Color32::from_rgb(150, 120, 200),
|
egui::Color32::from_rgb(150, 120, 200),
|
||||||
|
| Self::Svg =>
|
||||||
|
egui::Color32::from_rgb(200, 200, 240),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -972,6 +988,7 @@ impl DataTypeTrait<GlobalState> for BasicDataType {
|
|||||||
Cow::Borrowed("positive assert function"),
|
Cow::Borrowed("positive assert function"),
|
||||||
| Self::PositiveGroupFunction =>
|
| Self::PositiveGroupFunction =>
|
||||||
Cow::Borrowed("positive group function"),
|
Cow::Borrowed("positive group function"),
|
||||||
|
| Self::Svg => Cow::Borrowed("Svg"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1069,6 +1086,7 @@ impl NodeTemplateTrait for NodeInstruction {
|
|||||||
| Self::PositiveBisimilarityPaigeTarjan =>
|
| Self::PositiveBisimilarityPaigeTarjan =>
|
||||||
"Positive Paige & Torjan",
|
"Positive Paige & Torjan",
|
||||||
| Self::Sleep => "Sleep",
|
| Self::Sleep => "Sleep",
|
||||||
|
| Self::StringToSvg => "String to SVG",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1150,7 +1168,8 @@ impl NodeTemplateTrait for NodeInstruction {
|
|||||||
| Self::PositiveBisimilarityPaigeTarjanNoLabels
|
| Self::PositiveBisimilarityPaigeTarjanNoLabels
|
||||||
| Self::PositiveBisimilarityPaigeTarjan =>
|
| Self::PositiveBisimilarityPaigeTarjan =>
|
||||||
vec!["Positive Graph", "Positive Bisimilarity"],
|
vec!["Positive Graph", "Positive Bisimilarity"],
|
||||||
| Self::Sleep => vec!["General"],
|
| Self::Sleep
|
||||||
|
| Self::StringToSvg => vec!["General"],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1256,6 +1275,7 @@ impl NodeTemplateIter for AllInstructions {
|
|||||||
NodeInstruction::PositiveBisimilarityPaigeTarjanNoLabels,
|
NodeInstruction::PositiveBisimilarityPaigeTarjanNoLabels,
|
||||||
NodeInstruction::PositiveBisimilarityPaigeTarjan,
|
NodeInstruction::PositiveBisimilarityPaigeTarjan,
|
||||||
NodeInstruction::Sleep,
|
NodeInstruction::Sleep,
|
||||||
|
NodeInstruction::StringToSvg,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1395,6 +1415,9 @@ impl WidgetValueTrait for BasicValue {
|
|||||||
| BasicValue::PositiveGroupFunction { value: _ } => {
|
| BasicValue::PositiveGroupFunction { value: _ } => {
|
||||||
ui.label(param_name);
|
ui.label(param_name);
|
||||||
},
|
},
|
||||||
|
| BasicValue::Svg { value: _ } => {
|
||||||
|
ui.label(param_name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
responses
|
responses
|
||||||
@ -1523,7 +1546,7 @@ pub struct AppHandle {
|
|||||||
|
|
||||||
translator: Arc<Mutex<rsprocess::translator::Translator>>,
|
translator: Arc<Mutex<rsprocess::translator::Translator>>,
|
||||||
|
|
||||||
cached_last_value: Option<LayoutJob>,
|
cached_last_value: Option<WidgetLayout>,
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
app_logic_thread: Option<JoinHandle<anyhow::Result<()>>>,
|
app_logic_thread: Option<JoinHandle<anyhow::Result<()>>>,
|
||||||
@ -1870,15 +1893,15 @@ impl eframe::App for AppHandle {
|
|||||||
user_state.display_result
|
user_state.display_result
|
||||||
};
|
};
|
||||||
if display_result {
|
if display_result {
|
||||||
let mut text = LayoutJob::default();
|
let mut content = WidgetLayout::default();
|
||||||
let mut spin = false;
|
let mut spin = false;
|
||||||
|
|
||||||
if let Some(l_v) = &self.cached_last_value {
|
if let Some(l_v) = &self.cached_last_value {
|
||||||
text = l_v.clone();
|
content = l_v.clone();
|
||||||
} else {
|
} else {
|
||||||
#[cfg(not(target_arch = "wasm32"))] {
|
#[cfg(not(target_arch = "wasm32"))] {
|
||||||
// wasm does not support threads :-(
|
// wasm does not support threads :-(
|
||||||
// -------------------------------------------------------------
|
// ---------------------------------------------------------
|
||||||
// did we already start a thread?
|
// did we already start a thread?
|
||||||
if self.app_logic_thread.is_none() {
|
if self.app_logic_thread.is_none() {
|
||||||
let thread_join_handle = {
|
let thread_join_handle = {
|
||||||
@ -1911,7 +1934,7 @@ impl eframe::App for AppHandle {
|
|||||||
|
|
||||||
if let Err(e) = err {
|
if let Err(e) = err {
|
||||||
let text = get_layout(Err(e), &self.translator.lock().unwrap(), ctx);
|
let text = get_layout(Err(e), &self.translator.lock().unwrap(), ctx);
|
||||||
self.cached_last_value = Some(text.clone());
|
self.cached_last_value = Some(text);
|
||||||
} else if let Some(l_b_v) = self.cache.get_last_state() {
|
} else if let Some(l_b_v) = self.cache.get_last_state() {
|
||||||
if let BasicValue::SaveString { path, value } = &l_b_v {
|
if let BasicValue::SaveString { path, value } = &l_b_v {
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
@ -1927,8 +1950,8 @@ impl eframe::App for AppHandle {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
text = get_layout(Ok(l_b_v), &self.translator.lock().unwrap(), ctx);
|
content = get_layout(Ok(l_b_v), &self.translator.lock().unwrap(), ctx);
|
||||||
self.cached_last_value = Some(text.clone());
|
self.cached_last_value = Some(content.clone());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
spin = true;
|
spin = true;
|
||||||
@ -1945,7 +1968,7 @@ impl eframe::App for AppHandle {
|
|||||||
);
|
);
|
||||||
if let Err(e) = err {
|
if let Err(e) = err {
|
||||||
let text = get_layout(Err(e), &self.translator.lock().unwrap(), ctx);
|
let text = get_layout(Err(e), &self.translator.lock().unwrap(), ctx);
|
||||||
self.cached_last_value = Some(text.clone());
|
self.cached_last_value = Some(content.clone());
|
||||||
} else if let Some(l_b_v) = self.cache.get_last_state() {
|
} else if let Some(l_b_v) = self.cache.get_last_state() {
|
||||||
if let BasicValue::SaveString { path, value } = &l_b_v {
|
if let BasicValue::SaveString { path, value } = &l_b_v {
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
@ -1961,8 +1984,8 @@ impl eframe::App for AppHandle {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
text = get_layout(Ok(l_b_v), &self.translator.lock().unwrap(), ctx);
|
content = get_layout(Ok(l_b_v), &self.translator.lock().unwrap(), ctx);
|
||||||
self.cached_last_value = Some(text.clone());
|
self.cached_last_value = Some(content.clone());
|
||||||
}
|
}
|
||||||
spin = false;
|
spin = false;
|
||||||
}
|
}
|
||||||
@ -1972,24 +1995,22 @@ impl eframe::App for AppHandle {
|
|||||||
|
|
||||||
if spin {
|
if spin {
|
||||||
window.show(ctx, |ui| {
|
window.show(ctx, |ui| {
|
||||||
egui::ScrollArea::vertical().show(ui, |ui| {
|
|
||||||
use egui::widgets::Widget;
|
use egui::widgets::Widget;
|
||||||
ui.vertical_centered(|ui| {
|
ui.vertical_centered(|ui| {
|
||||||
ui.heading("Result");
|
ui.heading("Result");
|
||||||
});
|
});
|
||||||
|
ui.separator();
|
||||||
|
ui.vertical_centered(|ui| {
|
||||||
egui::widgets::Spinner::new().ui(ui);
|
egui::widgets::Spinner::new().ui(ui);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
window.show(ctx, |ui| {
|
window.show(ctx, |ui| {
|
||||||
egui::ScrollArea::vertical().show(ui, |ui| {
|
|
||||||
ui.vertical_centered(|ui| {
|
ui.vertical_centered(|ui| {
|
||||||
ui.heading("Result");
|
ui.heading("Result");
|
||||||
});
|
});
|
||||||
egui::ScrollArea::vertical().show(ui, |ui| {
|
ui.separator();
|
||||||
ui.label(text);
|
ui.add(content);
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2044,11 +2065,40 @@ fn create_output(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
enum WidgetLayout {
|
||||||
|
LayoutJob(LayoutJob),
|
||||||
|
Image(egui::TextureHandle),
|
||||||
|
|
||||||
|
#[default]
|
||||||
|
Empty,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl egui::Widget for WidgetLayout {
|
||||||
|
fn ui(self, ui: &mut egui::Ui) -> egui::Response {
|
||||||
|
match self {
|
||||||
|
| Self::LayoutJob(lj) => {
|
||||||
|
let mut response = None;
|
||||||
|
egui::ScrollArea::vertical().auto_shrink([false, false]).show(ui, |ui| {
|
||||||
|
response = Some(egui::Label::new(lj).ui(ui));
|
||||||
|
});
|
||||||
|
response.unwrap()
|
||||||
|
},
|
||||||
|
| Self::Empty => {
|
||||||
|
egui::Label::new("").ui(ui)
|
||||||
|
},
|
||||||
|
| Self::Image(i) => {
|
||||||
|
egui::Image::new(&i).max_size(ui.available_size()).ui(ui)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn get_layout(
|
fn get_layout(
|
||||||
value: anyhow::Result<BasicValue>,
|
value: anyhow::Result<BasicValue>,
|
||||||
translator: &rsprocess::translator::Translator,
|
translator: &rsprocess::translator::Translator,
|
||||||
ctx: &egui::Context,
|
ctx: &egui::Context,
|
||||||
) -> LayoutJob {
|
) -> WidgetLayout {
|
||||||
let mut text = LayoutJob::default();
|
let mut text = LayoutJob::default();
|
||||||
|
|
||||||
match value {
|
match value {
|
||||||
@ -2231,6 +2281,9 @@ fn get_layout(
|
|||||||
0.,
|
0.,
|
||||||
Default::default(),
|
Default::default(),
|
||||||
),
|
),
|
||||||
|
| BasicValue::Svg { value } => {
|
||||||
|
return WidgetLayout::Image(value.get_texture(ctx));
|
||||||
|
},
|
||||||
},
|
},
|
||||||
| Err(err) => {
|
| Err(err) => {
|
||||||
text.append(&format!("{err:?}"), 0., TextFormat {
|
text.append(&format!("{err:?}"), 0., TextFormat {
|
||||||
@ -2239,5 +2292,5 @@ fn get_layout(
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
text
|
WidgetLayout::LayoutJob(text)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2492,7 +2492,27 @@ fn process_template(
|
|||||||
#[cfg(target_arch = "wasm32")] {
|
#[cfg(target_arch = "wasm32")] {
|
||||||
anyhow::bail!("Cannot sleep on wams");
|
anyhow::bail!("Cannot sleep on wams");
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
| NodeInstruction::StringToSvg => {
|
||||||
|
let s = retrieve_from_cache![1];
|
||||||
|
let hash_inputs = hash_inputs!(s);
|
||||||
|
|
||||||
|
if let BasicValue::String { value } = s {
|
||||||
|
let res = match super::svg::Svg::parse_dot_string(&value) {
|
||||||
|
Ok(svg) => svg,
|
||||||
|
Err(e) => anyhow::bail!(e),
|
||||||
|
};
|
||||||
|
|
||||||
|
let res = BasicValue::Svg { value: res };
|
||||||
|
set_cache_output!((
|
||||||
|
output_names.first().unwrap(),
|
||||||
|
res,
|
||||||
|
hash_inputs
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
anyhow::bail!("Not a string");
|
||||||
}
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
mod app;
|
mod app;
|
||||||
mod app_logic;
|
mod app_logic;
|
||||||
mod helper;
|
mod helper;
|
||||||
|
mod svg;
|
||||||
|
|
||||||
pub use app::AppHandle;
|
pub use app::AppHandle;
|
||||||
|
|
||||||
|
|||||||
112
reaction_systems_gui/src/svg.rs
Normal file
112
reaction_systems_gui/src/svg.rs
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
use std::{fmt::Debug, hash::Hash, sync::{Arc, Mutex}};
|
||||||
|
|
||||||
|
use layout::{backends::svg::SVGWriter, gv::{self, GraphBuilder}};
|
||||||
|
use eframe::egui;
|
||||||
|
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "persistence",
|
||||||
|
derive(serde::Serialize, serde::Deserialize)
|
||||||
|
)]
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
pub(crate) struct Svg {
|
||||||
|
image: egui::ColorImage,
|
||||||
|
/// original size of the svg
|
||||||
|
svg_size: egui::Vec2,
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "persistence", serde(skip))]
|
||||||
|
svg_texture: Arc<Mutex<Option<egui::TextureHandle>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Svg {
|
||||||
|
pub(crate) fn parse_dot_string(dot_str: &str) -> Result<Svg, String> {
|
||||||
|
let mut parser = gv::DotParser::new(dot_str);
|
||||||
|
let g = match parser.process() {
|
||||||
|
Ok(g) => g,
|
||||||
|
Err(_) =>
|
||||||
|
// errors are printed to sdtout so we ignore them
|
||||||
|
return Err("Could not parse dot string.".into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut gb = GraphBuilder::new();
|
||||||
|
gb.visit_graph(&g);
|
||||||
|
let mut graph = gb.get();
|
||||||
|
let mut svg = SVGWriter::new();
|
||||||
|
graph.do_it(
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
&mut svg,
|
||||||
|
);
|
||||||
|
let content = svg.finalize();
|
||||||
|
|
||||||
|
let svg = match nsvg::parse_str(&content, nsvg::Units::Pixel, 96.0) {
|
||||||
|
Ok(svg) => svg,
|
||||||
|
Err(nsvg_err) => return Err(format!("{}", nsvg_err)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let svg_size = egui::vec2(svg.width(), svg.height());
|
||||||
|
|
||||||
|
let (w, h, data) = match svg.rasterize_to_raw_rgba(1.) {
|
||||||
|
Ok(o) => o,
|
||||||
|
Err(e) => return Err(format!("{}", e)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let image = egui::ColorImage::from_rgba_unmultiplied([w as _, h as _], &data);
|
||||||
|
|
||||||
|
let svg = Svg { image, svg_size, svg_texture: Arc::new(Mutex::new(None)) };
|
||||||
|
|
||||||
|
Ok(svg)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_texture(&self, ctx: &egui::Context) -> egui::TextureHandle {
|
||||||
|
let tx = self.svg_texture.lock().expect("Poisoned");
|
||||||
|
if tx.is_some() {
|
||||||
|
(*tx).clone().unwrap()
|
||||||
|
} else {
|
||||||
|
std::mem::drop(tx);
|
||||||
|
let svg_texture = ctx.load_texture("svg", self.image.clone(), Default::default());
|
||||||
|
*self.svg_texture.lock().expect("Poisoned") = Some(svg_texture.clone());
|
||||||
|
svg_texture
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for Svg {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "[image: {:?}, svg_size: {:?}, svg_texture: {}",
|
||||||
|
self.image,
|
||||||
|
self.svg_size,
|
||||||
|
if self.svg_texture.lock().expect("Poisoned").is_some() {
|
||||||
|
"Some(...)"
|
||||||
|
} else {
|
||||||
|
"None"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hash for Svg {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
macro_rules! hash_float {
|
||||||
|
($name:expr) => (
|
||||||
|
let bits = if $name.is_nan() {
|
||||||
|
// "Canonical" NaN.
|
||||||
|
0x7fc00000
|
||||||
|
} else {
|
||||||
|
// A trick taken from the `ordered-float` crate: -0.0 + 0.0 == +0.0.
|
||||||
|
// https://github.com/reem/rust-ordered-float/blob/1841f0541ea0e56779cbac03de2705149e020675/src/lib.rs#L2178-L2181
|
||||||
|
($name + 0.0).to_bits()
|
||||||
|
};
|
||||||
|
bits.hash(state);
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
hash_float!(self.svg_size.x);
|
||||||
|
hash_float!(self.svg_size.y);
|
||||||
|
self.image.pixels.hash(state);
|
||||||
|
self.image.size.hash(state);
|
||||||
|
hash_float!(self.image.source_size.x);
|
||||||
|
hash_float!(self.image.source_size.y);
|
||||||
|
self.svg_texture.lock().expect("Poisoned").hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user