439 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			439 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| // This file is part of Desktop App Toolkit,
 | |
| // a set of libraries for developing nice desktop applications.
 | |
| //
 | |
| // For license and copyright information please follow this link:
 | |
| // https://github.com/desktop-app/legal/blob/master/LEGAL
 | |
| //
 | |
| #include "ui/platform/win/ui_windows_direct_manipulation.h"
 | |
| 
 | |
| #include "base/integration.h"
 | |
| #include "base/platform/base_platform_info.h"
 | |
| #include "ui/rp_widget.h"
 | |
| #include "ui/platform/win/ui_window_win.h"
 | |
| 
 | |
| namespace Ui::Platform {
 | |
| 
 | |
| class DirectManipulation::Handler
 | |
| 	: public IDirectManipulationViewportEventHandler
 | |
| 	, public IDirectManipulationInteractionEventHandler {
 | |
| public:
 | |
| 	Handler();
 | |
| 
 | |
| 	void setViewportSize(QSize size);
 | |
| 
 | |
| 	HRESULT STDMETHODCALLTYPE QueryInterface(
 | |
| 		REFIID iid,
 | |
| 		void **ppv) override;
 | |
| 
 | |
| 	ULONG STDMETHODCALLTYPE AddRef() override {
 | |
| 		return ++_ref;
 | |
| 	}
 | |
| 	ULONG STDMETHODCALLTYPE Release() override {
 | |
| 		if (--_ref == 0) {
 | |
| 			delete this;
 | |
| 			return 0;
 | |
| 		}
 | |
| 		return _ref;
 | |
| 	}
 | |
| 
 | |
| 	[[nodiscard]] rpl::producer<bool> interacting() const {
 | |
| 		return _interacting.value();
 | |
| 	}
 | |
| 	[[nodiscard]] rpl::producer<Event> events() const {
 | |
| 		return _events.events();
 | |
| 	}
 | |
| 
 | |
| private:
 | |
| 	~Handler();
 | |
| 
 | |
| 	enum class State {
 | |
| 		None,
 | |
| 		Scroll,
 | |
| 		Fling,
 | |
| 		Pinch,
 | |
| 	};
 | |
| 
 | |
| 	void transitionToState(State state);
 | |
| 
 | |
| 	HRESULT STDMETHODCALLTYPE OnViewportStatusChanged(
 | |
| 		_In_ IDirectManipulationViewport* viewport,
 | |
| 		_In_ DIRECTMANIPULATION_STATUS current,
 | |
| 		_In_ DIRECTMANIPULATION_STATUS previous) override;
 | |
| 
 | |
| 	HRESULT STDMETHODCALLTYPE OnViewportUpdated(
 | |
| 		_In_ IDirectManipulationViewport *viewport) override;
 | |
| 
 | |
| 	HRESULT STDMETHODCALLTYPE OnContentUpdated(
 | |
| 		_In_ IDirectManipulationViewport *viewport,
 | |
| 		_In_ IDirectManipulationContent *content) override;
 | |
| 
 | |
| 	HRESULT STDMETHODCALLTYPE OnInteraction(
 | |
| 		_In_ IDirectManipulationViewport2 *viewport,
 | |
| 		_In_ DIRECTMANIPULATION_INTERACTION_TYPE interaction) override;
 | |
| 
 | |
| 	State _state = State::None;
 | |
| 	int _width = 0;
 | |
| 	int _height = 0;
 | |
| 	rpl::variable<bool> _interacting = false;
 | |
| 	rpl::event_stream<Event> _events;
 | |
| 	float _scale = 1.0f;
 | |
| 	float _xOffset = 0.f;
 | |
| 	float _yOffset = 0.f;
 | |
| 	bool _pendingScrollBegin = false;
 | |
| 
 | |
| 	std::atomic<ULONG> _ref = 1;
 | |
| 
 | |
| };
 | |
| 
 | |
| DirectManipulation::Handler::Handler() {
 | |
| }
 | |
| 
 | |
| DirectManipulation::Handler::~Handler() {
 | |
| }
 | |
| 
 | |
| void DirectManipulation::Handler::setViewportSize(QSize size) {
 | |
| 	_width = size.width();
 | |
| 	_height = size.height();
 | |
| }
 | |
| 
 | |
| STDMETHODIMP DirectManipulation::Handler::QueryInterface(
 | |
| 		REFIID iid,
 | |
| 		void **ppv) {
 | |
| 	const IID IID_IDirectManipulationViewportEventHandler =
 | |
| 		__uuidof(IDirectManipulationViewportEventHandler);
 | |
| 	const IID IID_IDirectManipulationInteractionEventHandler =
 | |
| 		__uuidof(IDirectManipulationInteractionEventHandler);
 | |
| 
 | |
| 	if ((IID_IUnknown == iid) ||
 | |
| 		(IID_IDirectManipulationViewportEventHandler == iid)) {
 | |
| 		*ppv = static_cast<IDirectManipulationViewportEventHandler*>(this);
 | |
| 		AddRef();
 | |
| 		return S_OK;
 | |
| 	}
 | |
| 	if (IID_IDirectManipulationInteractionEventHandler == iid) {
 | |
| 		*ppv = static_cast<IDirectManipulationInteractionEventHandler*>(
 | |
| 			this);
 | |
| 		AddRef();
 | |
| 		return S_OK;
 | |
| 	}
 | |
| 
 | |
| 	return E_NOINTERFACE;
 | |
| }
 | |
| 
 | |
| void DirectManipulation::Handler::transitionToState(State state)  {
 | |
| 	if (_state == state) {
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	const auto was = _state;
 | |
| 	_state = state;
 | |
| 
 | |
| 	switch (was) {
 | |
| 	case State::Scroll: {
 | |
| 		if (state != State::Fling) {
 | |
| 			_events.fire({ .type = Event::Type::ScrollStop });
 | |
| 		}
 | |
| 	} break;
 | |
| 	case State::Fling: {
 | |
| 		_events.fire({ .type = Event::Type::FlingStop });
 | |
| 	} break;
 | |
| 	case State::Pinch: {
 | |
| 		// _events.fire({ .type = Event::Type::PinchStop });
 | |
| 	} break;
 | |
| 	}
 | |
| 
 | |
| 	switch (state) {
 | |
| 	case State::Scroll: {
 | |
| 		_pendingScrollBegin = true;
 | |
| 	} break;
 | |
| 	case State::Fling: {
 | |
| 		Assert(was == State::Scroll);
 | |
| 		_events.fire({ .type = Event::Type::FlingStart });
 | |
| 	} break;
 | |
| 	case State::Pinch: {
 | |
| 		//_events.fire({ .type = Event::Type::PinchStart });
 | |
| 	} break;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| HRESULT DirectManipulation::Handler::OnViewportStatusChanged(
 | |
| 		IDirectManipulationViewport *viewport,
 | |
| 		DIRECTMANIPULATION_STATUS current,
 | |
| 		DIRECTMANIPULATION_STATUS previous) {
 | |
| 	Expects(viewport != nullptr);
 | |
| 
 | |
| 	if (current == previous) {
 | |
| 		return S_OK;
 | |
| 	} else if (current == DIRECTMANIPULATION_INERTIA) {
 | |
| 		if (previous != DIRECTMANIPULATION_RUNNING
 | |
| 			|| _state != State::Scroll) {
 | |
| 			return S_OK;
 | |
| 		}
 | |
| 		transitionToState(State::Fling);
 | |
| 	}
 | |
| 
 | |
| 	if (current == DIRECTMANIPULATION_RUNNING) {
 | |
| 		if (previous == DIRECTMANIPULATION_INERTIA) {
 | |
| 			transitionToState(State::None);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (current != DIRECTMANIPULATION_READY) {
 | |
| 		return S_OK;
 | |
| 	}
 | |
| 
 | |
| 	if (_scale != 1.0f || _xOffset != 0. || _yOffset != 0.) {
 | |
| 		const auto hr = viewport->ZoomToRect(0, 0, _width, _height, FALSE);
 | |
| 		if (!SUCCEEDED(hr)) {
 | |
| 			return hr;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	_scale = 1.0f;
 | |
| 	_xOffset = 0.0f;
 | |
| 	_yOffset = 0.0f;
 | |
| 
 | |
| 	transitionToState(State::None);
 | |
| 
 | |
| 	return S_OK;
 | |
| }
 | |
| 
 | |
| HRESULT DirectManipulation::Handler::OnViewportUpdated(
 | |
| 		IDirectManipulationViewport *viewport) {
 | |
| 	return S_OK;
 | |
| }
 | |
| 
 | |
| HRESULT DirectManipulation::Handler::OnContentUpdated(
 | |
| 		IDirectManipulationViewport *viewport,
 | |
| 		IDirectManipulationContent *content) {
 | |
| 	Expects(viewport != nullptr);
 | |
| 	Expects(content != nullptr);
 | |
| 
 | |
| 	float xform[6];
 | |
| 	const auto hr = content->GetContentTransform(xform, ARRAYSIZE(xform));
 | |
| 	if (!SUCCEEDED(hr)) {
 | |
| 		return hr;
 | |
| 	}
 | |
| 
 | |
| 	float scale = xform[0];
 | |
| 	float xOffset = xform[4];
 | |
| 	float yOffset = xform[5];
 | |
| 
 | |
| 	if (scale == 0.0f) {
 | |
| 		return hr;
 | |
| 	} else if (qFuzzyCompare(scale, _scale)
 | |
| 		&& xOffset == _xOffset
 | |
| 		&& yOffset == _yOffset) {
 | |
| 		return hr;
 | |
| 	}
 | |
| 	if (qFuzzyCompare(scale, 1.0f)) {
 | |
| 		if (_state == State::None) {
 | |
| 			transitionToState(State::Scroll);
 | |
| 		}
 | |
| 	} else {
 | |
| 		transitionToState(State::Pinch);
 | |
| 	}
 | |
| 
 | |
| 	auto getIntDeltaPart = [](float &was, float now) {
 | |
| 		if (was < now) {
 | |
| 			const auto result = std::floor(now - was);
 | |
| 			was += result;
 | |
| 			return int(result);
 | |
| 		} else {
 | |
| 			const auto result = std::floor(was - now);
 | |
| 			was -= result;
 | |
| 			return -int(result);
 | |
| 		}
 | |
| 	};
 | |
| 	const auto d = QPoint(
 | |
| 		getIntDeltaPart(_xOffset, xOffset),
 | |
| 		getIntDeltaPart(_yOffset, yOffset));
 | |
| 	if ((_state == State::Scroll || _state == State::Fling) && d.isNull()) {
 | |
| 		return S_OK;
 | |
| 	}
 | |
| 	if (_state == State::Scroll) {
 | |
| 		if (_pendingScrollBegin) {
 | |
| 			_events.fire({ .type = Event::Type::ScrollStart, .delta = d });
 | |
| 			_pendingScrollBegin = false;
 | |
| 		} else {
 | |
| 			_events.fire({ .type = Event::Type::Scroll, .delta = d });
 | |
| 		}
 | |
| 	} else if (_state == State::Fling) {
 | |
| 		_events.fire({ .type = Event::Type::Fling, .delta = d });
 | |
| 	} else {
 | |
| 		//_events.fire({ .type = Event::Type::Pinch, .delta = ... });
 | |
| 	}
 | |
| 	_scale = scale;
 | |
| 
 | |
| 	return hr;
 | |
| }
 | |
| 
 | |
| HRESULT DirectManipulation::Handler::OnInteraction(
 | |
| 		IDirectManipulationViewport2 *viewport,
 | |
| 		DIRECTMANIPULATION_INTERACTION_TYPE interaction) {
 | |
| 	if (interaction == DIRECTMANIPULATION_INTERACTION_BEGIN) {
 | |
| 		_interacting = true;
 | |
| 	} else if (interaction == DIRECTMANIPULATION_INTERACTION_END) {
 | |
| 		_interacting = false;
 | |
| 	}
 | |
| 	return S_OK;
 | |
| }
 | |
| 
 | |
| DirectManipulation::DirectManipulation(not_null<RpWidget*> widget)
 | |
| : _handle(GetWindowHandle(widget))
 | |
| , _interacting([=] { _updateManager->Update(nullptr); }) {
 | |
| 	if (!init(widget)) {
 | |
| 		destroy();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| DirectManipulation::~DirectManipulation() {
 | |
| 	destroy();
 | |
| }
 | |
| 
 | |
| bool DirectManipulation::valid() const {
 | |
| 	return _manager != nullptr;
 | |
| }
 | |
| 
 | |
| bool DirectManipulation::init(not_null<RpWidget*> widget) {
 | |
| 	if (!_handle || !::Platform::IsWindows10OrGreater()) {
 | |
| 		return false;
 | |
| 	}
 | |
| 	_manager = base::WinRT::TryCreateInstance<IDirectManipulationManager>(
 | |
| 		CLSID_DirectManipulationManager);
 | |
| 	if (!_manager) {
 | |
| 		return false;
 | |
| 	}
 | |
| 
 | |
| 	auto hr = S_OK;
 | |
| 	hr = _manager->GetUpdateManager(IID_PPV_ARGS(_updateManager.put()));
 | |
| 	if (!SUCCEEDED(hr) || !_updateManager) {
 | |
| 		return false;
 | |
| 	}
 | |
| 
 | |
| 	hr = _manager->CreateViewport(
 | |
| 		nullptr,
 | |
| 		_handle,
 | |
| 		IID_PPV_ARGS(_viewport.put()));
 | |
| 	if (!SUCCEEDED(hr) || !_viewport) {
 | |
| 		return false;
 | |
| 	}
 | |
| 
 | |
| 	const auto configuration = DIRECTMANIPULATION_CONFIGURATION_INTERACTION
 | |
| 		| DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_X
 | |
| 		| DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_Y
 | |
| 		| DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_INERTIA
 | |
| 		| DIRECTMANIPULATION_CONFIGURATION_RAILS_X
 | |
| 		| DIRECTMANIPULATION_CONFIGURATION_RAILS_Y;
 | |
| 
 | |
| 	hr = _viewport->ActivateConfiguration(configuration);
 | |
| 	if (!SUCCEEDED(hr)) {
 | |
| 		return false;
 | |
| 	}
 | |
| 	hr = _viewport->SetViewportOptions(
 | |
| 		DIRECTMANIPULATION_VIEWPORT_OPTIONS_MANUALUPDATE);
 | |
| 	if (!SUCCEEDED(hr)) {
 | |
| 		return false;
 | |
| 	}
 | |
| 
 | |
| 	_handler.attach(new Handler());
 | |
| 	_handler->interacting(
 | |
| 	) | rpl::start_with_next([=](bool interacting) {
 | |
| 		base::Integration::Instance().enterFromEventLoop([&] {
 | |
| 			if (interacting) {
 | |
| 				_interacting.start();
 | |
| 			} else {
 | |
| 				_interacting.stop();
 | |
| 			}
 | |
| 		});
 | |
| 	}, _lifetime);
 | |
| 
 | |
| 	widget->sizeValue() | rpl::start_with_next([=](QSize size) {
 | |
| 		const auto r = QRect(QPoint(), size * widget->devicePixelRatio());
 | |
| 		_handler->setViewportSize(r.size());
 | |
| 		const auto rect = RECT{ r.left(), r.top(), r.right(), r.bottom() };
 | |
| 		_viewport->SetViewportRect(&rect);
 | |
| 	}, _lifetime);
 | |
| 
 | |
| 	hr = _viewport->AddEventHandler(_handle, _handler.get(), &_cookie);
 | |
| 	if (!SUCCEEDED(hr)) {
 | |
| 		return false;
 | |
| 	}
 | |
| 
 | |
| 	RECT rect = { 0, 0, 1024, 1024 };
 | |
| 	hr = _viewport->SetViewportRect(&rect);
 | |
| 	if (!SUCCEEDED(hr)) {
 | |
| 		return false;
 | |
| 	}
 | |
| 
 | |
| 	hr = _manager->Activate(_handle);
 | |
| 	if (!SUCCEEDED(hr)) {
 | |
| 		return false;
 | |
| 	}
 | |
| 
 | |
| 	hr = _viewport->Enable();
 | |
| 	if (!SUCCEEDED(hr)) {
 | |
| 		return false;
 | |
| 	}
 | |
| 
 | |
| 	hr = _updateManager->Update(nullptr);
 | |
| 	if (!SUCCEEDED(hr)) {
 | |
| 		return false;
 | |
| 	}
 | |
| 
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| auto DirectManipulation::events() const -> rpl::producer<Event> {
 | |
| 	if (!_handler) {
 | |
| 		return rpl::never<Event>();
 | |
| 	}
 | |
| 	return [events = _handler->events()](auto consumer) mutable {
 | |
| 		auto result = rpl::lifetime();
 | |
| 		std::move(
 | |
| 			events
 | |
| 		) | rpl::start_with_next([=](Event &&event) {
 | |
| 			base::Integration::Instance().enterFromEventLoop([&] {
 | |
| 				consumer.put_next(std::move(event));
 | |
| 			});
 | |
| 		}, result);
 | |
| 		return result;
 | |
| 	};
 | |
| }
 | |
| 
 | |
| void DirectManipulation::handlePointerHitTest(WPARAM wParam) {
 | |
| 	const auto id = UINT32(GET_POINTERID_WPARAM(wParam));
 | |
| 	auto type = POINTER_INPUT_TYPE();
 | |
| 	if (::GetPointerType(id, &type) && type == PT_TOUCHPAD) {
 | |
| 		_viewport->SetContact(id);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void DirectManipulation::destroy() {
 | |
| 	_interacting.stop();
 | |
| 
 | |
| 	if (_handler) {
 | |
| 		_handler = nullptr;
 | |
| 	}
 | |
| 
 | |
| 	if (_viewport) {
 | |
| 		_viewport->Stop();
 | |
| 		if (_cookie) {
 | |
| 			_viewport->RemoveEventHandler(_cookie);
 | |
| 			_cookie = 0;
 | |
| 		}
 | |
| 		_viewport->Abandon();
 | |
| 		_viewport = nullptr;
 | |
| 	}
 | |
| 
 | |
| 	if (_updateManager) {
 | |
| 		_updateManager = nullptr;
 | |
| 	}
 | |
| 
 | |
| 	if (_manager) {
 | |
| 		_manager->Deactivate(_handle);
 | |
| 		_manager = nullptr;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| }  // namespace Ui::Platform
 | 
