window.rs 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. // This is based on the sprite example from piston-examples, available at https://github.com/PistonDevelopers/piston-examples
  2. use ai_behavior::{Action, Sequence, Wait, WaitForever, While};
  3. use ion_shell::{
  4. builtins::{BuiltinFunction, Status},
  5. types, Shell,
  6. };
  7. use piston_window::*;
  8. use sprite::*;
  9. use std::{cell::RefCell, fs::File, path::Path, rc::Rc};
  10. fn main() {
  11. // This is specific to piston. It does not matter
  12. // Skip to the next *****
  13. let (width, height) = (300, 300);
  14. let opengl = OpenGL::V3_2;
  15. let mut window: PistonWindow = WindowSettings::new("piston: sprite", (width, height))
  16. .exit_on_esc(true)
  17. .graphics_api(opengl)
  18. .build()
  19. .unwrap();
  20. let id;
  21. let scene = RefCell::new(Scene::new());
  22. let mut texture_context = TextureContext {
  23. factory: window.factory.clone(),
  24. encoder: window.factory.create_command_buffer().into(),
  25. };
  26. let tex = Rc::new(
  27. Texture::from_path(
  28. &mut texture_context,
  29. Path::new("./examples/rust.png"),
  30. Flip::None,
  31. &TextureSettings::new(),
  32. )
  33. .expect("This example is meant to be run in the crate's root"),
  34. );
  35. let mut sprite = Sprite::from_texture(tex.clone());
  36. sprite.set_position(width as f64 / 2.0, height as f64 / 2.0);
  37. id = scene.borrow_mut().add_child(sprite);
  38. // Run a sequence of animations.
  39. let seq = RefCell::new(Sequence(vec![
  40. Action(Ease(EaseFunction::CubicOut, Box::new(ScaleTo(2.0, 0.5, 0.5)))),
  41. Action(Ease(EaseFunction::BounceOut, Box::new(MoveBy(1.0, 0.0, 100.0)))),
  42. Action(Ease(EaseFunction::ElasticOut, Box::new(MoveBy(2.0, 0.0, -100.0)))),
  43. Action(Ease(EaseFunction::BackInOut, Box::new(MoveBy(1.0, 0.0, -100.0)))),
  44. Wait(0.5),
  45. Action(Ease(EaseFunction::ExponentialInOut, Box::new(MoveBy(1.0, 0.0, 100.0)))),
  46. Action(Blink(1.0, 5)),
  47. While(
  48. Box::new(WaitForever),
  49. vec![
  50. Action(Ease(EaseFunction::QuadraticIn, Box::new(FadeOut(1.0)))),
  51. Action(Ease(EaseFunction::QuadraticOut, Box::new(FadeIn(1.0)))),
  52. ],
  53. ),
  54. ]));
  55. scene.borrow_mut().run(id, &seq.borrow());
  56. let rotate =
  57. RefCell::new(Action(Ease(EaseFunction::ExponentialInOut, Box::new(RotateTo(2.0, 360.0)))));
  58. scene.borrow_mut().run(id, &rotate.borrow());
  59. // *****
  60. // Because the shell and your application generally live side by side, you won't be able to
  61. // create builtins satisfying at compile-time the rust borrowing rules
  62. //
  63. // A `RefCell` is generally the solution
  64. let colors = RefCell::new([1.0, 1.0, 1.0, 1.0]);
  65. // Create a custom builtin.
  66. // Builtins provide means by which the user configuration can notify your application of a
  67. // change. You must provide a help description along with each one of them
  68. let toggle_animation_builtin: BuiltinFunction = &|_args, _shell| {
  69. let mut scene = scene.borrow_mut();
  70. scene.toggle(id, &seq.borrow());
  71. scene.toggle(id, &rotate.borrow());
  72. // The `Status` struct is an helper to avoid dealing with return codes and error messages
  73. // directly.
  74. //
  75. // Rather than printing to stdout and then to return 2, you can now leave the job to
  76. // ion and call Status::bad_argument(<error message>). Where possible, builtins should use
  77. // the helper
  78. Status::SUCCESS
  79. };
  80. // Another builtin
  81. let set_background_builtin: BuiltinFunction = &|args, _shell| {
  82. let inner = |colors: &[types::Str]| -> Result<_, std::num::ParseFloatError> {
  83. let red = colors[0].parse::<f32>()?;
  84. let green = colors[1].parse::<f32>()?;
  85. let blue = colors[2].parse::<f32>()?;
  86. let alpha = match colors.get(3) {
  87. Some(alpha) => Some(alpha.parse::<f32>()?),
  88. None => None,
  89. };
  90. Ok((red, green, blue, alpha))
  91. };
  92. if args.len() > 5 || args.len() < 4 {
  93. return Status::bad_argument(
  94. "Wrong number of arguments provided: please provide 3 or 4",
  95. );
  96. }
  97. match inner(&args[1..]) {
  98. Err(why) => Status::error(format!("Could not parse the input color: {}", why)),
  99. Ok((red, green, blue, alpha)) => {
  100. let colors = &mut colors.borrow_mut();
  101. colors[0] = red.max(0.).min(1.);
  102. colors[1] = green.max(0.).min(1.);
  103. colors[2] = blue.max(0.).min(1.);
  104. if let Some(alpha) = alpha {
  105. colors[3] = alpha.max(0.).min(1.);
  106. }
  107. Status::SUCCESS
  108. }
  109. }
  110. };
  111. // Create a shell with default configuration as well as recommended builtins
  112. let mut shell = Shell::default();
  113. // Register the builtins, along with a short help text available with the `help` builtin
  114. shell.builtins_mut().add("toggle", toggle_animation_builtin, "toggle the animation");
  115. shell.builtins_mut().add("set_background", set_background_builtin, "set the background color");
  116. // Add global variables
  117. //
  118. // Variables should generally provided along with callbacks to allow to user to react to
  119. // changes. We'll leave it out in this example for the sake of simplicity
  120. let size = window.size();
  121. shell.variables_mut().set("WINDOW_WIDTH", size.width.to_string());
  122. shell.variables_mut().set("WINDOW_HEIGHT", size.height.to_string());
  123. // Load the config file. This is where a user can register callbacks, prepare itself, and setup
  124. // your application. All builtins and variables should be registered at this point
  125. //
  126. // Check for a global and a per-user config (here it lives in the same git repo, but in a real
  127. // application it should probably go to the ~/.config folder, or the recommended location on
  128. // your OS)
  129. if let Ok(file) =
  130. File::open("window-config.ion").or_else(|_| File::open("./examples/window-config.ion"))
  131. {
  132. if let Err(why) = shell.execute_command(file) {
  133. eprintln!("window: error in config file: {}", why);
  134. }
  135. }
  136. // Your application execution
  137. while let Some(e) = window.next() {
  138. scene.borrow_mut().event(&e);
  139. window.draw_2d(&e, |c, g, _| {
  140. clear(colors.clone().into_inner(), g);
  141. scene.borrow_mut().draw(c.transform, g);
  142. });
  143. if e.render_args().is_some() {
  144. // Get the on_render function
  145. if let Some(function) = shell.get_func("on_render") {
  146. // and then call it. N.B.: the first argument must always be ion
  147. if let Err(why) = shell.execute_function(&function, &["ion"]) {
  148. // check for errors
  149. eprintln!("window example: error in on_render callback: {}", why);
  150. }
  151. }
  152. }
  153. if let Some(Button::Keyboard(key)) = e.press_args() {
  154. if let Some(function) = shell.get_func("on_key") {
  155. if let Err(why) =
  156. // provide a parameter for the callback
  157. shell.execute_function(&function, &["ion", &key.code().to_string()])
  158. {
  159. eprintln!("window example: error in on_key callback: {}", why);
  160. }
  161. }
  162. }
  163. if let Some([x, y]) = e.mouse_cursor_args() {
  164. if let Some(function) = shell.get_func("on_mouse") {
  165. if let Err(why) =
  166. shell.execute_function(&function, &["ion", &x.to_string(), &y.to_string()])
  167. {
  168. eprintln!("window example: error in on_mouse callback: {}", why);
  169. }
  170. }
  171. }
  172. }
  173. }