diff --git a/Cargo.toml b/Cargo.toml index 4034492..d765294 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,20 +1,5 @@ -[package] -name = "egui_node_graph" -description = "A helper library to create interactive node graphs using egui" -homepage = "https://github.com/setzer22/egui_node_graph" -repository = "https://github.com/setzer22/egui_node_graph" -license = "MIT" -version = "0.1.0" -keywords = ["ui", "egui", "graph", "node"] -authors = ["setzer22"] -edition = "2021" - -[features] -persistence = ["serde", "slotmap/serde", "smallvec/serde"] - -[dependencies] -egui = { version = "0.16" } -slotmap = { version = "1.0" } -smallvec = { version = "1.7.0" } -serde = { version = "1.0", optional = true, features = ["derive"] } -thiserror = "1.0" +[workspace] +members = [ + "egui_node_graph", + "egui_node_graph_example", +] diff --git a/egui_node_graph/Cargo.toml b/egui_node_graph/Cargo.toml new file mode 100644 index 0000000..e93bcc6 --- /dev/null +++ b/egui_node_graph/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "egui_node_graph" +description = "A helper library to create interactive node graphs using egui" +homepage = "https://github.com/setzer22/egui_node_graph" +repository = "https://github.com/setzer22/egui_node_graph" +license = "MIT" +version = "0.1.0" +keywords = ["ui", "egui", "graph", "node"] +authors = ["setzer22"] +edition = "2021" +readme = "../README.md" +workspace = ".." + +[features] +persistence = ["serde", "slotmap/serde", "smallvec/serde"] + +[dependencies] +egui = { version = "0.16" } +slotmap = { version = "1.0" } +smallvec = { version = "1.7.0" } +serde = { version = "1.0", optional = true, features = ["derive"] } +thiserror = "1.0" diff --git a/egui_node_graph/src/color_hex_utils.rs b/egui_node_graph/src/color_hex_utils.rs new file mode 100644 index 0000000..e846f0a --- /dev/null +++ b/egui_node_graph/src/color_hex_utils.rs @@ -0,0 +1,94 @@ +use egui::Color32; + +/// Converts a hex string with a leading '#' into a egui::Color32. +/// - The first three channels are interpreted as R, G, B. +/// - The fourth channel, if present, is used as the alpha value. +/// - Both upper and lowercase characters can be used for the hex values. +/// +/// *Adapted from: https://docs.rs/raster/0.1.0/src/raster/lib.rs.html#425-725. +/// Credit goes to original authors.* +pub fn color_from_hex(hex: &str) -> Result { + // Convert a hex string to decimal. Eg. "00" -> 0. "FF" -> 255. + fn _hex_dec(hex_string: &str) -> Result { + match u8::from_str_radix(hex_string, 16) { + Ok(o) => Ok(o), + Err(e) => Err(format!("Error parsing hex: {}", e)), + } + } + + if hex.len() == 9 && hex.starts_with('#') { + // #FFFFFFFF (Red Green Blue Alpha) + return Ok(Color32::from_rgba_premultiplied( + _hex_dec(&hex[1..3])?, + _hex_dec(&hex[3..5])?, + _hex_dec(&hex[5..7])?, + _hex_dec(&hex[7..9])?, + )); + } else if hex.len() == 7 && hex.starts_with('#') { + // #FFFFFF (Red Green Blue) + return Ok(Color32::from_rgb( + _hex_dec(&hex[1..3])?, + _hex_dec(&hex[3..5])?, + _hex_dec(&hex[5..7])?, + )); + } + + Err(format!( + "Error parsing hex: {}. Example of valid formats: #FFFFFF or #ffffffff", + hex + )) +} + +/// Converts a Color32 into its canonical hexadecimal representation. +/// - The color string will be preceded by '#'. +/// - If the alpha channel is completely opaque, it will be ommitted. +/// - Characters from 'a' to 'f' will be written in lowercase. +#[allow(dead_code)] +pub fn color_to_hex(color: Color32) -> String { + if color.a() < 255 { + format!( + "#{:02x?}{:02x?}{:02x?}{:02x?}", + color.r(), + color.g(), + color.b(), + color.a() + ) + } else { + format!("#{:02x?}{:02x?}{:02x?}", color.r(), color.g(), color.b()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + pub fn test_color_from_and_to_hex() { + assert_eq!( + color_from_hex("#00ff00").unwrap(), + Color32::from_rgb(0, 255, 0) + ); + assert_eq!( + color_from_hex("#5577AA").unwrap(), + Color32::from_rgb(85, 119, 170) + ); + assert_eq!( + color_from_hex("#E2e2e277").unwrap(), + Color32::from_rgba_premultiplied(226, 226, 226, 119) + ); + assert!(color_from_hex("abcdefgh").is_err()); + + assert_eq!( + color_to_hex(Color32::from_rgb(0, 255, 0)), + "#00ff00".to_string() + ); + assert_eq!( + color_to_hex(Color32::from_rgb(85, 119, 170)), + "#5577aa".to_string() + ); + assert_eq!( + color_to_hex(Color32::from_rgba_premultiplied(226, 226, 226, 119)), + "e2e2e277".to_string() + ); + } +} diff --git a/egui_node_graph/src/editor_ui.rs b/egui_node_graph/src/editor_ui.rs new file mode 100644 index 0000000..d36ac29 --- /dev/null +++ b/egui_node_graph/src/editor_ui.rs @@ -0,0 +1,600 @@ +use std::collections::HashSet; + +use crate::color_hex_utils::*; +use crate::utils::ColorUtils; + +use super::*; +use egui::epaint::RectShape; +use egui::*; + +pub type PortLocations = std::collections::HashMap; + +pub enum DrawGraphNodeResponse { + ConnectEventStarted(NodeId, AnyParameterId), + ConnectEventEnded(AnyParameterId), + SetActiveNode(NodeId), + SelectNode(NodeId), + RunNodeSideEffect(NodeId), + ClearActiveNode, + DeleteNode(NodeId), + DisconnectEvent(InputId), + /// Emitted when a node is interacted with, and should be raised + RaiseNode(NodeId), +} +pub struct GraphNodeWidget<'a, NodeData, DataType, ValueType> { + pub position: &'a mut Pos2, + pub graph: &'a mut Graph, + pub port_locations: &'a mut PortLocations, + pub node_id: NodeId, + pub ongoing_drag: Option<(NodeId, AnyParameterId)>, + pub active: bool, + pub selected: bool, + pub pan: egui::Vec2, +} + +pub trait InputParamWidget { + fn value_widget(&mut self, param_name: &str, ui: &mut Ui); +} + +pub trait DataTypeTrait: PartialEq + Eq { + // The associated port color of this datatype + fn data_type_color(&self) -> egui::Color32; + + // The name of this datatype + fn name(&self) -> &str; +} + +impl + GraphEditorState +where + ValueType: InputParamWidget, + NodeKind: NodeKindTrait, + DataType: DataTypeTrait, +{ + pub fn draw_graph_editor( + &mut self, + ctx: &CtxRef, + all_kinds: impl NodeKindIter, + ) { + let mouse = &ctx.input().pointer; + let cursor_pos = mouse.hover_pos().unwrap_or(Pos2::ZERO); + + // Gets filled with the port locations as nodes are drawn + let mut port_locations = PortLocations::new(); + + // The responses returned from node drawing have side effects that are best + // executed at the end of this function. + let mut delayed_responses: Vec = vec![]; + + // Used to detect when the background was clicked, to dismiss certain selfs + let mut click_on_background = false; + + debug_assert_eq!( + self.node_order.iter().copied().collect::>(), + self.graph.iter_nodes().collect::>(), + "The node_order field of the GraphEditorself was left in an \ + inconsistent self. It has either more or less values than the graph." + ); + + CentralPanel::default().show(ctx, |ui| { + /* Draw nodes */ + for node_id in self.node_order.iter().copied() { + let responses = GraphNodeWidget { + position: self.node_positions.get_mut(node_id).unwrap(), + graph: &mut self.graph, + port_locations: &mut port_locations, + node_id, + ongoing_drag: self.connection_in_progress, + active: self + .active_node + .map(|active| active == node_id) + .unwrap_or(false), + selected: self + .selected_node + .map(|selected| selected == node_id) + .unwrap_or(false), + pan: self.pan_zoom.pan, + } + .show(ui); + + // Actions executed later + delayed_responses.extend(responses); + } + + let r = ui.allocate_rect(ui.min_rect(), Sense::click()); + if r.clicked() { + click_on_background = true; + } + }); + + /* Draw the node finder, if open */ + let mut should_close_node_finder = false; + if let Some(ref mut node_finder) = self.node_finder { + let mut node_finder_area = Area::new("node_finder"); + if let Some(pos) = node_finder.position { + node_finder_area = node_finder_area.current_pos(pos); + } + node_finder_area.show(ctx, |ui| { + if let Some(node_kind) = node_finder.show(ui, all_kinds) { + let new_node = self.graph.add_node( + node_kind.node_graph_label(), + node_kind.user_data(), + |graph, node_id| node_kind.build_node(graph, node_id), + ); + self.node_positions + .insert(new_node, cursor_pos - self.pan_zoom.pan); + self.node_order.push(new_node); + should_close_node_finder = true; + } + }); + } + if should_close_node_finder { + self.node_finder = None; + } + + /* Draw connections */ + let connection_stroke = egui::Stroke { + width: 5.0, + color: color_from_hex("#efefef").unwrap(), + }; + + if let Some((_, ref locator)) = self.connection_in_progress { + let painter = ctx.layer_painter(LayerId::background()); + let start_pos = port_locations[locator]; + painter.line_segment([start_pos, cursor_pos], connection_stroke) + } + + for (input, output) in self.graph.iter_connections() { + let painter = ctx.layer_painter(LayerId::background()); + let src_pos = port_locations[&AnyParameterId::Output(output)]; + let dst_pos = port_locations[&AnyParameterId::Input(input)]; + painter.line_segment([src_pos, dst_pos], connection_stroke); + } + + /* Handle responses from drawing nodes */ + + for response in delayed_responses { + match response { + DrawGraphNodeResponse::ConnectEventStarted(node_id, port) => { + self.connection_in_progress = Some((node_id, port)); + } + DrawGraphNodeResponse::ConnectEventEnded(locator) => { + let in_out = match ( + self.connection_in_progress + .map(|(_node, param)| param) + .take() + .expect("Cannot end drag without in-progress connection."), + locator, + ) { + (AnyParameterId::Input(input), AnyParameterId::Output(output)) + | (AnyParameterId::Output(output), AnyParameterId::Input(input)) => { + Some((input, output)) + } + _ => None, + }; + + if let Some((input, output)) = in_out { + self.graph.add_connection(output, input) + } + } + DrawGraphNodeResponse::SetActiveNode(node_id) => { + self.active_node = Some(node_id); + } + DrawGraphNodeResponse::SelectNode(node_id) => { + self.selected_node = Some(node_id); + } + DrawGraphNodeResponse::ClearActiveNode => { + self.active_node = None; + } + DrawGraphNodeResponse::RunNodeSideEffect(node_id) => { + self.run_side_effect = Some(node_id); + } + DrawGraphNodeResponse::DeleteNode(node_id) => { + self.graph.remove_node(node_id); + self.node_positions.remove(node_id); + // Make sure to not leave references to old nodes hanging + if self.active_node.map(|x| x == node_id).unwrap_or(false) { + self.active_node = None; + } + if self.selected_node.map(|x| x == node_id).unwrap_or(false) { + self.selected_node = None; + } + if self.run_side_effect.map(|x| x == node_id).unwrap_or(false) { + self.run_side_effect = None; + } + self.node_order.retain(|id| *id != node_id); + } + DrawGraphNodeResponse::DisconnectEvent(input_id) => { + let corresp_output = self + .graph + .connection(input_id) + .expect("Connection data should be valid"); + let other_node = self.graph.get_input(input_id).node(); + self.graph.remove_connection(input_id); + self.connection_in_progress = + Some((other_node, AnyParameterId::Output(corresp_output))); + } + DrawGraphNodeResponse::RaiseNode(node_id) => { + let old_pos = self + .node_order + .iter() + .position(|id| *id == node_id) + .expect("Node to be raised should be in `node_order`"); + self.node_order.remove(old_pos); + self.node_order.push(node_id); + } + } + } + + /* Mouse input handling */ + + if mouse.any_released() && self.connection_in_progress.is_some() { + self.connection_in_progress = None; + } + + if mouse.button_down(PointerButton::Secondary) { + self.node_finder = Some(NodeFinder::new_at(cursor_pos)); + } + if ctx.input().key_pressed(Key::Escape) { + self.node_finder = None; + } + + if ctx.input().pointer.middle_down() { + self.pan_zoom.pan += ctx.input().pointer.delta(); + } + + if click_on_background { + self.selected_node = None; + self.node_finder = None; + } + } +} + +impl<'a, NodeData, DataType, ValueType> GraphNodeWidget<'a, NodeData, DataType, ValueType> +where + ValueType: InputParamWidget, + DataType: DataTypeTrait, +{ + pub const MAX_NODE_SIZE: [f32; 2] = [200.0, 200.0]; + + pub fn show(self, ui: &mut Ui) -> Vec { + let mut child_ui = ui.child_ui_with_id_source( + Rect::from_min_size(*self.position + self.pan, Self::MAX_NODE_SIZE.into()), + Layout::default(), + self.node_id, + ); + + Self::show_graph_node(self, &mut child_ui) + } + + /// Draws this node. Also fills in the list of port locations with all of its ports. + /// Returns a response showing whether a drag event was started. + /// Parameters: + /// - **ongoing_drag**: Is there a port drag event currently going on? + fn show_graph_node(self, ui: &mut Ui) -> Vec { + let margin = egui::vec2(15.0, 5.0); + let _field_separation = 5.0; + let mut responses = Vec::::new(); + + let background_color = color_from_hex("#3f3f3f").unwrap(); + let titlebar_color = background_color.lighten(0.8); + let text_color = color_from_hex("#fefefe").unwrap(); + + ui.visuals_mut().widgets.noninteractive.fg_stroke = Stroke::new(2.0, text_color); + + // Preallocate shapes to paint below contents + let outline_shape = ui.painter().add(Shape::Noop); + let background_shape = ui.painter().add(Shape::Noop); + + let outer_rect_bounds = ui.available_rect_before_wrap(); + let mut inner_rect = outer_rect_bounds.shrink2(margin); + + // Make sure we don't shrink to the negative: + inner_rect.max.x = inner_rect.max.x.max(inner_rect.min.x); + inner_rect.max.y = inner_rect.max.y.max(inner_rect.min.y); + + let mut child_ui = ui.child_ui(inner_rect, *ui.layout()); + let mut title_height = 0.0; + + let mut input_port_heights = vec![]; + let mut output_port_heights = vec![]; + + child_ui.vertical(|ui| { + ui.horizontal(|ui| { + ui.add(Label::new( + RichText::new(&self.graph[self.node_id].label) + .text_style(TextStyle::Button) + .color(color_from_hex("#fefefe").unwrap()), + )); + }); + ui.add_space(margin.y); + title_height = ui.min_size().y; + + // First pass: Draw the inner fields. Compute port heights + let inputs = self.graph[self.node_id].inputs.clone(); + for (param_name, param_id) in inputs { + if self.graph[param_id].shown_inline { + let height_before = ui.min_rect().bottom(); + if self.graph.connection(param_id).is_some() { + ui.label(param_name); + } else { + self.graph[param_id].value.value_widget(¶m_name, ui); + } + let height_after = ui.min_rect().bottom(); + input_port_heights.push((height_before + height_after) / 2.0); + } + } + + let outputs = self.graph[self.node_id].outputs.clone(); + for (param_name, _param) in outputs { + let height_before = ui.min_rect().bottom(); + ui.label(¶m_name); + let height_after = ui.min_rect().bottom(); + output_port_heights.push((height_before + height_after) / 2.0); + } + + /* TODO: Restore button row + // Button row + ui.horizontal(|ui| { + // Show 'Enable' button for nodes that output a mesh + if self.graph[self.node_id].can_be_enabled(self.graph) { + ui.horizontal(|ui| { + if !self.active { + if ui.button("šŸ‘ Set active").clicked() { + responses.push(DrawGraphNodeResponse::SetActiveNode(self.node_id)); + } + } else { + let button = egui::Button::new( + RichText::new("šŸ‘ Active").color(egui::Color32::BLACK), + ) + .fill(egui::Color32::GOLD); + if ui.add(button).clicked() { + responses.push(DrawGraphNodeResponse::ClearActiveNode); + } + } + }); + } + // Show 'Run' button for executable nodes + if self.graph[self.node_id].is_executable() && ui.button("ā›­ Run").clicked() { + responses.push(DrawGraphNodeResponse::RunNodeSideEffect(self.node_id)); + } + }) + */ + }); + + // Second pass, iterate again to draw the ports. This happens outside + // the child_ui because we want ports to overflow the node background. + + let outer_rect = child_ui.min_rect().expand2(margin); + let port_left = outer_rect.left(); + let port_right = outer_rect.right(); + + #[allow(clippy::too_many_arguments)] + fn draw_port( + ui: &mut Ui, + graph: &Graph, + node_id: NodeId, + port_pos: Pos2, + responses: &mut Vec, + param_id: AnyParameterId, + port_locations: &mut PortLocations, + ongoing_drag: Option<(NodeId, AnyParameterId)>, + is_connected_input: bool, + ) where + DataType: DataTypeTrait, + { + let port_type = graph.any_param_type(param_id).unwrap(); + + let port_rect = Rect::from_center_size(port_pos, egui::vec2(10.0, 10.0)); + + let sense = if ongoing_drag.is_some() { + Sense::hover() + } else { + Sense::click_and_drag() + }; + + let resp = ui.allocate_rect(port_rect, sense); + let port_color = if resp.hovered() { + Color32::WHITE + } else { + port_type.data_type_color() + }; + ui.painter() + .circle(port_rect.center(), 5.0, port_color, Stroke::none()); + + if resp.drag_started() { + if is_connected_input { + responses.push(DrawGraphNodeResponse::DisconnectEvent( + param_id.assume_input(), + )); + } else { + responses.push(DrawGraphNodeResponse::ConnectEventStarted( + node_id, param_id, + )); + } + } + + if let Some((origin_node, origin_param)) = ongoing_drag { + if origin_node != node_id { + // Don't allow self-loops + if graph.any_param_type(origin_param).unwrap() == port_type + && resp.hovered() + && ui.input().pointer.any_released() + { + responses.push(DrawGraphNodeResponse::ConnectEventEnded(param_id)); + } + } + } + + port_locations.insert(param_id, port_rect.center()); + } + + // Input ports + for ((_, param), port_height) in self.graph[self.node_id] + .inputs + .iter() + .zip(input_port_heights.into_iter()) + { + let should_draw = match self.graph[*param].kind() { + InputParamKind::ConnectionOnly => true, + InputParamKind::ConstantOnly => false, + InputParamKind::ConnectionOrConstant => true, + }; + + if should_draw { + let pos_left = pos2(port_left, port_height); + draw_port( + ui, + self.graph, + self.node_id, + pos_left, + &mut responses, + AnyParameterId::Input(*param), + self.port_locations, + self.ongoing_drag, + self.graph.connection(*param).is_some(), + ); + } + } + + // Output ports + for ((_, param), port_height) in self.graph[self.node_id] + .outputs + .iter() + .zip(output_port_heights.into_iter()) + { + let pos_right = pos2(port_right, port_height); + draw_port( + ui, + self.graph, + self.node_id, + pos_right, + &mut responses, + AnyParameterId::Output(*param), + self.port_locations, + self.ongoing_drag, + false, + ); + } + + // Draw the background shape. + // NOTE: This code is a bit more involve than it needs to be because egui + // does not support drawing rectangles with asymmetrical round corners. + + let (shape, outline) = { + let corner_radius = 4.0; + + let titlebar_height = title_height + margin.y; + let titlebar_rect = + Rect::from_min_size(outer_rect.min, vec2(outer_rect.width(), titlebar_height)); + let titlebar = Shape::Rect(RectShape { + rect: titlebar_rect, + corner_radius, + fill: titlebar_color, + stroke: Stroke::none(), + }); + + let body_rect = Rect::from_min_size( + outer_rect.min + vec2(0.0, titlebar_height - corner_radius), + vec2(outer_rect.width(), outer_rect.height() - titlebar_height), + ); + let body = Shape::Rect(RectShape { + rect: body_rect, + corner_radius: 0.0, + fill: background_color, + stroke: Stroke::none(), + }); + + let bottom_body_rect = Rect::from_min_size( + body_rect.min + vec2(0.0, body_rect.height() - titlebar_height * 0.5), + vec2(outer_rect.width(), titlebar_height), + ); + let bottom_body = Shape::Rect(RectShape { + rect: bottom_body_rect, + corner_radius, + fill: background_color, + stroke: Stroke::none(), + }); + + let outline = if self.selected { + Shape::Rect(RectShape { + rect: titlebar_rect + .union(body_rect) + .union(bottom_body_rect) + .expand(1.0), + corner_radius: 4.0, + fill: Color32::WHITE.lighten(0.8), + stroke: Stroke::none(), + }) + } else { + Shape::Noop + }; + + (Shape::Vec(vec![titlebar, body, bottom_body]), outline) + }; + + ui.painter().set(background_shape, shape); + ui.painter().set(outline_shape, outline); + + // --- Interaction --- + + // Titlebar buttons + if Self::close_button(ui, outer_rect).clicked() { + responses.push(DrawGraphNodeResponse::DeleteNode(self.node_id)); + }; + + let window_response = ui.interact( + outer_rect, + Id::new((self.node_id, "window")), + Sense::click_and_drag(), + ); + + // Movement + *self.position += window_response.drag_delta(); + if window_response.drag_delta().length_sq() > 0.0 { + responses.push(DrawGraphNodeResponse::RaiseNode(self.node_id)); + } + + // Node selection + // + // HACK: Only set the select response when no other response is active. + // This prevents some issues. + if responses.is_empty() && window_response.clicked_by(PointerButton::Primary) { + responses.push(DrawGraphNodeResponse::SelectNode(self.node_id)); + responses.push(DrawGraphNodeResponse::RaiseNode(self.node_id)); + } + + responses + } + + fn close_button(ui: &mut Ui, node_rect: Rect) -> Response { + // Measurements + let margin = 8.0; + let size = 10.0; + let stroke_width = 2.0; + let offs = margin + size / 2.0; + + let position = pos2(node_rect.right() - offs, node_rect.top() + offs); + let rect = Rect::from_center_size(position, vec2(size, size)); + let resp = ui.allocate_rect(rect, Sense::click()); + + let color = if resp.clicked() { + color_from_hex("#ffffff").unwrap() + } else if resp.hovered() { + color_from_hex("#dddddd").unwrap() + } else { + color_from_hex("#aaaaaa").unwrap() + }; + let stroke = Stroke { + width: stroke_width, + color, + }; + + ui.painter() + .line_segment([rect.left_top(), rect.right_bottom()], stroke); + ui.painter() + .line_segment([rect.right_top(), rect.left_bottom()], stroke); + + resp + } +} diff --git a/src/error.rs b/egui_node_graph/src/error.rs similarity index 100% rename from src/error.rs rename to egui_node_graph/src/error.rs diff --git a/src/graph_impls.rs b/egui_node_graph/src/graph_impls.rs similarity index 76% rename from src/graph_impls.rs rename to egui_node_graph/src/graph_impls.rs index d16b978..667fae5 100644 --- a/src/graph_impls.rs +++ b/egui_node_graph/src/graph_impls.rs @@ -1,3 +1,5 @@ +use egui::plot::Value; + use super::*; impl Graph { @@ -14,7 +16,7 @@ impl Graph { &mut self, label: String, user_data: NodeData, - f: impl FnOnce(NodeId, &mut Node), + f: impl FnOnce(&mut Graph, NodeId), ) -> NodeId { let node_id = self.nodes.insert_with_key(|node_id| { Node { @@ -27,11 +29,42 @@ impl Graph { } }); - f(node_id, self.nodes.get_mut(node_id).unwrap()); + f(self, node_id); node_id } + pub fn add_input_param( + &mut self, + node_id: NodeId, + name: String, + typ: DataType, + value: ValueType, + kind: InputParamKind, + shown_inline: bool, + ) -> InputId { + let input_id = self.inputs.insert_with_key(|input_id| InputParam { + id: input_id, + typ, + value, + kind, + node: node_id, + shown_inline, + }); + self.nodes[node_id].inputs.push((name, input_id)); + input_id + } + + pub fn add_output_param(&mut self, node_id: NodeId, name: String, typ: DataType) -> OutputId { + let output_id = self.outputs.insert_with_key(|output_id| OutputParam { + id: output_id, + node: node_id, + typ, + }); + self.nodes[node_id].outputs.push((name, output_id)); + output_id + } + pub fn remove_node(&mut self, node_id: NodeId) { self.connections .retain(|i, o| !(self.outputs[*o].node == node_id || self.inputs[i].node == node_id)); @@ -127,4 +160,18 @@ impl Node { .map(|x| x.1) .ok_or_else(|| EguiGraphError::NoParameterNamed(self.id, name.into())) } -} \ No newline at end of file +} + +impl InputParam { + pub fn value(&self) -> &ValueType { + &self.value + } + + pub fn kind(&self) -> InputParamKind { + self.kind + } + + pub fn node(&self) -> NodeId { + self.node + } +} diff --git a/src/id_type.rs b/egui_node_graph/src/id_type.rs similarity index 100% rename from src/id_type.rs rename to egui_node_graph/src/id_type.rs diff --git a/src/index_impls.rs b/egui_node_graph/src/index_impls.rs similarity index 100% rename from src/index_impls.rs rename to egui_node_graph/src/index_impls.rs diff --git a/src/lib.rs b/egui_node_graph/src/lib.rs similarity index 97% rename from src/lib.rs rename to egui_node_graph/src/lib.rs index 537774f..3cfb7e8 100644 --- a/src/lib.rs +++ b/egui_node_graph/src/lib.rs @@ -16,6 +16,13 @@ pub use ui_state::*; pub mod node_finder; pub use node_finder::*; +pub mod editor_ui; +pub use editor_ui::*; + +mod utils; + +mod color_hex_utils; + #[cfg(feature = "persistence")] use serde::{Deserialize, Serialize}; diff --git a/src/node_finder.rs b/egui_node_graph/src/node_finder.rs similarity index 53% rename from src/node_finder.rs rename to egui_node_graph/src/node_finder.rs index d116a8c..d6fb35f 100644 --- a/src/node_finder.rs +++ b/egui_node_graph/src/node_finder.rs @@ -1,9 +1,9 @@ use std::marker::PhantomData; -use super::*; +use crate::{color_hex_utils::*, Graph, Node, NodeId}; + use egui::*; -#[derive(Default)] pub struct NodeFinder { query: String, /// Reset every frame. When set, the node finder will be moved at that position @@ -14,25 +14,55 @@ pub struct NodeFinder { pub trait NodeKindIter { type Item; - fn all_kinds<'a>() -> Box>; + fn all_kinds(&self) -> Box + '_>; } -impl NodeFinder { +pub trait NodeKindTrait: Clone { + type NodeData; + type DataType; + type ValueType; + + /// Returns a descriptive name for the node kind, used in the node finder. + fn node_finder_label(&self) -> &str; + + /// Returns a descriptive name for the node kind, used in the graph. + fn node_graph_label(&self) -> String; + + /// Returns the user data for this node kind. + fn user_data(&self) -> Self::NodeData; + + /// This function is run when this node kind gets added to the graph. The + /// node will be empty by default, and this function can be used to fill its + /// parameters. + fn build_node( + &self, + graph: &mut Graph, + node_id: NodeId, + ); +} + +impl NodeFinder +where + NodeKind: NodeKindTrait, +{ pub fn new_at(pos: Pos2) -> Self { NodeFinder { + query: "".into(), position: Some(pos), just_spawned: true, _phantom: Default::default(), - ..Default::default() } } /// Shows the node selector panel with a search bar. Returns whether a node /// archetype was selected and, in that case, the finder should be hidden on /// the next frame. - pub fn show(&mut self, ui: &mut Ui, all_kinds: impl NodeKindIter) -> Option { + pub fn show( + &mut self, + ui: &mut Ui, + all_kinds: impl NodeKindIter, + ) -> Option { let background_color = color_from_hex("#3f3f3f").unwrap(); - let _titlebar_color = background_color.linear_multiply(0.8); let text_color = color_from_hex("#fefefe").unwrap(); ui.visuals_mut().widgets.noninteractive.fg_stroke = Stroke::new(2.0, text_color); @@ -54,15 +84,15 @@ impl NodeFinder { let mut query_submit = resp.lost_focus() && ui.input().key_down(Key::Enter); Frame::default().margin(vec2(10.0, 10.0)).show(ui, |ui| { - for archetype in all_kinds.all_kinds() { - let archetype_name = archetype.type_label(); - if archetype_name.contains(self.query.as_str()) { + for kind in all_kinds.all_kinds() { + let kind_name = kind.node_finder_label(); + if kind_name.contains(self.query.as_str()) { if query_submit { - submitted_archetype = Some(archetype); + submitted_archetype = Some(kind); query_submit = false; } - if ui.selectable_label(false, archetype_name).clicked() { - submitted_archetype = Some(archetype); + if ui.selectable_label(false, kind_name).clicked() { + submitted_archetype = Some(kind); } } } @@ -70,6 +100,6 @@ impl NodeFinder { }); }); - submitted_archetype + submitted_archetype.cloned() } } diff --git a/src/ui_state.rs b/egui_node_graph/src/ui_state.rs similarity index 89% rename from src/ui_state.rs rename to egui_node_graph/src/ui_state.rs index dcd7f89..e6fe8b6 100644 --- a/src/ui_state.rs +++ b/egui_node_graph/src/ui_state.rs @@ -1,7 +1,7 @@ use super::*; #[cfg(feature = "persistence")] -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; #[derive(Copy, Clone)] #[cfg_attr(feature = "persistence", derive(Serialize, Deserialize))] @@ -10,7 +10,7 @@ pub struct PanZoom { pub zoom: f32, } -pub struct GraphEditorState { +pub struct GraphEditorState { pub graph: Graph, /// Nodes are drawn in this order. Draw order is important because nodes /// that are drawn last are on top. @@ -27,7 +27,7 @@ pub struct GraphEditorState { /// The position of each node. pub node_positions: SecondaryMap, /// The node finder is used to create new nodes. - pub node_finder: Option, + pub node_finder: Option>, /// When this option is set by the UI, the side effect encoded by the node /// will be executed at the start of the next frame. pub run_side_effect: Option, @@ -35,7 +35,9 @@ pub struct GraphEditorState { pub pan_zoom: PanZoom, } -impl GraphEditorState { +impl + GraphEditorState +{ pub fn new(default_zoom: f32) -> Self { Self { graph: Graph::new(), diff --git a/egui_node_graph/src/utils.rs b/egui_node_graph/src/utils.rs new file mode 100644 index 0000000..cfa6588 --- /dev/null +++ b/egui_node_graph/src/utils.rs @@ -0,0 +1,15 @@ +pub trait ColorUtils { + /// Multiplies the color rgb values by `factor`, keeping alpha untouched. + fn lighten(&self, factor: f32) -> Self; +} + +impl ColorUtils for egui::Color32 { + fn lighten(&self, factor: f32) -> Self { + egui::Color32::from_rgba_premultiplied( + (self.r() as f32 * factor) as u8, + (self.g() as f32 * factor) as u8, + (self.b() as f32 * factor) as u8, + self.a(), + ) + } +} \ No newline at end of file diff --git a/egui_node_graph_example/.gitignore b/egui_node_graph_example/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/egui_node_graph_example/.gitignore @@ -0,0 +1 @@ +/target diff --git a/egui_node_graph_example/Cargo.toml b/egui_node_graph_example/Cargo.toml new file mode 100644 index 0000000..a8c88e3 --- /dev/null +++ b/egui_node_graph_example/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "egui_node_graph_example" +version = "0.1.0" +authors = ["setzer22"] +edition = "2021" +rust-version = "1.56" + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +eframe = "0.16.0" +egui_node_graph = { path = "../egui_node_graph" } + +[features] +default = [] + +[profile.release] +opt-level = 2 # fast and small wasm diff --git a/egui_node_graph_example/build_web.bat b/egui_node_graph_example/build_web.bat new file mode 100644 index 0000000..897f067 --- /dev/null +++ b/egui_node_graph_example/build_web.bat @@ -0,0 +1,78 @@ +@echo off + +SET script_path=%~dp0 +cd %script_path% + +SET OPEN=0 +SET FAST=0 + +:do_while + IF (%1) == () GOTO end_while + + IF %1 == -h GOTO print_help + IF %1 == --help GOTO print_help + + IF %1 == --fast ( + SET FAST=1 + SHIFT + GOTO do_while + ) + + IF %1 == --open ( + SET OPEN=1 + SHIFT + GOTO do_while + ) + + echo Unknown command "%1" +:end_while + +@REM call this first : `./setup_web.bat` + +for %%I in (.) do SET FOLDER_NAME=%%~nxI + +@REM assume crate name is the same as the folder name +SET CRATE_NAME=%FOLDER_NAME% + +@REM for those who name crates with-kebab-case +SET CRATE_NAME_SNAKE_CASE=%FOLDER_NAME:-=_% + +@REM This is required to enable the web_sys clipboard API which egui_web uses +@REM https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Clipboard.html +@REM https://rustwasm.github.io/docs/wasm-bindgen/web-sys/unstable-apis.html +SET RUSTFLAGS=--cfg=web_sys_unstable_apis + +@REM Clear output from old stuff: +DEL /F docs\%CRATE_NAME_SNAKE_CASE%_bg.wasm + +echo Building rust... +SET BUILD=release +cargo build -p %CRATE_NAME% --release --lib --target wasm32-unknown-unknown + +@REM Get the output directory (in the workspace it is in another location) +FOR /F %%i IN ('cargo metadata --format-version=1 ^| jq --raw-output .target_directory') DO SET TARGET=%%i + +echo Generating JS bindings for wasm... +SET TARGET_NAME=%CRATE_NAME_SNAKE_CASE%.wasm +wasm-bindgen "%TARGET%\wasm32-unknown-unknown\%BUILD%\%TARGET_NAME%" --out-dir "docs" --no-modules --no-typescript + +IF %FAST% == 0 ( + echo Optimizing wasm... + @REM to get wasm-opt: apt/brew/dnf install binaryen + @REM add -g to get debug symbols : + wasm-opt "docs\%CRATE_NAME%_bg.wasm" -O2 --fast-math -o "docs\%CRATE_NAME%_bg.wasm" +) + +echo Finished: docs/%CRATE_NAME_SNAKE_CASE%.wasm" + +IF %OPEN% == 1 start http://localhost:8080/index.html + +GOTO end_program + +:print_help +echo build_web.sh [--fast] [--open] +echo --fast: skip optimization step +echo --open: open the result in a browser +GOTO end_program + +:end_program diff --git a/egui_node_graph_example/build_web.sh b/egui_node_graph_example/build_web.sh new file mode 100755 index 0000000..0de31a3 --- /dev/null +++ b/egui_node_graph_example/build_web.sh @@ -0,0 +1,76 @@ +#!/bin/bash +set -eu +script_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) +cd "$script_path" + +OPEN=false +FAST=false + +while test $# -gt 0; do + case "$1" in + -h|--help) + echo "build_web.sh [--fast] [--open]" + echo " --fast: skip optimization step" + echo " --open: open the result in a browser" + exit 0 + ;; + --fast) + shift + FAST=true + ;; + --open) + shift + OPEN=true + ;; + *) + break + ;; + esac +done + +# ./setup_web.sh # <- call this first! + +FOLDER_NAME=${PWD##*/} +CRATE_NAME=$FOLDER_NAME # assume crate name is the same as the folder name +CRATE_NAME_SNAKE_CASE="${CRATE_NAME//-/_}" # for those who name crates with-kebab-case + +# This is required to enable the web_sys clipboard API which egui_web uses +# https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Clipboard.html +# https://rustwasm.github.io/docs/wasm-bindgen/web-sys/unstable-apis.html +export RUSTFLAGS=--cfg=web_sys_unstable_apis + +# Clear output from old stuff: +rm -f "docs/${CRATE_NAME_SNAKE_CASE}_bg.wasm" + +echo "Building rust…" +BUILD=release +cargo build -p "${CRATE_NAME}" --release --lib --target wasm32-unknown-unknown + +# Get the output directory (in the workspace it is in another location) +TARGET=$(cargo metadata --format-version=1 | jq --raw-output .target_directory) + +echo "Generating JS bindings for wasm…" +TARGET_NAME="${CRATE_NAME_SNAKE_CASE}.wasm" +wasm-bindgen "${TARGET}/wasm32-unknown-unknown/${BUILD}/${TARGET_NAME}" \ + --out-dir docs --no-modules --no-typescript + +if [[ "${FAST}" == false ]]; then + echo "Optimizing wasm…" + # to get wasm-opt: apt/brew/dnf install binaryen + wasm-opt "docs/${CRATE_NAME}_bg.wasm" -O2 --fast-math -o "docs/${CRATE_NAME}_bg.wasm" # add -g to get debug symbols +fi + +echo "Finished: docs/${CRATE_NAME_SNAKE_CASE}.wasm" + +if [[ "${OPEN}" == true ]]; then + if [[ "$OSTYPE" == "linux-gnu"* ]]; then + # Linux, ex: Fedora + xdg-open http://localhost:8080/index.html + elif [[ "$OSTYPE" == "msys" ]]; then + # Windows + start http://localhost:8080/index.html + else + # Darwin/MacOS, or something else + open http://localhost:8080/index.html + fi +fi diff --git a/egui_node_graph_example/check.sh b/egui_node_graph_example/check.sh new file mode 100755 index 0000000..29485f3 --- /dev/null +++ b/egui_node_graph_example/check.sh @@ -0,0 +1,10 @@ +#!/bin/bash +# This scripts runs various CI-like checks in a convenient way. +set -eux + +cargo check --workspace --all-targets +cargo check --workspace --all-features --lib --target wasm32-unknown-unknown +cargo fmt --all -- --check +cargo clippy --workspace --all-targets --all-features -- -D warnings -W clippy::all +cargo test --workspace --all-targets --all-features +cargo test --workspace --doc diff --git a/egui_node_graph_example/docs/README.md b/egui_node_graph_example/docs/README.md new file mode 100644 index 0000000..bf94f3e --- /dev/null +++ b/egui_node_graph_example/docs/README.md @@ -0,0 +1,3 @@ +This folder contains the files required for the web app. + +The reason the folder is called "docs" is because that is the name that GitHub requires in order to host a web page from the `master` branch of a repository. You can test the `eframe_template` at . diff --git a/egui_node_graph_example/docs/eframe_template.js b/egui_node_graph_example/docs/eframe_template.js new file mode 100644 index 0000000..9be70a6 --- /dev/null +++ b/egui_node_graph_example/docs/eframe_template.js @@ -0,0 +1,1272 @@ +let wasm_bindgen; +(function() { + const __exports = {}; + let wasm; + + const heap = new Array(32).fill(undefined); + + heap.push(undefined, null, true, false); + +function getObject(idx) { return heap[idx]; } + +let heap_next = heap.length; + +function dropObject(idx) { + if (idx < 36) return; + heap[idx] = heap_next; + heap_next = idx; +} + +function takeObject(idx) { + const ret = getObject(idx); + dropObject(idx); + return ret; +} + +let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }); + +cachedTextDecoder.decode(); + +let cachegetUint8Memory0 = null; +function getUint8Memory0() { + if (cachegetUint8Memory0 === null || cachegetUint8Memory0.buffer !== wasm.memory.buffer) { + cachegetUint8Memory0 = new Uint8Array(wasm.memory.buffer); + } + return cachegetUint8Memory0; +} + +function getStringFromWasm0(ptr, len) { + return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)); +} + +function addHeapObject(obj) { + if (heap_next === heap.length) heap.push(heap.length + 1); + const idx = heap_next; + heap_next = heap[idx]; + + heap[idx] = obj; + return idx; +} + +function isLikeNone(x) { + return x === undefined || x === null; +} + +let cachegetFloat64Memory0 = null; +function getFloat64Memory0() { + if (cachegetFloat64Memory0 === null || cachegetFloat64Memory0.buffer !== wasm.memory.buffer) { + cachegetFloat64Memory0 = new Float64Array(wasm.memory.buffer); + } + return cachegetFloat64Memory0; +} + +let cachegetInt32Memory0 = null; +function getInt32Memory0() { + if (cachegetInt32Memory0 === null || cachegetInt32Memory0.buffer !== wasm.memory.buffer) { + cachegetInt32Memory0 = new Int32Array(wasm.memory.buffer); + } + return cachegetInt32Memory0; +} + +let WASM_VECTOR_LEN = 0; + +let cachedTextEncoder = new TextEncoder('utf-8'); + +const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' + ? function (arg, view) { + return cachedTextEncoder.encodeInto(arg, view); +} + : function (arg, view) { + const buf = cachedTextEncoder.encode(arg); + view.set(buf); + return { + read: arg.length, + written: buf.length + }; +}); + +function passStringToWasm0(arg, malloc, realloc) { + + if (realloc === undefined) { + const buf = cachedTextEncoder.encode(arg); + const ptr = malloc(buf.length); + getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf); + WASM_VECTOR_LEN = buf.length; + return ptr; + } + + let len = arg.length; + let ptr = malloc(len); + + const mem = getUint8Memory0(); + + let offset = 0; + + for (; offset < len; offset++) { + const code = arg.charCodeAt(offset); + if (code > 0x7F) break; + mem[ptr + offset] = code; + } + + if (offset !== len) { + if (offset !== 0) { + arg = arg.slice(offset); + } + ptr = realloc(ptr, len, len = offset + arg.length * 3); + const view = getUint8Memory0().subarray(ptr + offset, ptr + len); + const ret = encodeString(arg, view); + + offset += ret.written; + } + + WASM_VECTOR_LEN = offset; + return ptr; +} + +function debugString(val) { + // primitive types + const type = typeof val; + if (type == 'number' || type == 'boolean' || val == null) { + return `${val}`; + } + if (type == 'string') { + return `"${val}"`; + } + if (type == 'symbol') { + const description = val.description; + if (description == null) { + return 'Symbol'; + } else { + return `Symbol(${description})`; + } + } + if (type == 'function') { + const name = val.name; + if (typeof name == 'string' && name.length > 0) { + return `Function(${name})`; + } else { + return 'Function'; + } + } + // objects + if (Array.isArray(val)) { + const length = val.length; + let debug = '['; + if (length > 0) { + debug += debugString(val[0]); + } + for(let i = 1; i < length; i++) { + debug += ', ' + debugString(val[i]); + } + debug += ']'; + return debug; + } + // Test for built-in + const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); + let className; + if (builtInMatches.length > 1) { + className = builtInMatches[1]; + } else { + // Failed to match the standard '[object ClassName]' + return toString.call(val); + } + if (className == 'Object') { + // we're a user defined class or Object + // JSON.stringify avoids problems with cycles, and is generally much + // easier than looping through ownProperties of `val`. + try { + return 'Object(' + JSON.stringify(val) + ')'; + } catch (_) { + return 'Object'; + } + } + // errors + if (val instanceof Error) { + return `${val.name}: ${val.message}\n${val.stack}`; + } + // TODO we could test for more things here, like `Set`s and `Map`s. + return className; +} + +function makeMutClosure(arg0, arg1, dtor, f) { + const state = { a: arg0, b: arg1, cnt: 1, dtor }; + const real = (...args) => { + // First up with a closure we increment the internal reference + // count. This ensures that the Rust closure environment won't + // be deallocated while we're invoking it. + state.cnt++; + const a = state.a; + state.a = 0; + try { + return f(a, state.b, ...args); + } finally { + if (--state.cnt === 0) { + wasm.__wbindgen_export_2.get(state.dtor)(a, state.b); + + } else { + state.a = a; + } + } + }; + real.original = state; + + return real; +} +function __wbg_adapter_26(arg0, arg1) { + wasm._dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h82daea18f996a61e(arg0, arg1); +} + +function __wbg_adapter_29(arg0, arg1, arg2) { + wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h02380ceefaeb445c(arg0, arg1, addHeapObject(arg2)); +} + +function __wbg_adapter_32(arg0, arg1, arg2) { + wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h02380ceefaeb445c(arg0, arg1, addHeapObject(arg2)); +} + +function __wbg_adapter_35(arg0, arg1, arg2) { + wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h02380ceefaeb445c(arg0, arg1, addHeapObject(arg2)); +} + +function __wbg_adapter_38(arg0, arg1, arg2) { + wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h02380ceefaeb445c(arg0, arg1, addHeapObject(arg2)); +} + +function __wbg_adapter_41(arg0, arg1, arg2) { + wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h02380ceefaeb445c(arg0, arg1, addHeapObject(arg2)); +} + +function __wbg_adapter_44(arg0, arg1, arg2) { + wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h02380ceefaeb445c(arg0, arg1, addHeapObject(arg2)); +} + +function __wbg_adapter_47(arg0, arg1, arg2) { + wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h02380ceefaeb445c(arg0, arg1, addHeapObject(arg2)); +} + +function __wbg_adapter_50(arg0, arg1) { + wasm._dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__he8468c0457df91ee(arg0, arg1); +} + +function __wbg_adapter_53(arg0, arg1, arg2) { + wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h02380ceefaeb445c(arg0, arg1, addHeapObject(arg2)); +} + +function __wbg_adapter_56(arg0, arg1, arg2) { + wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__ha61e5578eca01976(arg0, arg1, addHeapObject(arg2)); +} + +/** +* This is the entry-point for all the web-assembly. +* This is called once from the HTML. +* It loads the app, installs some callbacks, then returns. +* You can add more callbacks like this if you want to call in to your code. +* @param {string} canvas_id +*/ +__exports.start = function(canvas_id) { + var ptr0 = passStringToWasm0(canvas_id, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len0 = WASM_VECTOR_LEN; + wasm.start(ptr0, len0); +}; + +function getArrayU8FromWasm0(ptr, len) { + return getUint8Memory0().subarray(ptr / 1, ptr / 1 + len); +} + +function handleError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + wasm.__wbindgen_exn_store(addHeapObject(e)); + } +} + +async function load(module, imports) { + if (typeof Response === 'function' && module instanceof Response) { + if (typeof WebAssembly.instantiateStreaming === 'function') { + try { + return await WebAssembly.instantiateStreaming(module, imports); + + } catch (e) { + if (module.headers.get('Content-Type') != 'application/wasm') { + console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e); + + } else { + throw e; + } + } + } + + const bytes = await module.arrayBuffer(); + return await WebAssembly.instantiate(bytes, imports); + + } else { + const instance = await WebAssembly.instantiate(module, imports); + + if (instance instanceof WebAssembly.Instance) { + return { instance, module }; + + } else { + return instance; + } + } +} + +async function init(input) { + if (typeof input === 'undefined') { + let src; + if (typeof document === 'undefined') { + src = location.href; + } else { + src = document.currentScript.src; + } + input = src.replace(/\.js$/, '_bg.wasm'); + } + const imports = {}; + imports.wbg = {}; + imports.wbg.__wbindgen_object_drop_ref = function(arg0) { + takeObject(arg0); + }; + imports.wbg.__wbindgen_string_new = function(arg0, arg1) { + var ret = getStringFromWasm0(arg0, arg1); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_cb_drop = function(arg0) { + const obj = takeObject(arg0).original; + if (obj.cnt-- == 1) { + obj.a = 0; + return true; + } + var ret = false; + return ret; + }; + imports.wbg.__wbindgen_number_get = function(arg0, arg1) { + const obj = getObject(arg1); + var ret = typeof(obj) === 'number' ? obj : undefined; + getFloat64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? 0 : ret; + getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(ret); + }; + imports.wbg.__wbindgen_object_clone_ref = function(arg0) { + var ret = getObject(arg0); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_string_get = function(arg0, arg1) { + const obj = getObject(arg1); + var ret = typeof(obj) === 'string' ? obj : undefined; + var ptr0 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; + }; + imports.wbg.__wbindgen_boolean_get = function(arg0) { + const v = getObject(arg0); + var ret = typeof(v) === 'boolean' ? (v ? 1 : 0) : 2; + return ret; + }; + imports.wbg.__wbg_instanceof_WebGl2RenderingContext_56ad96bfac3f5531 = function(arg0) { + var ret = getObject(arg0) instanceof WebGL2RenderingContext; + return ret; + }; + imports.wbg.__wbg_drawingBufferWidth_561b8beaef3111f5 = function(arg0) { + var ret = getObject(arg0).drawingBufferWidth; + return ret; + }; + imports.wbg.__wbg_drawingBufferHeight_aa35759c7f962358 = function(arg0) { + var ret = getObject(arg0).drawingBufferHeight; + return ret; + }; + imports.wbg.__wbg_bindVertexArray_52b8b2f5fd93d81d = function(arg0, arg1) { + getObject(arg0).bindVertexArray(getObject(arg1)); + }; + imports.wbg.__wbg_bufferData_bba22fbe5dd1f1d6 = function(arg0, arg1, arg2, arg3) { + getObject(arg0).bufferData(arg1 >>> 0, getObject(arg2), arg3 >>> 0); + }; + imports.wbg.__wbg_bufferData_794d61d3c392fafd = function(arg0, arg1, arg2, arg3, arg4) { + getObject(arg0).bufferData(arg1 >>> 0, getArrayU8FromWasm0(arg2, arg3), arg4 >>> 0); + }; + imports.wbg.__wbg_createVertexArray_d59135c0a43c410b = function(arg0) { + var ret = getObject(arg0).createVertexArray(); + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }; + imports.wbg.__wbg_deleteVertexArray_385874f9e1499a3f = function(arg0, arg1) { + getObject(arg0).deleteVertexArray(getObject(arg1)); + }; + imports.wbg.__wbg_texImage2D_29ea0a7f026e239b = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10) { + getObject(arg0).texImage2D(arg1 >>> 0, arg2, arg3, arg4, arg5, arg6, arg7 >>> 0, arg8 >>> 0, arg9 === 0 ? undefined : getArrayU8FromWasm0(arg9, arg10)); + }, arguments) }; + imports.wbg.__wbg_activeTexture_0092956fa2eefd8c = function(arg0, arg1) { + getObject(arg0).activeTexture(arg1 >>> 0); + }; + imports.wbg.__wbg_attachShader_7faccaa7b5ac28a6 = function(arg0, arg1, arg2) { + getObject(arg0).attachShader(getObject(arg1), getObject(arg2)); + }; + imports.wbg.__wbg_bindBuffer_4ece833dd10cac2f = function(arg0, arg1, arg2) { + getObject(arg0).bindBuffer(arg1 >>> 0, getObject(arg2)); + }; + imports.wbg.__wbg_bindFramebuffer_48c4bf8ff82bf7e9 = function(arg0, arg1, arg2) { + getObject(arg0).bindFramebuffer(arg1 >>> 0, getObject(arg2)); + }; + imports.wbg.__wbg_bindTexture_9d8ed0fcd83eb0a9 = function(arg0, arg1, arg2) { + getObject(arg0).bindTexture(arg1 >>> 0, getObject(arg2)); + }; + imports.wbg.__wbg_blendFunc_b254bb91838df1dd = function(arg0, arg1, arg2) { + getObject(arg0).blendFunc(arg1 >>> 0, arg2 >>> 0); + }; + imports.wbg.__wbg_clear_4ce66c813d66e77d = function(arg0, arg1) { + getObject(arg0).clear(arg1 >>> 0); + }; + imports.wbg.__wbg_clearColor_71f96fd72a7646a6 = function(arg0, arg1, arg2, arg3, arg4) { + getObject(arg0).clearColor(arg1, arg2, arg3, arg4); + }; + imports.wbg.__wbg_compileShader_dd66d66a5a6481f3 = function(arg0, arg1) { + getObject(arg0).compileShader(getObject(arg1)); + }; + imports.wbg.__wbg_createBuffer_5c5caa16032a81b7 = function(arg0) { + var ret = getObject(arg0).createBuffer(); + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }; + imports.wbg.__wbg_createFramebuffer_9818fc04b4a38c18 = function(arg0) { + var ret = getObject(arg0).createFramebuffer(); + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }; + imports.wbg.__wbg_createProgram_32d01a55e144b9fc = function(arg0) { + var ret = getObject(arg0).createProgram(); + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }; + imports.wbg.__wbg_createShader_6e8eed55567fe1a6 = function(arg0, arg1) { + var ret = getObject(arg0).createShader(arg1 >>> 0); + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }; + imports.wbg.__wbg_createTexture_8f31e7386e22fc37 = function(arg0) { + var ret = getObject(arg0).createTexture(); + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }; + imports.wbg.__wbg_deleteBuffer_de80b51d8166fddb = function(arg0, arg1) { + getObject(arg0).deleteBuffer(getObject(arg1)); + }; + imports.wbg.__wbg_deleteFramebuffer_5f58ccb548438c57 = function(arg0, arg1) { + getObject(arg0).deleteFramebuffer(getObject(arg1)); + }; + imports.wbg.__wbg_deleteProgram_3ec3c43f2cddde7f = function(arg0, arg1) { + getObject(arg0).deleteProgram(getObject(arg1)); + }; + imports.wbg.__wbg_deleteTexture_a0632c71429795ac = function(arg0, arg1) { + getObject(arg0).deleteTexture(getObject(arg1)); + }; + imports.wbg.__wbg_disable_b05e075ae54fa448 = function(arg0, arg1) { + getObject(arg0).disable(arg1 >>> 0); + }; + imports.wbg.__wbg_drawElements_a41bb53d39cd6297 = function(arg0, arg1, arg2, arg3, arg4) { + getObject(arg0).drawElements(arg1 >>> 0, arg2, arg3 >>> 0, arg4); + }; + imports.wbg.__wbg_enable_766e546395da5a5d = function(arg0, arg1) { + getObject(arg0).enable(arg1 >>> 0); + }; + imports.wbg.__wbg_enableVertexAttribArray_91da8d3cbe0c2bbd = function(arg0, arg1) { + getObject(arg0).enableVertexAttribArray(arg1 >>> 0); + }; + imports.wbg.__wbg_framebufferTexture2D_3da41a7f38e2c523 = function(arg0, arg1, arg2, arg3, arg4, arg5) { + getObject(arg0).framebufferTexture2D(arg1 >>> 0, arg2 >>> 0, arg3 >>> 0, getObject(arg4), arg5); + }; + imports.wbg.__wbg_getAttribLocation_5d304d390c7273f5 = function(arg0, arg1, arg2, arg3) { + var ret = getObject(arg0).getAttribLocation(getObject(arg1), getStringFromWasm0(arg2, arg3)); + return ret; + }; + imports.wbg.__wbg_getProgramInfoLog_18c849a5fa54e7b1 = function(arg0, arg1, arg2) { + var ret = getObject(arg1).getProgramInfoLog(getObject(arg2)); + var ptr0 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; + }; + imports.wbg.__wbg_getProgramParameter_80edd3cfbcf7cf1d = function(arg0, arg1, arg2) { + var ret = getObject(arg0).getProgramParameter(getObject(arg1), arg2 >>> 0); + return addHeapObject(ret); + }; + imports.wbg.__wbg_getShaderInfoLog_ba1de20c14b6fb63 = function(arg0, arg1, arg2) { + var ret = getObject(arg1).getShaderInfoLog(getObject(arg2)); + var ptr0 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; + }; + imports.wbg.__wbg_getShaderParameter_264d9ab5c13ece4d = function(arg0, arg1, arg2) { + var ret = getObject(arg0).getShaderParameter(getObject(arg1), arg2 >>> 0); + return addHeapObject(ret); + }; + imports.wbg.__wbg_getUniformLocation_77b2d89291f84289 = function(arg0, arg1, arg2, arg3) { + var ret = getObject(arg0).getUniformLocation(getObject(arg1), getStringFromWasm0(arg2, arg3)); + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }; + imports.wbg.__wbg_linkProgram_b84796e37364e5c9 = function(arg0, arg1) { + getObject(arg0).linkProgram(getObject(arg1)); + }; + imports.wbg.__wbg_pixelStorei_a9b9b42ef01616b2 = function(arg0, arg1, arg2) { + getObject(arg0).pixelStorei(arg1 >>> 0, arg2); + }; + imports.wbg.__wbg_scissor_5802aaee71f2eb0e = function(arg0, arg1, arg2, arg3, arg4) { + getObject(arg0).scissor(arg1, arg2, arg3, arg4); + }; + imports.wbg.__wbg_shaderSource_18f45f93c05a8311 = function(arg0, arg1, arg2, arg3) { + getObject(arg0).shaderSource(getObject(arg1), getStringFromWasm0(arg2, arg3)); + }; + imports.wbg.__wbg_texParameteri_c54aab65b2f8cf6d = function(arg0, arg1, arg2, arg3) { + getObject(arg0).texParameteri(arg1 >>> 0, arg2 >>> 0, arg3); + }; + imports.wbg.__wbg_uniform1i_e287345af4468e22 = function(arg0, arg1, arg2) { + getObject(arg0).uniform1i(getObject(arg1), arg2); + }; + imports.wbg.__wbg_uniform2f_f8d8e7662e0e0eb6 = function(arg0, arg1, arg2, arg3) { + getObject(arg0).uniform2f(getObject(arg1), arg2, arg3); + }; + imports.wbg.__wbg_useProgram_c2fdf4a953d1128a = function(arg0, arg1) { + getObject(arg0).useProgram(getObject(arg1)); + }; + imports.wbg.__wbg_vertexAttribPointer_76d558694fe81cd7 = function(arg0, arg1, arg2, arg3, arg4, arg5, arg6) { + getObject(arg0).vertexAttribPointer(arg1 >>> 0, arg2, arg3 >>> 0, arg4 !== 0, arg5, arg6); + }; + imports.wbg.__wbg_viewport_da0901eee69b9909 = function(arg0, arg1, arg2, arg3, arg4) { + getObject(arg0).viewport(arg1, arg2, arg3, arg4); + }; + imports.wbg.__wbg_instanceof_Window_c4b70662a0d2c5ec = function(arg0) { + var ret = getObject(arg0) instanceof Window; + return ret; + }; + imports.wbg.__wbg_document_1c64944725c0d81d = function(arg0) { + var ret = getObject(arg0).document; + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }; + imports.wbg.__wbg_location_f98ad02632f88c43 = function(arg0) { + var ret = getObject(arg0).location; + return addHeapObject(ret); + }; + imports.wbg.__wbg_navigator_480e592af6ad365b = function(arg0) { + var ret = getObject(arg0).navigator; + return addHeapObject(ret); + }; + imports.wbg.__wbg_innerWidth_ef25c730fca132cf = function() { return handleError(function (arg0) { + var ret = getObject(arg0).innerWidth; + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_innerHeight_1b1217a63a77bf61 = function() { return handleError(function (arg0) { + var ret = getObject(arg0).innerHeight; + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_devicePixelRatio_d8c3852bb37f76bf = function(arg0) { + var ret = getObject(arg0).devicePixelRatio; + return ret; + }; + imports.wbg.__wbg_performance_947628766699c5bb = function(arg0) { + var ret = getObject(arg0).performance; + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }; + imports.wbg.__wbg_localStorage_6775414303ab5085 = function() { return handleError(function (arg0) { + var ret = getObject(arg0).localStorage; + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_matchMedia_b95c474c6db67a60 = function() { return handleError(function (arg0, arg1, arg2) { + var ret = getObject(arg0).matchMedia(getStringFromWasm0(arg1, arg2)); + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_open_5416e4448a959cfa = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) { + var ret = getObject(arg0).open(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4)); + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_requestAnimationFrame_71638ca922068239 = function() { return handleError(function (arg0, arg1) { + var ret = getObject(arg0).requestAnimationFrame(getObject(arg1)); + return ret; + }, arguments) }; + imports.wbg.__wbg_setInterval_ec2d9dc4a54a6566 = function() { return handleError(function (arg0, arg1, arg2) { + var ret = getObject(arg0).setInterval(getObject(arg1), arg2); + return ret; + }, arguments) }; + imports.wbg.__wbg_setTimeout_df66d951b1726b78 = function() { return handleError(function (arg0, arg1, arg2) { + var ret = getObject(arg0).setTimeout(getObject(arg1), arg2); + return ret; + }, arguments) }; + imports.wbg.__wbg_type_7a49279491e15d0a = function(arg0, arg1) { + var ret = getObject(arg1).type; + var ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; + }; + imports.wbg.__wbg_preventDefault_9866c9fd51eecfb6 = function(arg0) { + getObject(arg0).preventDefault(); + }; + imports.wbg.__wbg_stopPropagation_ae76be6b0f664ee8 = function(arg0) { + getObject(arg0).stopPropagation(); + }; + imports.wbg.__wbg_length_a2870b8b80e120c3 = function(arg0) { + var ret = getObject(arg0).length; + return ret; + }; + imports.wbg.__wbg_get_b84d80d476cf15e4 = function(arg0, arg1) { + var ret = getObject(arg0)[arg1 >>> 0]; + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }; + imports.wbg.__wbg_keyCode_490ed69472addfdc = function(arg0) { + var ret = getObject(arg0).keyCode; + return ret; + }; + imports.wbg.__wbg_altKey_3dcb50d5afbc5036 = function(arg0) { + var ret = getObject(arg0).altKey; + return ret; + }; + imports.wbg.__wbg_ctrlKey_fb62ba10b63b34a4 = function(arg0) { + var ret = getObject(arg0).ctrlKey; + return ret; + }; + imports.wbg.__wbg_shiftKey_bd2875540e5db840 = function(arg0) { + var ret = getObject(arg0).shiftKey; + return ret; + }; + imports.wbg.__wbg_metaKey_94ca09e07f21f240 = function(arg0) { + var ret = getObject(arg0).metaKey; + return ret; + }; + imports.wbg.__wbg_isComposing_d05ebca75d81bc30 = function(arg0) { + var ret = getObject(arg0).isComposing; + return ret; + }; + imports.wbg.__wbg_key_10dcaa4bb6d5449f = function(arg0, arg1) { + var ret = getObject(arg1).key; + var ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; + }; + imports.wbg.__wbg_length_1d27563e3515722e = function(arg0) { + var ret = getObject(arg0).length; + return ret; + }; + imports.wbg.__wbg_item_a23b382195352a8a = function(arg0, arg1) { + var ret = getObject(arg0).item(arg1 >>> 0); + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }; + imports.wbg.__wbg_get_20b719b18767c76e = function(arg0, arg1) { + var ret = getObject(arg0)[arg1 >>> 0]; + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }; + imports.wbg.__wbg_name_6af1a38f3edc1522 = function(arg0, arg1) { + var ret = getObject(arg1).name; + var ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; + }; + imports.wbg.__wbg_lastModified_c61609c3c6a0bd88 = function(arg0) { + var ret = getObject(arg0).lastModified; + return ret; + }; + imports.wbg.__wbg_instanceof_HtmlCanvasElement_25d964a0dde6717e = function(arg0) { + var ret = getObject(arg0) instanceof HTMLCanvasElement; + return ret; + }; + imports.wbg.__wbg_width_555f63ab09ba7d3f = function(arg0) { + var ret = getObject(arg0).width; + return ret; + }; + imports.wbg.__wbg_setwidth_c1a7061891b71f25 = function(arg0, arg1) { + getObject(arg0).width = arg1 >>> 0; + }; + imports.wbg.__wbg_height_7153faec70fbaf7b = function(arg0) { + var ret = getObject(arg0).height; + return ret; + }; + imports.wbg.__wbg_setheight_88894b05710ff752 = function(arg0, arg1) { + getObject(arg0).height = arg1 >>> 0; + }; + imports.wbg.__wbg_getContext_f701d0231ae22393 = function() { return handleError(function (arg0, arg1, arg2) { + var ret = getObject(arg0).getContext(getStringFromWasm0(arg1, arg2)); + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_touches_3bcd168150040d19 = function(arg0) { + var ret = getObject(arg0).touches; + return addHeapObject(ret); + }; + imports.wbg.__wbg_changedTouches_d84714496e7f4712 = function(arg0) { + var ret = getObject(arg0).changedTouches; + return addHeapObject(ret); + }; + imports.wbg.__wbg_top_3946f8347860b55c = function(arg0) { + var ret = getObject(arg0).top; + return ret; + }; + imports.wbg.__wbg_left_31cce57341292712 = function(arg0) { + var ret = getObject(arg0).left; + return ret; + }; + imports.wbg.__wbg_clipboard_3dff7cff084c4be2 = function(arg0) { + var ret = getObject(arg0).clipboard; + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }; + imports.wbg.__wbg_userAgent_bdd46cceef222f52 = function() { return handleError(function (arg0, arg1) { + var ret = getObject(arg1).userAgent; + var ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; + }, arguments) }; + imports.wbg.__wbg_width_d55d3698a2514ec1 = function(arg0) { + var ret = getObject(arg0).width; + return ret; + }; + imports.wbg.__wbg_height_df08a93b45ce76ec = function(arg0) { + var ret = getObject(arg0).height; + return ret; + }; + imports.wbg.__wbg_appendChild_d318db34c4559916 = function() { return handleError(function (arg0, arg1) { + var ret = getObject(arg0).appendChild(getObject(arg1)); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_size_3d49b94127cdd6ed = function(arg0) { + var ret = getObject(arg0).size; + return ret; + }; + imports.wbg.__wbg_arrayBuffer_e857fb358de5f814 = function(arg0) { + var ret = getObject(arg0).arrayBuffer(); + return addHeapObject(ret); + }; + imports.wbg.__wbg_hash_0fff5255cf3c317c = function() { return handleError(function (arg0, arg1) { + var ret = getObject(arg1).hash; + var ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; + }, arguments) }; + imports.wbg.__wbg_data_dbff09eb89176161 = function(arg0, arg1) { + var ret = getObject(arg1).data; + var ptr0 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; + }; + imports.wbg.__wbg_setProperty_1460c660bc329763 = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) { + getObject(arg0).setProperty(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4)); + }, arguments) }; + imports.wbg.__wbg_type_a6fcda966902940d = function(arg0, arg1) { + var ret = getObject(arg1).type; + var ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; + }; + imports.wbg.__wbg_clientX_97ff0f5c7b19e687 = function(arg0) { + var ret = getObject(arg0).clientX; + return ret; + }; + imports.wbg.__wbg_clientY_cacd4a7e44b9719b = function(arg0) { + var ret = getObject(arg0).clientY; + return ret; + }; + imports.wbg.__wbg_ctrlKey_9761d22fa42f09c0 = function(arg0) { + var ret = getObject(arg0).ctrlKey; + return ret; + }; + imports.wbg.__wbg_metaKey_e6b9e0aa35aa2974 = function(arg0) { + var ret = getObject(arg0).metaKey; + return ret; + }; + imports.wbg.__wbg_button_a02c0467d38e8338 = function(arg0) { + var ret = getObject(arg0).button; + return ret; + }; + imports.wbg.__wbg_getItem_77fb9d4666f3b93a = function() { return handleError(function (arg0, arg1, arg2, arg3) { + var ret = getObject(arg1).getItem(getStringFromWasm0(arg2, arg3)); + var ptr0 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; + }, arguments) }; + imports.wbg.__wbg_setItem_b0c4561489dffecd = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) { + getObject(arg0).setItem(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4)); + }, arguments) }; + imports.wbg.__wbg_body_78ae4fd43b446013 = function(arg0) { + var ret = getObject(arg0).body; + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }; + imports.wbg.__wbg_createElement_86c152812a141a62 = function() { return handleError(function (arg0, arg1, arg2) { + var ret = getObject(arg0).createElement(getStringFromWasm0(arg1, arg2)); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_getElementById_f3e94458ce77f0d0 = function(arg0, arg1, arg2) { + var ret = getObject(arg0).getElementById(getStringFromWasm0(arg1, arg2)); + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }; + imports.wbg.__wbg_setid_681bb5a14c3d5850 = function(arg0, arg1, arg2) { + getObject(arg0).id = getStringFromWasm0(arg1, arg2); + }; + imports.wbg.__wbg_scrollLeft_e79152b1f5d86671 = function(arg0) { + var ret = getObject(arg0).scrollLeft; + return ret; + }; + imports.wbg.__wbg_clientWidth_4d9e01af2b5b9f21 = function(arg0) { + var ret = getObject(arg0).clientWidth; + return ret; + }; + imports.wbg.__wbg_clientHeight_87c209f0cacf2e97 = function(arg0) { + var ret = getObject(arg0).clientHeight; + return ret; + }; + imports.wbg.__wbg_getBoundingClientRect_2fba0402ea2a6ec4 = function(arg0) { + var ret = getObject(arg0).getBoundingClientRect(); + return addHeapObject(ret); + }; + imports.wbg.__wbg_instanceof_WebGlRenderingContext_101b938bec1286a3 = function(arg0) { + var ret = getObject(arg0) instanceof WebGLRenderingContext; + return ret; + }; + imports.wbg.__wbg_drawingBufferWidth_8b0c2b31d9d6eee7 = function(arg0) { + var ret = getObject(arg0).drawingBufferWidth; + return ret; + }; + imports.wbg.__wbg_drawingBufferHeight_f62678018bab567c = function(arg0) { + var ret = getObject(arg0).drawingBufferHeight; + return ret; + }; + imports.wbg.__wbg_bufferData_6beb22ecb30c1316 = function(arg0, arg1, arg2, arg3) { + getObject(arg0).bufferData(arg1 >>> 0, getObject(arg2), arg3 >>> 0); + }; + imports.wbg.__wbg_bufferData_2f9be23b37e5a1a4 = function(arg0, arg1, arg2, arg3, arg4) { + getObject(arg0).bufferData(arg1 >>> 0, getArrayU8FromWasm0(arg2, arg3), arg4 >>> 0); + }; + imports.wbg.__wbg_texImage2D_712c56fe5a9825ed = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10) { + getObject(arg0).texImage2D(arg1 >>> 0, arg2, arg3, arg4, arg5, arg6, arg7 >>> 0, arg8 >>> 0, arg9 === 0 ? undefined : getArrayU8FromWasm0(arg9, arg10)); + }, arguments) }; + imports.wbg.__wbg_activeTexture_b34aca0c2110966c = function(arg0, arg1) { + getObject(arg0).activeTexture(arg1 >>> 0); + }; + imports.wbg.__wbg_attachShader_eaa824fd5b37a770 = function(arg0, arg1, arg2) { + getObject(arg0).attachShader(getObject(arg1), getObject(arg2)); + }; + imports.wbg.__wbg_bindBuffer_2ca7e1c18819ecb2 = function(arg0, arg1, arg2) { + getObject(arg0).bindBuffer(arg1 >>> 0, getObject(arg2)); + }; + imports.wbg.__wbg_bindFramebuffer_c9f468afa9d42a5f = function(arg0, arg1, arg2) { + getObject(arg0).bindFramebuffer(arg1 >>> 0, getObject(arg2)); + }; + imports.wbg.__wbg_bindTexture_edd827f3dba6038e = function(arg0, arg1, arg2) { + getObject(arg0).bindTexture(arg1 >>> 0, getObject(arg2)); + }; + imports.wbg.__wbg_blendFunc_d5ab9f0ff5a40a48 = function(arg0, arg1, arg2) { + getObject(arg0).blendFunc(arg1 >>> 0, arg2 >>> 0); + }; + imports.wbg.__wbg_clear_da26620d46f0a11a = function(arg0, arg1) { + getObject(arg0).clear(arg1 >>> 0); + }; + imports.wbg.__wbg_clearColor_cbf22f8faa5a52c1 = function(arg0, arg1, arg2, arg3, arg4) { + getObject(arg0).clearColor(arg1, arg2, arg3, arg4); + }; + imports.wbg.__wbg_compileShader_8fb70a472f32552c = function(arg0, arg1) { + getObject(arg0).compileShader(getObject(arg1)); + }; + imports.wbg.__wbg_createBuffer_4802e2f0e1b1acdf = function(arg0) { + var ret = getObject(arg0).createBuffer(); + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }; + imports.wbg.__wbg_createFramebuffer_0157699cdc720b46 = function(arg0) { + var ret = getObject(arg0).createFramebuffer(); + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }; + imports.wbg.__wbg_createProgram_b1d94f4c7554d3a1 = function(arg0) { + var ret = getObject(arg0).createProgram(); + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }; + imports.wbg.__wbg_createShader_da09e167692f0dc7 = function(arg0, arg1) { + var ret = getObject(arg0).createShader(arg1 >>> 0); + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }; + imports.wbg.__wbg_createTexture_bafc7c08393ae59d = function(arg0) { + var ret = getObject(arg0).createTexture(); + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }; + imports.wbg.__wbg_deleteBuffer_9c31f3452ba32db1 = function(arg0, arg1) { + getObject(arg0).deleteBuffer(getObject(arg1)); + }; + imports.wbg.__wbg_deleteFramebuffer_0f43513bd6c6d986 = function(arg0, arg1) { + getObject(arg0).deleteFramebuffer(getObject(arg1)); + }; + imports.wbg.__wbg_deleteProgram_a2c849932f79e7af = function(arg0, arg1) { + getObject(arg0).deleteProgram(getObject(arg1)); + }; + imports.wbg.__wbg_deleteTexture_82d755a5ac828346 = function(arg0, arg1) { + getObject(arg0).deleteTexture(getObject(arg1)); + }; + imports.wbg.__wbg_disable_b07faddb7d04349f = function(arg0, arg1) { + getObject(arg0).disable(arg1 >>> 0); + }; + imports.wbg.__wbg_drawElements_8e8af4b6757fedce = function(arg0, arg1, arg2, arg3, arg4) { + getObject(arg0).drawElements(arg1 >>> 0, arg2, arg3 >>> 0, arg4); + }; + imports.wbg.__wbg_enable_d3d210aeb08eff52 = function(arg0, arg1) { + getObject(arg0).enable(arg1 >>> 0); + }; + imports.wbg.__wbg_enableVertexAttribArray_d539e547495bea44 = function(arg0, arg1) { + getObject(arg0).enableVertexAttribArray(arg1 >>> 0); + }; + imports.wbg.__wbg_framebufferTexture2D_923c6fc6645661bc = function(arg0, arg1, arg2, arg3, arg4, arg5) { + getObject(arg0).framebufferTexture2D(arg1 >>> 0, arg2 >>> 0, arg3 >>> 0, getObject(arg4), arg5); + }; + imports.wbg.__wbg_getAttribLocation_706a0beabcdaebcf = function(arg0, arg1, arg2, arg3) { + var ret = getObject(arg0).getAttribLocation(getObject(arg1), getStringFromWasm0(arg2, arg3)); + return ret; + }; + imports.wbg.__wbg_getExtension_045789240c50a108 = function() { return handleError(function (arg0, arg1, arg2) { + var ret = getObject(arg0).getExtension(getStringFromWasm0(arg1, arg2)); + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_getParameter_6412bd2d0602696d = function() { return handleError(function (arg0, arg1) { + var ret = getObject(arg0).getParameter(arg1 >>> 0); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_getProgramInfoLog_b60e82d52c200cbd = function(arg0, arg1, arg2) { + var ret = getObject(arg1).getProgramInfoLog(getObject(arg2)); + var ptr0 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; + }; + imports.wbg.__wbg_getProgramParameter_229c193895936bbe = function(arg0, arg1, arg2) { + var ret = getObject(arg0).getProgramParameter(getObject(arg1), arg2 >>> 0); + return addHeapObject(ret); + }; + imports.wbg.__wbg_getShaderInfoLog_ba51160c01b98360 = function(arg0, arg1, arg2) { + var ret = getObject(arg1).getShaderInfoLog(getObject(arg2)); + var ptr0 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; + }; + imports.wbg.__wbg_getShaderParameter_dadc55c10928575d = function(arg0, arg1, arg2) { + var ret = getObject(arg0).getShaderParameter(getObject(arg1), arg2 >>> 0); + return addHeapObject(ret); + }; + imports.wbg.__wbg_getUniformLocation_c3b3570b4632cc5c = function(arg0, arg1, arg2, arg3) { + var ret = getObject(arg0).getUniformLocation(getObject(arg1), getStringFromWasm0(arg2, arg3)); + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }; + imports.wbg.__wbg_linkProgram_7080c84b0233cea2 = function(arg0, arg1) { + getObject(arg0).linkProgram(getObject(arg1)); + }; + imports.wbg.__wbg_pixelStorei_3cd96723ae22a5c6 = function(arg0, arg1, arg2) { + getObject(arg0).pixelStorei(arg1 >>> 0, arg2); + }; + imports.wbg.__wbg_scissor_35fe98c7da06091c = function(arg0, arg1, arg2, arg3, arg4) { + getObject(arg0).scissor(arg1, arg2, arg3, arg4); + }; + imports.wbg.__wbg_shaderSource_67b991301db003d0 = function(arg0, arg1, arg2, arg3) { + getObject(arg0).shaderSource(getObject(arg1), getStringFromWasm0(arg2, arg3)); + }; + imports.wbg.__wbg_texParameteri_bd724f6a5ad0cbbc = function(arg0, arg1, arg2, arg3) { + getObject(arg0).texParameteri(arg1 >>> 0, arg2 >>> 0, arg3); + }; + imports.wbg.__wbg_uniform1i_0811c29c0eebe191 = function(arg0, arg1, arg2) { + getObject(arg0).uniform1i(getObject(arg1), arg2); + }; + imports.wbg.__wbg_uniform2f_c4c110dee7f069e7 = function(arg0, arg1, arg2, arg3) { + getObject(arg0).uniform2f(getObject(arg1), arg2, arg3); + }; + imports.wbg.__wbg_useProgram_b72b0bfcbc720fa9 = function(arg0, arg1) { + getObject(arg0).useProgram(getObject(arg1)); + }; + imports.wbg.__wbg_vertexAttribPointer_b5cb524c6fe9eec8 = function(arg0, arg1, arg2, arg3, arg4, arg5, arg6) { + getObject(arg0).vertexAttribPointer(arg1 >>> 0, arg2, arg3 >>> 0, arg4 !== 0, arg5, arg6); + }; + imports.wbg.__wbg_viewport_89af3aceb7036a2c = function(arg0, arg1, arg2, arg3, arg4) { + getObject(arg0).viewport(arg1, arg2, arg3, arg4); + }; + imports.wbg.__wbg_error_cc38ce2b4b661e1d = function(arg0) { + console.error(getObject(arg0)); + }; + imports.wbg.__wbg_log_3445347661d4505e = function(arg0) { + console.log(getObject(arg0)); + }; + imports.wbg.__wbg_warn_5ec7c7c02d0b3841 = function(arg0) { + console.warn(getObject(arg0)); + }; + imports.wbg.__wbg_scrollTop_14114fee3506489f = function(arg0) { + var ret = getObject(arg0).scrollTop; + return ret; + }; + imports.wbg.__wbg_hidden_cf2bd9859a26899c = function(arg0) { + var ret = getObject(arg0).hidden; + return ret; + }; + imports.wbg.__wbg_sethidden_8e35dd2030c5f20a = function(arg0, arg1) { + getObject(arg0).hidden = arg1 !== 0; + }; + imports.wbg.__wbg_style_c88e323890d3a091 = function(arg0) { + var ret = getObject(arg0).style; + return addHeapObject(ret); + }; + imports.wbg.__wbg_offsetTop_83b2934370041fae = function(arg0) { + var ret = getObject(arg0).offsetTop; + return ret; + }; + imports.wbg.__wbg_offsetLeft_d6d050965faa87a8 = function(arg0) { + var ret = getObject(arg0).offsetLeft; + return ret; + }; + imports.wbg.__wbg_offsetWidth_69cd6669725b154f = function(arg0) { + var ret = getObject(arg0).offsetWidth; + return ret; + }; + imports.wbg.__wbg_blur_0bae1ed9ffb0b918 = function() { return handleError(function (arg0) { + getObject(arg0).blur(); + }, arguments) }; + imports.wbg.__wbg_focus_00530e359f44fc6e = function() { return handleError(function (arg0) { + getObject(arg0).focus(); + }, arguments) }; + imports.wbg.__wbg_clipboardData_d717f7cf398c0dd9 = function(arg0) { + var ret = getObject(arg0).clipboardData; + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }; + imports.wbg.__wbg_dataTransfer_ebba35c1049e694f = function(arg0) { + var ret = getObject(arg0).dataTransfer; + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }; + imports.wbg.__wbg_addEventListener_52721772cc0a7f30 = function() { return handleError(function (arg0, arg1, arg2, arg3) { + getObject(arg0).addEventListener(getStringFromWasm0(arg1, arg2), getObject(arg3)); + }, arguments) }; + imports.wbg.__wbg_deltaX_8cfc6cd15e97d97c = function(arg0) { + var ret = getObject(arg0).deltaX; + return ret; + }; + imports.wbg.__wbg_deltaY_080604c20160c0e8 = function(arg0) { + var ret = getObject(arg0).deltaY; + return ret; + }; + imports.wbg.__wbg_deltaMode_c5ec1ee518ea0a08 = function(arg0) { + var ret = getObject(arg0).deltaMode; + return ret; + }; + imports.wbg.__wbg_instanceof_HtmlInputElement_8cafe5f30dfdb6bc = function(arg0) { + var ret = getObject(arg0) instanceof HTMLInputElement; + return ret; + }; + imports.wbg.__wbg_setautofocus_5d3aec51de5021e2 = function(arg0, arg1) { + getObject(arg0).autofocus = arg1 !== 0; + }; + imports.wbg.__wbg_setsize_9ec16303ce038acb = function(arg0, arg1) { + getObject(arg0).size = arg1 >>> 0; + }; + imports.wbg.__wbg_value_0627d4b1c27534e6 = function(arg0, arg1) { + var ret = getObject(arg1).value; + var ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; + }; + imports.wbg.__wbg_setvalue_2459f62386b6967f = function(arg0, arg1, arg2) { + getObject(arg0).value = getStringFromWasm0(arg1, arg2); + }; + imports.wbg.__wbg_writeText_3b86a6dbc18b261b = function(arg0, arg1, arg2) { + var ret = getObject(arg0).writeText(getStringFromWasm0(arg1, arg2)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_items_0e0d8664cb0c227c = function(arg0) { + var ret = getObject(arg0).items; + return addHeapObject(ret); + }; + imports.wbg.__wbg_files_d148fafe4f8ef096 = function(arg0) { + var ret = getObject(arg0).files; + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }; + imports.wbg.__wbg_getData_a4934f84b4074e8b = function() { return handleError(function (arg0, arg1, arg2, arg3) { + var ret = getObject(arg1).getData(getStringFromWasm0(arg2, arg3)); + var ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; + }, arguments) }; + imports.wbg.__wbg_length_b3892c671bcff0a9 = function(arg0) { + var ret = getObject(arg0).length; + return ret; + }; + imports.wbg.__wbg_get_eb708b1d3ad92ce5 = function(arg0, arg1) { + var ret = getObject(arg0)[arg1 >>> 0]; + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }; + imports.wbg.__wbg_matches_76fae292b8cd60a6 = function(arg0) { + var ret = getObject(arg0).matches; + return ret; + }; + imports.wbg.__wbg_now_559193109055ebad = function(arg0) { + var ret = getObject(arg0).now(); + return ret; + }; + imports.wbg.__wbg_identifier_87ee1c4654593a75 = function(arg0) { + var ret = getObject(arg0).identifier; + return ret; + }; + imports.wbg.__wbg_pageX_e47dc88281930930 = function(arg0) { + var ret = getObject(arg0).pageX; + return ret; + }; + imports.wbg.__wbg_pageY_b6b579adcea2948f = function(arg0) { + var ret = getObject(arg0).pageY; + return ret; + }; + imports.wbg.__wbg_force_c47d39a3ad56c12f = function(arg0) { + var ret = getObject(arg0).force; + return ret; + }; + imports.wbg.__wbg_newnoargs_be86524d73f67598 = function(arg0, arg1) { + var ret = new Function(getStringFromWasm0(arg0, arg1)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_call_888d259a5fefc347 = function() { return handleError(function (arg0, arg1) { + var ret = getObject(arg0).call(getObject(arg1)); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_resolve_d23068002f584f22 = function(arg0) { + var ret = Promise.resolve(getObject(arg0)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_then_2fcac196782070cc = function(arg0, arg1) { + var ret = getObject(arg0).then(getObject(arg1)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_then_8c2d62e8ae5978f7 = function(arg0, arg1, arg2) { + var ret = getObject(arg0).then(getObject(arg1), getObject(arg2)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_self_c6fbdfc2918d5e58 = function() { return handleError(function () { + var ret = self.self; + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_window_baec038b5ab35c54 = function() { return handleError(function () { + var ret = window.window; + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_globalThis_3f735a5746d41fbd = function() { return handleError(function () { + var ret = globalThis.globalThis; + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_global_1bc0b39582740e95 = function() { return handleError(function () { + var ret = global.global; + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbindgen_is_undefined = function(arg0) { + var ret = getObject(arg0) === undefined; + return ret; + }; + imports.wbg.__wbg_buffer_397eaa4d72ee94dd = function(arg0) { + var ret = getObject(arg0).buffer; + return addHeapObject(ret); + }; + imports.wbg.__wbg_new_99c38feff948285c = function(arg0) { + var ret = new Int16Array(getObject(arg0)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_new_a7ce447f15ff496f = function(arg0) { + var ret = new Uint8Array(getObject(arg0)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_set_969ad0a60e51d320 = function(arg0, arg1, arg2) { + getObject(arg0).set(getObject(arg1), arg2 >>> 0); + }; + imports.wbg.__wbg_length_1eb8fc608a0d4cdb = function(arg0) { + var ret = getObject(arg0).length; + return ret; + }; + imports.wbg.__wbg_new_8b45a9becdb89691 = function(arg0) { + var ret = new Float32Array(getObject(arg0)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_subarray_5208d7c1876d9ee7 = function(arg0, arg1, arg2) { + var ret = getObject(arg0).subarray(arg1 >>> 0, arg2 >>> 0); + return addHeapObject(ret); + }; + imports.wbg.__wbg_subarray_8b658422a224f479 = function(arg0, arg1, arg2) { + var ret = getObject(arg0).subarray(arg1 >>> 0, arg2 >>> 0); + return addHeapObject(ret); + }; + imports.wbg.__wbg_subarray_9e3273b330900f8c = function(arg0, arg1, arg2) { + var ret = getObject(arg0).subarray(arg1 >>> 0, arg2 >>> 0); + return addHeapObject(ret); + }; + imports.wbg.__wbg_instanceof_Memory_625744f21df3a5ec = function(arg0) { + var ret = getObject(arg0) instanceof WebAssembly.Memory; + return ret; + }; + imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { + var ret = debugString(getObject(arg1)); + var ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; + }; + imports.wbg.__wbindgen_throw = function(arg0, arg1) { + throw new Error(getStringFromWasm0(arg0, arg1)); + }; + imports.wbg.__wbindgen_rethrow = function(arg0) { + throw takeObject(arg0); + }; + imports.wbg.__wbindgen_memory = function() { + var ret = wasm.memory; + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_closure_wrapper565 = function(arg0, arg1, arg2) { + var ret = makeMutClosure(arg0, arg1, 158, __wbg_adapter_26); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_closure_wrapper566 = function(arg0, arg1, arg2) { + var ret = makeMutClosure(arg0, arg1, 158, __wbg_adapter_29); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_closure_wrapper568 = function(arg0, arg1, arg2) { + var ret = makeMutClosure(arg0, arg1, 158, __wbg_adapter_32); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_closure_wrapper570 = function(arg0, arg1, arg2) { + var ret = makeMutClosure(arg0, arg1, 158, __wbg_adapter_35); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_closure_wrapper573 = function(arg0, arg1, arg2) { + var ret = makeMutClosure(arg0, arg1, 158, __wbg_adapter_38); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_closure_wrapper575 = function(arg0, arg1, arg2) { + var ret = makeMutClosure(arg0, arg1, 158, __wbg_adapter_41); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_closure_wrapper577 = function(arg0, arg1, arg2) { + var ret = makeMutClosure(arg0, arg1, 158, __wbg_adapter_44); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_closure_wrapper579 = function(arg0, arg1, arg2) { + var ret = makeMutClosure(arg0, arg1, 158, __wbg_adapter_47); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_closure_wrapper581 = function(arg0, arg1, arg2) { + var ret = makeMutClosure(arg0, arg1, 158, __wbg_adapter_50); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_closure_wrapper583 = function(arg0, arg1, arg2) { + var ret = makeMutClosure(arg0, arg1, 158, __wbg_adapter_53); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_closure_wrapper668 = function(arg0, arg1, arg2) { + var ret = makeMutClosure(arg0, arg1, 195, __wbg_adapter_56); + return addHeapObject(ret); + }; + + if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) { + input = fetch(input); + } + + + + const { instance, module } = await load(await input, imports); + + wasm = instance.exports; + init.__wbindgen_wasm_module = module; + + return wasm; +} + +wasm_bindgen = Object.assign(init, __exports); + +})(); diff --git a/egui_node_graph_example/docs/eframe_template_bg.wasm b/egui_node_graph_example/docs/eframe_template_bg.wasm new file mode 100644 index 0000000..003ab82 Binary files /dev/null and b/egui_node_graph_example/docs/eframe_template_bg.wasm differ diff --git a/egui_node_graph_example/docs/favicon.ico b/egui_node_graph_example/docs/favicon.ico new file mode 100755 index 0000000..61ad031 Binary files /dev/null and b/egui_node_graph_example/docs/favicon.ico differ diff --git a/egui_node_graph_example/docs/icon-256.png b/egui_node_graph_example/docs/icon-256.png new file mode 100644 index 0000000..ae72287 Binary files /dev/null and b/egui_node_graph_example/docs/icon-256.png differ diff --git a/egui_node_graph_example/docs/index.html b/egui_node_graph_example/docs/index.html new file mode 100644 index 0000000..61ea670 --- /dev/null +++ b/egui_node_graph_example/docs/index.html @@ -0,0 +1,152 @@ + + + + + + + + + eframe template + + + + + + + + +
+ Loading…   +
+
+ + + + + + + + + + + + diff --git a/egui_node_graph_example/docs/manifest.json b/egui_node_graph_example/docs/manifest.json new file mode 100644 index 0000000..204413e --- /dev/null +++ b/egui_node_graph_example/docs/manifest.json @@ -0,0 +1,14 @@ +{ + "name": "Egui Template PWA", + "short_name": "egui-template-pwa", + "icons": [{ + "src": "./icon-256.png", + "sizes": "256x256", + "type": "image/png" + }], + "lang": "en-US", + "start_url": "./index.html", + "display": "standalone", + "background_color": "white", + "theme_color": "white" + } diff --git a/egui_node_graph_example/docs/sw.js b/egui_node_graph_example/docs/sw.js new file mode 100644 index 0000000..7ecd229 --- /dev/null +++ b/egui_node_graph_example/docs/sw.js @@ -0,0 +1,25 @@ +var cacheName = 'egui-template-pwa'; +var filesToCache = [ + './', + './index.html', + './eframe_template.js', + './eframe_template_bg.wasm', +]; + +/* Start the service worker and cache all of the app's content */ +self.addEventListener('install', function (e) { + e.waitUntil( + caches.open(cacheName).then(function (cache) { + return cache.addAll(filesToCache); + }) + ); +}); + +/* Serve cached content when offline */ +self.addEventListener('fetch', function (e) { + e.respondWith( + caches.match(e.request).then(function (response) { + return response || fetch(e.request); + }) + ); +}); diff --git a/egui_node_graph_example/setup_web.bat b/egui_node_graph_example/setup_web.bat new file mode 100644 index 0000000..5545ec6 --- /dev/null +++ b/egui_node_graph_example/setup_web.bat @@ -0,0 +1,7 @@ +@REM Pre-requisites: +rustup target add wasm32-unknown-unknown +cargo install wasm-bindgen-cli +cargo update -p wasm-bindgen + +@REM For local tests with `./start_server`: +cargo install basic-http-server diff --git a/egui_node_graph_example/setup_web.sh b/egui_node_graph_example/setup_web.sh new file mode 100755 index 0000000..8d5b5b2 --- /dev/null +++ b/egui_node_graph_example/setup_web.sh @@ -0,0 +1,10 @@ +#!/bin/bash +set -eu + +# Pre-requisites: +rustup target add wasm32-unknown-unknown +cargo install wasm-bindgen-cli +cargo update -p wasm-bindgen + +# For local tests with `./start_server`: +cargo install basic-http-server diff --git a/egui_node_graph_example/src/app.rs b/egui_node_graph_example/src/app.rs new file mode 100644 index 0000000..cc5f995 --- /dev/null +++ b/egui_node_graph_example/src/app.rs @@ -0,0 +1,245 @@ +use eframe::{ + egui::{self, DragValue}, + epi, +}; +use egui_node_graph::*; + +// ========= First, define your user data types ============= + +/// The NodeData holds a custom data struct inside each node. It's useful to +/// store additional information that doesn't live in parameters. For this +/// simple example we don't really want to store anything. +pub struct MyNodeData; + +/// `DataType`s are what defines the possible range of connections when +/// attaching two ports together. The graph UI will make sure to not allow +/// attaching incompatible datatypes. +#[derive(PartialEq, Eq)] +pub enum MyDataType { + Scalar, + Vec2, +} + +/// In the graph, input parameters can optionally have a constant value. This +/// value can be directly edited in a widget inside the node itself. +/// +/// There will usually be a correspondence between DataTypes and ValueTypes. But +/// this library makes no attempt to check this consistency. For instance, it is +/// up to the user code in this example to make sure no parameter is created +/// with a DataType of Scalar and a ValueType of Vec2. +pub enum MyValueType { + Vec2 { value: egui::Vec2 }, + Scalar { value: f32 }, +} + +/// NodeKind is a mechanism to define node "templates". It's what the graph will +/// display in the "new node" popup. The user code needs to tell the library how +/// to convert a NodeKind into a Node. +#[derive(Clone, Copy)] +pub enum MyNodeKind { + AddScalar, + SubtractScalar, + VectorTimesScalar, + AddVector, +} + +// =========== Then, you need to implement some traits ============ + +// A trait for the data types, to tell the library how to display them +impl DataTypeTrait for MyDataType { + fn data_type_color(&self) -> egui::Color32 { + match self { + MyDataType::Scalar => egui::Color32::from_rgb(38, 109, 211), + MyDataType::Vec2 => egui::Color32::from_rgb(238, 207, 109), + } + } + + fn name(&self) -> &str { + match self { + MyDataType::Scalar => "scalar", + MyDataType::Vec2 => "2d vector", + } + } +} + +// A trait for the node kinds, which tells the library how to build new nodes +// from the templates in the node finder +impl NodeKindTrait for MyNodeKind { + type NodeData = MyNodeData; + type DataType = MyDataType; + type ValueType = MyValueType; + + fn node_finder_label(&self) -> &str { + match self { + MyNodeKind::AddScalar => "Scalar add", + MyNodeKind::SubtractScalar => "Scalar subtract", + MyNodeKind::VectorTimesScalar => "Vector times scalar", + MyNodeKind::AddVector => "Vector subtract", + } + } + + fn node_graph_label(&self) -> String { + // It's okay to delegate this to node_finder_label if you don't want to + // show different names in the node finder and the node itself. + self.node_finder_label().into() + } + + fn user_data(&self) -> Self::NodeData { + MyNodeData + } + + fn build_node( + &self, + graph: &mut Graph, + node_id: NodeId, + ) { + // The nodes are created empty by default. This function needs to take + // care of creating the desired inputs and outputs based on the template + + // We define some macros here to avoid boilerplate. Note that this is + // entirely optional. + macro_rules! input { + (scalar $name:expr) => { + graph.add_input_param( + node_id, + $name.to_string(), + MyDataType::Scalar, + MyValueType::Scalar { value: 0.0 }, + InputParamKind::ConnectionOrConstant, + true, + ); + }; + (vector $name:expr) => { + graph.add_input_param( + node_id, + $name.to_string(), + MyDataType::Vec2, + MyValueType::Vec2 { + value: egui::vec2(0.0, 0.0), + }, + InputParamKind::ConnectionOrConstant, + true, + ); + }; + } + + macro_rules! output { + (scalar $name:expr) => { + graph.add_output_param(node_id, $name.to_string(), MyDataType::Scalar); + }; + (vector $name:expr) => { + graph.add_output_param(node_id, $name.to_string(), MyDataType::Vec2); + }; + } + + match self { + MyNodeKind::AddScalar => { + // The first input param doesn't use the macro so we can comment + // it in more detail. + graph.add_input_param( + node_id, + // This is the name of the parameter. Can be later used to + // retrieve the value. Parameter names should be unique. + "A".into(), + // The data type for this input. In this case, a scalar + MyDataType::Scalar, + // The value type for this input. We store zero as default + MyValueType::Scalar { value: 0.0 }, + // The input parameter kind. This allows defining whether a + // parameter accepts input connections and/or an inline + // widget to set its value. + InputParamKind::ConnectionOrConstant, + true, + ); + input!(scalar "B"); + output!(scalar "out"); + } + MyNodeKind::SubtractScalar => { + input!(scalar "A"); + input!(scalar "B"); + output!(scalar "out"); + } + MyNodeKind::VectorTimesScalar => { + input!(scalar "scalar"); + input!(vector "vector"); + output!(vector "out"); + } + MyNodeKind::AddVector => { + input!(vector "v1"); + input!(vector "v2"); + output!(vector "out"); + } + } + } +} + +pub struct AllMyNodeKinds; +impl NodeKindIter for AllMyNodeKinds { + type Item = MyNodeKind; + + fn all_kinds(&self) -> Box + '_> { + // This function must return a list of node kinds, which the node finder + // will use to display it to the user. Crates like strum can reduce the + // boilerplate in enumerating all variants of an enum. + // + // The Box here is required because traits in Rust cannot be generic + // over return parameters, so you can't return an iterator. + Box::new( + [ + MyNodeKind::AddScalar, + MyNodeKind::SubtractScalar, + MyNodeKind::VectorTimesScalar, + MyNodeKind::AddVector, + ] + .iter(), + ) + } +} + +impl InputParamWidget for MyValueType { + fn value_widget(&mut self, param_name: &str, ui: &mut egui::Ui) { + // This trait is used to tell the library which UI to display for the + // inline parameter widgets. + match self { + MyValueType::Vec2 { value } => { + ui.label(param_name); + ui.horizontal(|ui| { + ui.label("x"); + ui.add(DragValue::new(&mut value.x)); + ui.label("y"); + ui.add(DragValue::new(&mut value.y)); + }); + } + MyValueType::Scalar { value } => { + ui.horizontal(|ui| { + ui.label(param_name); + ui.add(DragValue::new(value)); + }); + } + } + } +} + +pub struct NodeGraphExample { + state: GraphEditorState, +} + +impl Default for NodeGraphExample { + fn default() -> Self { + Self { + state: GraphEditorState::new(1.0), + } + } +} + +impl epi::App for NodeGraphExample { + fn name(&self) -> &str { + "eframe template" + } + + /// Called each time the UI needs repainting, which may be many times per second. + /// Put your widgets into a `SidePanel`, `TopPanel`, `CentralPanel`, `Window` or `Area`. + fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) { + self.state.draw_graph_editor(ctx, AllMyNodeKinds); + } +} diff --git a/egui_node_graph_example/src/lib.rs b/egui_node_graph_example/src/lib.rs new file mode 100644 index 0000000..b2faa05 --- /dev/null +++ b/egui_node_graph_example/src/lib.rs @@ -0,0 +1,23 @@ +#![forbid(unsafe_code)] +#![cfg_attr(not(debug_assertions), deny(warnings))] // Forbid warnings in release builds +#![warn(clippy::all, rust_2018_idioms)] + +mod app; +pub use app::NodeGraphExample; + +// ---------------------------------------------------------------------------- +// When compiling for web: + +#[cfg(target_arch = "wasm32")] +use eframe::wasm_bindgen::{self, prelude::*}; + +/// This is the entry-point for all the web-assembly. +/// This is called once from the HTML. +/// It loads the app, installs some callbacks, then returns. +/// You can add more callbacks like this if you want to call in to your code. +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen] +pub fn start(canvas_id: &str) -> Result<(), eframe::wasm_bindgen::JsValue> { + let app = NodeGraphExample::default(); + eframe::start_web(canvas_id, Box::new(app)) +} diff --git a/egui_node_graph_example/src/main.rs b/egui_node_graph_example/src/main.rs new file mode 100644 index 0000000..7479fad --- /dev/null +++ b/egui_node_graph_example/src/main.rs @@ -0,0 +1,11 @@ +#![forbid(unsafe_code)] +#![cfg_attr(not(debug_assertions), deny(warnings))] // Forbid warnings in release builds +#![warn(clippy::all, rust_2018_idioms)] + +// When compiling natively: +#[cfg(not(target_arch = "wasm32"))] +fn main() { + let app = egui_node_graph_example::NodeGraphExample::default(); + let native_options = eframe::NativeOptions::default(); + eframe::run_native(Box::new(app), native_options); +} diff --git a/egui_node_graph_example/start_server.bat b/egui_node_graph_example/start_server.bat new file mode 100644 index 0000000..0fb9d97 --- /dev/null +++ b/egui_node_graph_example/start_server.bat @@ -0,0 +1,11 @@ +@echo off + +@REM Starts a local web-server that serves the contents of the `doc/` folder, +@REM which is the folder to where the web version is compiled. + +cargo install basic-http-server + +echo "open http://localhost:8080" + +(cd docs && basic-http-server --addr 127.0.0.1:8080 .) +@REM (cd docs && python3 -m http.server 8080 --bind 127.0.0.1) diff --git a/egui_node_graph_example/start_server.sh b/egui_node_graph_example/start_server.sh new file mode 100755 index 0000000..b586103 --- /dev/null +++ b/egui_node_graph_example/start_server.sh @@ -0,0 +1,12 @@ +#!/bin/bash +set -eu + +# Starts a local web-server that serves the contents of the `doc/` folder, +# which is the folder to where the web version is compiled. + +cargo install basic-http-server + +echo "open http://localhost:8080" + +(cd docs && basic-http-server --addr 127.0.0.1:8080 .) +# (cd docs && python3 -m http.server 8080 --bind 127.0.0.1)