indexing description: "[ Base class for interactive scenes. Descendants just have to implement the feature `initialize_scene' to fill the `main_container' with the contained objects. An EM_SCENE consists of: - An `event_loop' that runs the scene and dispatches events to subscribers. - A `screen' onto which the scene is drawn when running. - A `main_container' holding the drawable objects the scene is built of. When scene is running, all drawable objects contained in `main_container' get drawn onto `screen'. There are some handle_xxx features to handle important events for user interaction that descendants can redefine to interact. An EM_SCENE is supposed to be used as follows (i.e. by EM_APPLICATION): - initialize_scene: called first to initialize the scene (create contained drawable objects inside `main_container') - run: called after `initialize_scene' to run the scene - other features: called by event_loop.dispatch If you want to end the scene just call event_loop.stop. This will end the scene and the caller (i.e. the application) gets the control back. If you want that another scene is executed after your scene ends set `next_scene' accordingly. If you want to end the program set `next_scene' to `Void'. Use animation features to get animatable objects (i.e. sprites) running. Like this all animatable objects get animated before the scene is redrawn. EM_SCENE publishes mouse events to drawable objects it contains. Subclasses must call `make_scene' at creation. ]" date: "$Date$" revision: "$Revision$" deferred class EM_SCENE inherit EM_TIME_SINGLETON export {NONE} all end MEMORY export {NONE} all end EM_SHARED_SCENE export {NONE} all end feature {NONE} -- Initialization make_scene is -- Default initialization. do is_make_scene_called := True create main_container.make create animation_event create event_loop.make_poll create background_color.make_black end is_make_scene_called: BOOLEAN -- Is `make_scene' called? feature -- Initialization initialize_scene is -- Initialize the scene by filling `main_container' with drawables -- the scene consists of. deferred end feature -- Access next_scene: EM_SCENE -- Scene that should be executed after this scene ends. -- If next_scene = Void the program just ends. event_loop: EM_EVENT_LOOP -- Event loop that makes scene running screen: EM_VIDEO_SURFACE -- Surface where scene is drawed background_color: EM_COLOR -- Color used to fill `screen' before drawing scene -- (Descendants can set it to Void if they don't want to empty the screen on each redraw, -- set to black by default). feature -- Status report is_running: BOOLEAN -- Is scene currently running? -- Otherwise no events are dispatched and no animation will run right now. feature -- Status setting set_frame_counter_visibility (a_value: BOOLEAN) is -- If `a_value' is `True' make frame counter visible, -- else make it invisible. do display_frame_counter := a_value end feature -- Element change set_next_scene (a_scene: like next_scene) is -- Set `next_scene' to `a_scene'. do next_scene := a_scene ensure next_scene_set: next_scene = a_scene end feature -- Miscellaneous start_next_scene is -- Stop `Current' and advance to `next_scene'. do event_loop.stop end quit is -- Stop scene whitout starting another one. do next_scene := Void event_loop.stop end run (a_screen: EM_VIDEO_SURFACE) is -- Run the scene and show it on `a_screen'. require a_screen_not_void: a_screen /= Void do if display_frame_counter then create frame_counter.make end screen := a_screen event_loop.key_down_event.subscribe (agent handle_key_down_event (?)) event_loop.key_up_event.subscribe (agent handle_key_up_event (?)) event_loop.mouse_button_down_event.subscribe (agent handle_mouse_button_down_event (?)) event_loop.mouse_button_up_event.subscribe (agent handle_mouse_button_up_event (?)) event_loop.mouse_motion_event.subscribe (agent handle_mouse_motion_event (?)) event_loop.quit_event.subscribe (agent handle_quit_event (?)) event_loop.outside_event.subscribe (agent handle_outside_event) event_loop.outside_event.subscribe (agent animate) event_loop.outside_event.subscribe (agent redraw) event_loop.joystick_axis_event.subscribe (agent handle_joystick_axis_event (?)) event_loop.joystick_ball_event.subscribe (agent handle_joystick_ball_event (?)) event_loop.joystick_button_down_event.subscribe (agent handle_joystick_button_down_event (?)) event_loop.joystick_button_up_event.subscribe (agent handle_joystick_button_up_event (?)) event_loop.joystick_hat_event.subscribe (agent handle_joystick_hat_event (?)) if main_container /= Void then event_loop.mouse_button_down_event.subscribe (agent main_container.publish_mouse_event) event_loop.mouse_button_up_event.subscribe (agent main_container.publish_mouse_event) event_loop.mouse_motion_event.subscribe (agent main_container.publish_mouse_event) end is_running := True set_running_scene (Current) event_loop.dispatch set_running_scene (Void) is_running := False end feature -- Animation start_animating (an_animatable: EM_ANIMATABLE) is -- Subscribe `an_animatable' to be animated when `Current' is running. do if not animation_event.has (agent an_animatable.go_to_time) then animation_event.subscribe (agent an_animatable.go_to_time) end end stop_animating (an_animatable: EM_ANIMATABLE) is -- Unsubscribe `an_animatable' from beeing animated when `Current' is running. do if animation_event.has (agent an_animatable.go_to_time) then animation_event.unsubscribe (agent an_animatable.go_to_time) end end animate is -- Let all subscribed animatable objects perform their animation. -- (Calls `go_to_time' of animatable objects with current time tick) local cur_time: INTEGER do cur_time := time.ticks animation_event.publish ([cur_time]) end feature -- Events animation_event: EM_EVENT_TYPE [TUPLE [INTEGER]] -- Animation event, allows animatable objects to perform animation -- (i.e. moving them selves) before they get drawed. -- As an argument the reference time in milliseconds is passed -- up to which the animatable objects should draw them selves. -- This event gets published right before the scene is redrawed. feature -- Drawing redraw is -- Redraw `Current' scene. -- Causes to clear `screen' and redraw `main_container' onto it. -- Does nothing if `main_container' is `Void'. require screen_not_void: screen /= Void do if main_container /= Void then if background_color /= Void then screen.fill (background_color) end main_container.draw (screen) if display_frame_counter then frame_counter.draw (screen) end screen.redraw -- TODO: Do we have to ensure partial garbage collection after each redraw? end end feature {NONE} -- Keyboard management handle_key_down_event (a_keyboard_event: EM_KEYBOARD_EVENT) is -- Handle key down event. -- (Descendants can redefine this feature -- to do something when a key is pressed.) require a_keyboard_event_not_void: a_keyboard_event /= Void do end handle_key_up_event (a_keyboard_event: EM_KEYBOARD_EVENT) is -- Handle key up event. -- (Descendants can redefine this feature -- to do something when a key is released.) require a_keyboard_event_not_void: a_keyboard_event /= Void do end feature {NONE} -- Mouse management handle_mouse_button_down_event (a_mouse_button_event: EM_MOUSEBUTTON_EVENT) is -- Handle mouse button down event. -- (Descendants can redefine this feature -- to do something when a mouse button is pressed.) require a_mouse_button_event_not_void: a_mouse_button_event /= Void do end handle_mouse_button_up_event (a_mouse_button_event: EM_MOUSEBUTTON_EVENT) is -- Handle mouse button up event. -- (Descendants can redefine this feature -- to do something when a mouse button is released.) require a_mouse_button_event_not_void: a_mouse_button_event /= Void do end handle_mouse_motion_event (a_mouse_motion_event: EM_MOUSEMOTION_EVENT) is -- Handle mouse button move event. -- (Descendants can redefine this feature -- to do something when the mouse is moved) require a_mouse_motion_event_not_void: a_mouse_motion_event /= Void do end feature {NONE} -- Joystick management handle_joystick_axis_event (a_joystick_event: EM_JOYSTICK_AXIS_EVENT) is -- Handle joystick axis event. -- (Descendants can redefine this feature -- to do something when the mouse is moved) require a_joystick_event_not_void: a_joystick_event /= Void do end handle_joystick_ball_event (a_joystick_event: EM_JOYSTICK_BALL_EVENT) is -- Handle joystick ball event. -- (Descendants can redefine this feature -- to do something when the mouse is moved) require a_joystick_event_not_void: a_joystick_event /= Void do end handle_joystick_button_down_event (a_joystick_event: EM_JOYSTICK_BUTTON_EVENT) is -- Handle joystick button down event. -- (Descendants can redefine this feature -- to do something when the mouse is moved) require a_joystick_event_not_void: a_joystick_event /= Void do end handle_joystick_button_up_event (a_joystick_event: EM_JOYSTICK_BUTTON_EVENT) is -- Handle joystick button up event. -- (Descendants can redefine this feature -- to do something when the mouse is moved) require a_joystick_event_not_void: a_joystick_event /= Void do end handle_joystick_hat_event (a_joystick_event: EM_JOYSTICK_HAT_EVENT) is -- Handle joystick hat event. -- (Descendants can redefine this feature -- to do something when the mouse is moved) require a_joystick_event_not_void: a_joystick_event /= Void do end feature {NONE} -- Miscellaneous event handlers handle_quit_event (a_quit_event: EM_QUIT_EVENT) is -- Handle quit event. -- If you override this feature you have to either call -- the original version or `quit' yourself. -- (Descendants can redefine this feature -- to do something when application is quit.) require a_quit_event_not_void: a_quit_event /= Void do quit end handle_outside_event is -- Handle the outside event. -- (Descendants can redefine this feature -- to do something after each event loop pass.) do end feature {NONE} -- Implementation main_container: EM_DRAWABLE_CONTAINER [EM_DRAWABLE] -- Container that contains all drawable objects the scene is built of. frame_counter: EM_FRAME_COUNTER -- The frame counter display_frame_counter: BOOLEAN -- Is the frame counter displayed? invariant make_scene_called: is_make_scene_called animation_event_not_void: animation_event /= Void event_loop_not_void: event_loop /= Void screen_not_void_when_running: is_running implies screen /= Void end