bouncing_balls.sccd 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. Diagram(name = 'Bouncing_Balls',
  2. author = 'Simon Van Mierlo+Joeri Exelmans+Raphael Mannadiar',
  3. description = 'Tkinter frame with bouncing balls in it.'):
  4. Top {}
  5. Inport(name = 'ui')
  6. Class(name = 'MainApp', default = True):
  7. Association(name = 'fields', class = 'Field')
  8. Constructor {
  9. self.nr_of_fields = 0
  10. ui->bind_event(ui.window, ui.EVENTS.WINDOW_CLOSE, self.controller, 'window_close')
  11. }
  12. StateMachine:
  13. initial = 'running'
  14. State('running'):
  15. initial = 'root'
  16. Orthogonal('root'):
  17. State('main_behaviour'):
  18. initial = 'initializing'
  19. State('initializing'):
  20. Transition(target='../running'):
  21. raise += { create_field() }
  22. State('running'):
  23. Transition(target='.'):
  24. event = button_pressed(event_name: str)
  25. guard = {event_name == 'create_new_field'}
  26. raise += { create_field() }
  27. State('cd_behaviour'):
  28. initial = 'waiting'
  29. State('waiting'):
  30. Transition(target='../creating'):
  31. event = create_field()
  32. raise += { scope=cd, create_instance("fields") }
  33. Transition(target='../check_nr_of_fields'):
  34. event = delete_field(association_name: str)
  35. raise += { scope=cd, delete_instance(association_name) }
  36. Actions {
  37. self.nr_of_fields -= 1
  38. }
  39. State('creating'):
  40. Transition(target='../waiting'):
  41. event = instance_created(association_name: string)
  42. raise += { scope=cd, start_instance(association_name) }
  43. raise += { scope=narrow, target=association_name, set_association_name(association_name) }
  44. Actions {
  45. self.nr_of_fields += 1
  46. }
  47. State('check_nr_of_fields'):
  48. Transition(target='../../../stopped'):
  49. guard = {self.nr_of_fields == 0}
  50. Actions {
  51. ui->close_window(ui.window)
  52. }
  53. Transition(target='../waiting'):
  54. guard = {self.nr_of_fields != 0}
  55. State('stopped')
  56. Class(name = 'Field'):
  57. Attribute(name='canvas')
  58. Attribute(name='field_window')
  59. Inport(name = 'field_ui')
  60. Association(name = 'balls', class = 'Ball')
  61. Association(name = 'buttons', class = 'Button')
  62. Association(name = 'parent', class = 'MainApp', min = 1, max = 1)
  63. Constructor {
  64. self.field_window = ui->new_window(400,450)
  65. self.canvas = ui->append_canvas(self.field_window,400,400,['background':'#eee'])
  66. ui->bind_event(self.field_window, ui.EVENTS.WINDOW_CLOSE, self.controller, 'window_close')
  67. ui->bind_event(self.field_window, ui.EVENTS.KEY_PRESS, self.controller, 'key_press')
  68. ui->bind_event(self.canvas.element, ui.EVENTS.MOUSE_RIGHT_CLICK, self.controller,'right_click', self.inports["field_ui"])
  69. ui->bind_event(self.canvas.element, ui.EVENTS.MOUSE_MOVE, self.controller, 'mouse_move')
  70. ui->bind_event(self.canvas.element, ui.EVENTS.MOUSE_RELEASE, self.controller, 'mouse_release')
  71. }
  72. Destructor {
  73. ui->close_window(self.field_window)
  74. }
  75. StateMachine:
  76. initial = 'root'
  77. State('root'):
  78. initial = 'waiting'
  79. State('waiting'):
  80. Transition(target='../initializing'):
  81. event = set_association_name(association_name: str)
  82. Actions {
  83. self.association_name = association_name
  84. }
  85. State('initializing'):
  86. Transition(target='../creating'):
  87. raise += { scope=cd, create_instance('buttons', "Button", self, 'create_new_field', 'Spawn New Window') }
  88. State('creating'):
  89. Transition(target='../packing'):
  90. event = instance_created(association_name: string)
  91. raise += { scope=cd, start_instance(association_name) }
  92. State('packing'):
  93. Transition(target='../running'):
  94. event = button_created()
  95. Orthogonal('running'):
  96. Transition(port = 'ui', target='../deleting'):
  97. event = window_close(window: Window)
  98. guard = { window == self.field_window or window == ui.window }
  99. raise += { scope=narrow, target='balls',delete_self() }
  100. raise += { scope=cd, delete_instance("buttons") }
  101. State('main_behaviour'):
  102. initial = 'running'
  103. State('running'):
  104. Transition(port = 'field_ui', target='../creating'):
  105. event = right_click(x: int, y: int, button: Button)
  106. raise += { scope=cd, create_instance('balls', "Ball", self.canvas, x, y, self.field_window) }
  107. State('creating'):
  108. Transition(target='../running'):
  109. event = instance_created(association_name: string)
  110. raise += { scope=cd, start_instance(association_name) }
  111. raise += { scope=narrow, target=association_name, set_association_name(association_name) }
  112. State('deleting_behaviour'):
  113. initial = 'running'
  114. State('running'):
  115. Transition(target='.'):
  116. event = delete_ball(association_name: str)
  117. raise += { scope=cd, delete_instance(association_name) }
  118. State('child_behaviour'):
  119. initial = 'listening'
  120. State('listening'):
  121. Transition(target='.'):
  122. event = button_pressed(event_name: str)
  123. raise += { scope=narrow, target='parent', button_pressed(event_name) }
  124. State('deleting'):
  125. Transition(after=0.05, target='../deleted'):
  126. raise += { scope=narrow, target='parent', delete_field(self.association_name) }
  127. State('deleted')
  128. Class(name = 'Button'):
  129. Association(name = 'parent', class = 'Field', min = 1, max = 1)
  130. Constructor(parent: Field, event_name: str, button_text: str) {
  131. self.event_name = event_name
  132. Button button = ui->append_button(parent.field_window, event_name)
  133. ui->bind_event(button.element, ui.EVENTS.MOUSE_CLICK, self.controller, 'mouse_click')
  134. }
  135. StateMachine:
  136. initial = 'initializing'
  137. State('initializing'):
  138. Transition(target='../running'):
  139. raise += { scope=narrow, target='parent', button_created() }
  140. State('running'):
  141. Transition(port = 'ui', target='.'):
  142. event = mouse_click(x: int, y: int, button: Button)
  143. guard = {button == ui.MOUSE_BUTTONS.LEFT}
  144. raise += { scope=narrow, target='parent', button_pressed(self.event_name) }
  145. Class(name = 'Ball'):
  146. Association(name = 'parent', class = 'Field', min = 1, max = 1)
  147. Inport(name = 'ball_ui')
  148. Attribute(name = 'canvas')
  149. Attribute(name = 'element')
  150. Attribute(name = 'field_window')
  151. Constructor(canvas, x, y, field_window) {
  152. self.canvas = canvas
  153. self.field_window = field_window
  154. self.r = 20.0
  155. self.vel = ['x': utils->random() * 2.0 - 1.0, 'y': utils->random() * 2.0 - 1.0]
  156. self.mouse_pos = ['':'']
  157. self.smooth = 0.4
  158. Circle circle = self.canvas->add_circle(x, y, self.r, ['fill':'#000'])
  159. ui->bind_event(circle, ui.EVENTS.MOUSE_PRESS, self.controller, 'mouse_press', self.inports["ball_ui"])
  160. ui->bind_event(circle, ui.EVENTS.MOUSE_RIGHT_CLICK, self.controller, 'right_click')
  161. self.element = circle
  162. }
  163. Destructor {
  164. self.canvas->remove_element(self.element)
  165. }
  166. StateMachine:
  167. initial = 'main_behaviour'
  168. State('main_behaviour'):
  169. initial = 'initializing'
  170. State('initializing'):
  171. Transition(target='../bouncing'):
  172. event = set_association_name(association_name: str)
  173. Actions {
  174. self.association_name = association_name
  175. }
  176. State('bouncing'):
  177. Transition(after=0.01, target='.'):
  178. Actions {
  179. Integer pos = self.element->get_position()
  180. if pos.x-self.r <= 0 or pos.x+self.r >= self.canvas.width:
  181. self.vel['x'] = -self.vel['x']
  182. end
  183. if pos.y-self.r <= 0 or pos.y+self.r >= self.canvas.height:
  184. self.vel['y'] = -self.vel['y']
  185. end
  186. self.element->move(self.vel['x'], self.vel['y'])
  187. }
  188. Transition(port = 'ball_ui', target='../selected'):
  189. event = mouse_press(x: int,y: int,button: Button)
  190. guard = {button == ui.MOUSE_BUTTONS.LEFT}
  191. Actions {
  192. self.element->set_color("#ff0")
  193. }
  194. State('dragging'):
  195. Transition(port = 'ui', target='.'):
  196. event = mouse_move(x: int, y: int, button: Button)
  197. Actions {
  198. Integer dx = x - self.mouse_pos['x']
  199. Integer dy = y - self.mouse_pos['y']
  200. self.element->move(dx, dy)
  201. Integer pos = self.element->get_position()
  202. if pos.x - self.r <= 0 :
  203. pos.x = self.r + 1
  204. else:
  205. if pos.x + self.r >= self.canvas.width :
  206. pos.x = self.canvas.width - self.r - 1
  207. end
  208. end
  209. if pos.y - self.r <= 0 :
  210. pos.y = self.r + 1
  211. else:
  212. if pos.y+self.r >= self.canvas.height :
  213. pos.y = self.canvas.height-self.r - 1
  214. end
  215. end
  216. self.element->set_position(pos.x, pos.y)
  217. self.mouse_pos = ['x':x, 'y':y]
  218. self.vel = [
  219. 'x': (1 - self.smooth)*dx + self.smooth*self.vel['x'],
  220. 'y': (1 - self.smooth)*dy + self.smooth*self.vel['y']
  221. ]
  222. }
  223. Transition(port = 'ui', target='../bouncing'):
  224. event = mouse_release(x: int, y: int)
  225. Actions {
  226. self.element->set_color("#f00")
  227. }
  228. State('selected'):
  229. Transition(port = 'ball_ui', target='../dragging'):
  230. event = mouse_press(x: int, y: int, button: Button)
  231. guard = {button == ui.MOUSE_BUTTONS.LEFT}
  232. Actions {
  233. self.mouse_pos = ['x':x, 'y':y]
  234. }
  235. Transition(port = 'ui', target='.'):
  236. event = key_press(key: Key, active_window: Window)
  237. guard = {key == ui.KEYCODES.DELETE and active_window == self.field_window}
  238. raise += { scope=local, delete_self() }
  239. Transition(target='../deleted'):
  240. event = delete_self()
  241. raise += { scope=narrow, target='parent', delete_ball(self.association_name) }
  242. State('deleted')