class EL_AUTO_CELL_HIDING_BOX

(source code)

Description

A box that will hide selected widget members if the mouse pointer is not over the box, and show them if the pointer enters the box.

note
	description: "[
		A box that will hide selected widget members if the mouse pointer is not over the box,
		and show them if the pointer enters the box.
	]"

	author: "Finnian Reilly"
	copyright: "Copyright (c) 2001-2022 Finnian Reilly"
	contact: "finnian at eiffel hyphen loop dot com"

	license: "MIT license (See: en.wikipedia.org/wiki/MIT_License)"
	date: "2022-11-15 19:56:05 GMT (Tuesday 15th November 2022)"
	revision: "8"

deferred class
	EL_AUTO_CELL_HIDING_BOX

inherit
	EL_BOX
		rename
			remove as internal_remove,
			merge_right as internal_merge_right
		redefine
			make
		end

	EL_MODULE_ACTION

feature {NONE} -- Initialization

	make (border_cms, padding_cms: REAL)
		do
			Precursor (border_cms, padding_cms)
			create hidden_components.make (3)
			create always_hidden_components.make (3)
			create internal_rectangle
			resize_actions.extend (agent on_resize)
		end

feature -- Element change

	remove
		do
			if auto_hide_enabled then
				hidden_components.start; hidden_components.prune (item)
				remove_widget_actions (item)
			end
			internal_remove
		end

	merge_right (other: like Current)
			-- Works fine on GTK but on Windows there are problems with the expansion status,
			-- unexpanded items behave as expanded, even when explicitly set again.
		do
			if auto_hide_enabled then
				other.do_all (agent auto_hide_widget)
			end
			internal_merge_right (other)
		end

feature -- Status change

	enable_always_hidden (a_widget: EV_WIDGET)
		do
			hidden_components.start; hidden_components.prune (a_widget)
			remove_widget_actions (a_widget)
			always_hidden_components.extend (a_widget)
			a_widget.hide
		end

	enable_widget_auto_hide (a_widget: EV_WIDGET)
		do
			always_hidden_components.start; always_hidden_components.prune (a_widget)
			auto_hide_widget (a_widget)
		end

	enable_shown (a_widget: EV_WIDGET)
		do
			across << always_hidden_components, hidden_components >> as components loop
				components.item.start; components.item.prune (a_widget)
			end
		end

	disable_auto_hide
		do
			hidden_components.wipe_out
			across Current as this loop
				remove_widget_actions (this.item)
				if not always_hidden_components.has (this.item) then
					this.item.show
				end
			end
			auto_hide_enabled := false
		end

	enable_auto_hide
		do
			across Current as this loop
				if not always_hidden_components.has (this.item) then
					auto_hide_widget (this.item)
				end
			end
			auto_hide_enabled := True
		end

feature -- Status query

	auto_hide_enabled: BOOLEAN

feature {NONE} -- Events

	on_enter (a_widget: EV_WIDGET)
		do
--			log.enter ("on_enter")
			if auto_hide_enabled then
				across hidden_components as hidden loop hidden.item.show end
				auto_hide_enabled := False
			end
--			log.exit
		end

	on_leave (a_widget: EV_WIDGET)
		do
--			log.enter ("on_leave")
			if not across Current as this some widget_has_pointer (this.item) end then
				across hidden_components as hidden loop hidden.item.hide end
				auto_hide_enabled := True
--				log.put_line ("hidden now")
			end
--			log.exit
		end

	on_resize (a_x, a_y, a_width, a_height: INTEGER)
			--
		do
--			log.enter_with_args ("on_resize", << a_x, a_y, a_width, a_height >>)
			if {PLATFORM}.is_windows then
				Action.do_once_on_idle (agent check_pointer_position)
			end
--			log.exit
		end

feature {NONE} -- Windows workaround

	-- Work around for two bugs in Windows implementation
	-- 1. If the pointer is positioned over the newly created box,
	--    on_enter does not get called unless the pointer moves See #18608

	-- 2. Coordinates of box is misreported in routine on_resize See #18607
	--

	check_pointer_position
			-- check if pointer over this box and if so, nudge the pointer position to
			-- trigger on_enter
		do
--			log.enter ("check_pointer")
			if auto_hide_enabled and then widget_has_pointer (Current) then
				Action.do_once_on_idle (agent nudge_pointer_position)
			end
--			log.exit
		end

	nudge_pointer_position
		local
			l_position: EV_COORDINATE
		do
			l_position := screen.pointer_position
			screen.set_pointer_position (l_position.x, l_position.y + Wobble_offset)
			Wobble_offset.set_item (-Wobble_offset)
		end

	Wobble_offset: INTEGER_REF
		once
			create Result
			Result.set_item (1)
		end

feature {NONE} -- Implementation

	auto_hide_widget (a_widget: EV_WIDGET)
		do
			a_widget.pointer_enter_actions.extend (agent on_enter (a_widget))
			a_widget.pointer_leave_actions.extend (agent on_leave (a_widget))
			if not a_widget.is_show_requested then
				hidden_components.extend (a_widget)
			end
		end

	remove_widget_actions (a_widget: EV_WIDGET)
		do
			remove_action (a_widget.pointer_enter_actions, create {EL_PROCEDURE}.make (agent on_enter))
			a_widget.pointer_enter_actions.resume

			remove_action (a_widget.pointer_leave_actions, create {EL_PROCEDURE}.make (agent on_leave))
			a_widget.pointer_leave_actions.resume
		end

	remove_action (actions: EV_NOTIFY_ACTION_SEQUENCE; action_to_remove: EL_PROCEDURE)
		do
			from actions.start until actions.after loop
				if action_to_remove.same_procedure (actions.item) then
					actions.remove
				else
					actions.forth
				end
			end
		end

	widget_has_pointer (a_widget: EV_WIDGET): BOOLEAN
		do
			internal_rectangle.move_and_resize (a_widget.screen_x, a_widget.screen_y, a_widget.width, a_widget.height)
			Result := internal_rectangle.has (screen.pointer_position)
		end

	internal_rectangle: EV_RECTANGLE

	hidden_components: EL_ARRAYED_LIST [EV_WIDGET]
		-- widgets that are hidden only while cursor is not over the box

	always_hidden_components: ARRAYED_LIST [EV_WIDGET]
		-- widgets that are always hidden even if the cursor is over the box

end