indexing description: "[ A scene that can contain multiple components. Components have to be subclasses of EM_COMPONENT. Add new components with `add_component'. This enables mixing of widgets, 2D and 3D in one scene. Implemented components are: - EM_WIDGET: Display a widget. - EM_WINDOW: Display a window (a special EM_WIDGET). - EM_2D_COMPONENT: Use an EM_SURFACE to draw the content of the component. - EM_3D_COMPONENT: Use OpenGL to draw the component. The next scene can be set and started using `set_next_scene' and `start_next_scene'. Subclasses must call `make_component_scene' at creation. ]" date: "$Date$" revision: "$Revision$" class EM_COMPONENT_SCENE inherit EM_SCENE redefine start_next_scene, handle_mouse_button_down_event, handle_mouse_button_up_event, handle_mouse_motion_event, handle_key_down_event, handle_key_up_event, handle_joystick_axis_event, handle_joystick_ball_event, handle_joystick_button_down_event, handle_joystick_button_up_event, handle_joystick_hat_event, handle_update_event end EM_SHARED_THEME export {NONE} all end EM_SHARED_WIDGET_OPTIONS export {NONE} all end EM_SHARED_COMPONENT_EVENT_QUEUE export {NONE} all end EM_SHARED_SUBSYSTEMS export {NONE} all end EM_SHARED_FOCUS export {NONE} all end EM_CONSTANTS export {NONE} all end EMGL_DRAW export {NONE} all end create make_component_scene feature {NONE} -- Initialisation make_component_scene is -- Initialise component scene. do is_make_component_scene_called := True make_scene create components_impl.make end is_make_component_scene_called: BOOLEAN -- Is `make_component_scene' called? initialize_scene is -- Initialise scene (from EM_SCENE). do Component_event_queue.process_events end feature -- Access components: DS_LINEAR [EM_COMPONENT] is -- List of components do Result := components_impl ensure components_not_void: Result /= Void end component_at_position (a_x, a_y: INTEGER): EM_COMPONENT is -- Top most component at position `a_x' `a_y' local cursor: DS_BILINKED_LIST_CURSOR [EM_COMPONENT] component: EM_COMPONENT do from cursor := components_impl.new_cursor cursor.finish until cursor.off or Result /= Void loop component := cursor.item if component.is_visible and component.x < a_x and component.x + component.width > a_x and component.y < a_y and component.y + component.height > a_y then Result := component end cursor.back end end feature -- Measurement width: INTEGER is -- Width of scene do Result := Video_subsystem.video_surface_width end height: INTEGER is -- Height of scene do Result := Video_subsystem.video_surface_height end feature -- Status report has_component (a_component: EM_COMPONENT): BOOLEAN is -- Does `Current' has `a_component' to display? require a_component_not_void: a_component /= Void do Result := components_impl.has (a_component) ensure consistent: Result = components.has (a_component) end feature -- Element change add_component (a_component: EM_COMPONENT) is -- Add `a_component' to `components'. require a_component_not_void: a_component /= Void a_component_not_present: not components.has (a_component) do components_impl.put_last (a_component) ensure a_component_added: components.has (a_component) components_size_incremented: components.count = old components.count + 1 end feature -- Removal remove_component (a_component: EM_COMPONENT) is -- Remove `a_component' from `components'. require a_component_not_void: a_component /= Void do components_impl.delete (a_component) ensure component_removed_if_present: old components.has (a_component) implies (not components.has (a_component)) components_size_decremented: old components.has (a_component) implies (components.count = old components.count - 1) end wipe_out_components is -- Remove all `components'. do components_impl.wipe_out ensure components_removed: components.is_empty end feature -- Miscellaneous start_next_scene is -- Stop `Current' and advance to `next_scene'. do Component_event_queue.process_events Precursor {EM_SCENE} end feature -- Drawing redraw is -- Redraw the scene. local cursor: DS_BILINKED_LIST_CURSOR [EM_COMPONENT] component: EM_COMPONENT do if video_subsystem.opengl_enabled then emgl_clear (Em_gl_color_buffer_bit | Em_gl_depth_buffer_bit) end from cursor := components_impl.new_cursor cursor.start until cursor.off loop component := cursor.item if component.is_visible then component.redraw end cursor.forth end if is_frame_counter_displayed then if video_subsystem.opengl_enabled and not video_subsystem.video_surface.is_opengl_blitting_enabled then video_subsystem.video_surface.enable_opengl_blitting end frame_counter.draw (screen) end screen.redraw end feature {NONE} -- Mouse management is_mouse_down: BOOLEAN -- Is mouse currently down? is_mouse_dragged: BOOLEAN -- Is mouse currently dragged? drag_start_component: EM_COMPONENT -- Component where mouse drag started mouse_focus: EM_COMPONENT -- Component with mouse focus screen_mouse_x: INTEGER -- X position of mouse on screen screen_mouse_y: INTEGER -- Y position of mouse on screen handle_mouse_button_down_event (event: EM_MOUSEBUTTON_EVENT) is -- Handle mouse button down event. do handle_input_activity if mouse_focus /= Void then if event.is_mouse_wheel_down or event.is_mouse_wheel_up then -- Mouse wheel -- do nothing here. send a mouse-wheel event to mouse_focus in handle_mouse_button_up_event else -- Mouse pressed is_mouse_down := True is_mouse_dragged := False drag_start_component := mouse_focus event.set_coordinates (event.screen_x-mouse_focus.x, event.screen_y-mouse_focus.y) Keyboard_focus.start_check mouse_focus.mouse_button_down (event) if Keyboard_focus.new_focus = Void and mouse_focus.is_keyboard_sensitive then Keyboard_focus.set_new_focus (mouse_focus) end Keyboard_focus.stop_check end else Keyboard_focus.set_current_focus (void) end end handle_mouse_button_up_event (event: EM_MOUSEBUTTON_EVENT) is -- Handle mouse button up event. do handle_input_activity if mouse_focus /= Void then if event.is_mouse_wheel_down then -- Mouse Wheel down mouse_focus.mouse_wheel_down elseif event.is_mouse_wheel_up then -- Mouse Wheel up mouse_focus.mouse_wheel_up else event.set_coordinates (event.screen_x-mouse_focus.x, event.screen_y-mouse_focus.y) mouse_focus.mouse_button_up (event) event.set_coordinates (event.screen_x-mouse_focus.x, event.screen_y-mouse_focus.y) if is_mouse_dragged then -- Mouse was dragged mouse_focus.mouse_drag_stop (event) is_mouse_dragged := False else -- mouse button clicked mouse_focus.mouse_clicked (event) end end is_mouse_down := False end end handle_mouse_motion_event (event: EM_MOUSEMOTION_EVENT) is -- Handle mouse motion event. local component: EM_COMPONENT do handle_input_activity component := component_at_position (event.x, event.y) set_mouse_focus (component) -- TODO: implement mouse dragging out of component and back in. -- if is_mouse_dragged then -- if component = drag_start_component then -- if mouse_focus = Void then -- set_mouse_focus (drag_start_component) -- end -- check -- drag_comonent_has_mouse_focus: drag_start_component = mouse_focus -- end -- else -- if mouse_focus /= Void then -- set_mouse_focus (Void) -- end -- end -- else -- set_mouse_focus (component) -- end screen_mouse_x := event.x screen_mouse_y := event.y if mouse_focus /= Void then if is_mouse_down then -- Mouse Dragging if not is_mouse_dragged then is_mouse_dragged := True event.set_coordinates (event.screen_x-mouse_focus.x, event.screen_y-mouse_focus.y) mouse_focus.mouse_drag_start (event) end event.set_coordinates (event.screen_x-mouse_focus.x, event.screen_y-mouse_focus.y) mouse_focus.mouse_dragged (event) else -- Mouse moving event.set_coordinates (event.screen_x-mouse_focus.x, event.screen_y-mouse_focus.y) mouse_focus.mouse_moved (event) end end end feature {EM_WINDOW} -- Mouse management set_mouse_focus (new_focus: EM_COMPONENT) is -- Set mouse focus to `new_focus'. require new_focus: new_focus /= Void implies has_component (new_focus) do if new_focus /= mouse_focus then if mouse_focus /= Void then mouse_focus.mouse_exited end mouse_focus := new_focus if mouse_focus /= Void then mouse_focus.mouse_entered Shared_mouse_focus.set_current_focus (mouse_focus) end end ensure mouse_focus_set: mouse_focus = new_focus end feature {NONE} -- Keyboard management handle_key_down_event (event: EM_KEYBOARD_EVENT) is -- Handle key down event. do handle_input_activity if Keyboard_focus.has_focus then keyboard_focus.current_focus.key_down_event.publish ([event]) end end handle_key_up_event (event: EM_KEYBOARD_EVENT) is -- Handle key up event. do handle_input_activity if Keyboard_focus.has_focus then keyboard_focus.current_focus.key_up_event.publish ([event]) end end feature {NONE} -- Joystick management handle_joystick_axis_event (event: EM_JOYSTICK_AXIS_EVENT) is -- Handle button down event do handle_input_activity if Joystick_focus.has_focus then Joystick_focus.publish_axis_event (event) end end handle_joystick_ball_event (event: EM_JOYSTICK_BALL_EVENT) is -- Handle ball event do handle_input_activity if Joystick_focus.has_focus then Joystick_focus.publish_ball_event (event) end end handle_joystick_button_down_event (event: EM_JOYSTICK_BUTTON_EVENT) is -- Handle button down event do handle_input_activity if Joystick_focus.has_focus then Joystick_focus.publish_button_down_event (event) end end handle_joystick_button_up_event (event: EM_JOYSTICK_BUTTON_EVENT) is -- Handle button up event do handle_input_activity if Joystick_focus.has_focus then Joystick_focus.publish_button_up_event (event) end end handle_joystick_hat_event (event: EM_JOYSTICK_HAT_EVENT) is -- Handle hat event do handle_input_activity if Joystick_focus.has_focus then Joystick_focus.publish_hat_event (event) end end feature {NONE} -- Implementation components_impl: DS_BILINKED_LIST [EM_COMPONENT] -- List of components last_input_activity: INTEGER -- Clock tick of last user input handle_update_event is -- Handle outside event. local tooltip_x, tooltip_y: INTEGER do -- Check input activity to show tooltip if necessary if Widget_options.are_tooltips_enabled and then not is_tooltip_visible and then last_input_activity + Widget_options.tooltip_show_delay < Time.ticks and then Shared_mouse_focus.has_focus and then Shared_mouse_focus.current_focus.tooltip /= Void then tooltip_component.set_text (Shared_mouse_focus.current_focus.tooltip) tooltip_component.resize_to_optimal_dimension if tooltip_component.width + screen_mouse_x > screen.width then tooltip_x := screen.width - Tooltip_component.width - 10 else tooltip_x := screen_mouse_x end if tooltip_component.height + screen_mouse_y + 20 > screen.height then tooltip_y := screen_mouse_y - 30 else tooltip_y := screen_mouse_y + 20 end tooltip_component.set_position (tooltip_x, tooltip_y) add_component (tooltip_component) is_tooltip_visible := True end -- Handle events Component_event_queue.process_events end handle_input_activity is -- Handle input activity. do last_input_activity := Time.ticks if is_tooltip_visible then remove_component (tooltip_component) is_tooltip_visible := False end end is_tooltip_visible: BOOLEAN -- Is tooltip currently visible? tooltip_component: EM_LABEL is -- Label to display tooltip do Result := tooltip_component_cell.item Result.set_background_color (theme_colors.tooltip_background) Result.set_foreground_color (theme_colors.tooltip_text) Result.set_border (create {EM_LINE_BORDER}.make (theme_colors.tooltip_text, 1)) ensure tooltip_component_not_void: Result /= Void end tooltip_component_cell: DS_CELL [EM_LABEL] is -- Cell to hold tooltip label once create Result.make (create {EM_LABEL}.make_empty) end invariant make_component_scene_called: is_make_component_scene_called components_impl_not_void: components_impl /= Void end