504 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			504 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
| This file is part of Telegram Desktop,
 | |
| the official desktop application for the Telegram messaging service.
 | |
| 
 | |
| For license and copyright information please follow this link:
 | |
| https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | |
| */
 | |
| #include "editor/photo_editor_controls.h"
 | |
| 
 | |
| #include "editor/controllers/controllers.h"
 | |
| #include "lang/lang_keys.h"
 | |
| #include "ui/image/image_prepare.h"
 | |
| #include "ui/widgets/buttons.h"
 | |
| 
 | |
| #include "styles/style_editor.h"
 | |
| 
 | |
| namespace Editor {
 | |
| 
 | |
| class EdgeButton final : public Ui::RippleButton {
 | |
| public:
 | |
| 	EdgeButton(
 | |
| 		not_null<Ui::RpWidget*> parent,
 | |
| 		const QString &text,
 | |
| 		int height,
 | |
| 		bool left,
 | |
| 		const style::color &bg,
 | |
| 		const style::color &fg,
 | |
| 		const style::RippleAnimation &st);
 | |
| 
 | |
| protected:
 | |
| 	QImage prepareRippleMask() const override;
 | |
| 	QPoint prepareRippleStartPosition() const override;
 | |
| 
 | |
| private:
 | |
| 	void init();
 | |
| 
 | |
| 	const style::color &_fg;
 | |
| 	Ui::Text::String _text;
 | |
| 	const int _width;
 | |
| 	const QRect _rippleRect;
 | |
| 	const QColor _bg;
 | |
| 	const bool _left;
 | |
| 
 | |
| 	QImage rounded(std::optional<QColor> color) const;
 | |
| 
 | |
| };
 | |
| 
 | |
| EdgeButton::EdgeButton(
 | |
| 	not_null<Ui::RpWidget*> parent,
 | |
| 	const QString &text,
 | |
| 	int height,
 | |
| 	bool left,
 | |
| 	const style::color &bg,
 | |
| 	const style::color &fg,
 | |
| 	const style::RippleAnimation &st)
 | |
| : Ui::RippleButton(parent, st)
 | |
| , _fg(fg)
 | |
| , _text(st::semiboldTextStyle, text)
 | |
| , _width(_text.maxWidth()
 | |
| 	+ st::photoEditorTextButtonPadding.left()
 | |
| 	+ st::photoEditorTextButtonPadding.right())
 | |
| , _rippleRect(QRect(0, 0, _width, height))
 | |
| , _bg(bg->c)
 | |
| , _left(left) {
 | |
| 	resize(_width, height);
 | |
| 	init();
 | |
| }
 | |
| 
 | |
| void EdgeButton::init() {
 | |
| 	// const auto bg = rounded(_bg);
 | |
| 
 | |
| 	paintRequest(
 | |
| 	) | rpl::start_with_next([=] {
 | |
| 		Painter p(this);
 | |
| 
 | |
| 		// p.drawImage(QPoint(), bg);
 | |
| 
 | |
| 		paintRipple(p, _rippleRect.x(), _rippleRect.y());
 | |
| 
 | |
| 		p.setPen(_fg);
 | |
| 		const auto textTop = (height() - _text.minHeight()) / 2;
 | |
| 		_text.draw(p, 0, textTop, width(), style::al_center);
 | |
| 	}, lifetime());
 | |
| }
 | |
| 
 | |
| QImage EdgeButton::rounded(std::optional<QColor> color) const {
 | |
| 	auto result = QImage(
 | |
| 		_rippleRect.size() * cIntRetinaFactor(),
 | |
| 		QImage::Format_ARGB32_Premultiplied);
 | |
| 	result.setDevicePixelRatio(cIntRetinaFactor());
 | |
| 	result.fill(color.value_or(Qt::white));
 | |
| 
 | |
| 	const auto parts = RectPart::None
 | |
| 		| (_left ? RectPart::TopLeft : RectPart::TopRight)
 | |
| 		| (_left ? RectPart::BottomLeft : RectPart::BottomRight);
 | |
| 	return Images::Round(std::move(result), ImageRoundRadius::Large, parts);
 | |
| }
 | |
| 
 | |
| QImage EdgeButton::prepareRippleMask() const {
 | |
| 	return rounded(std::nullopt);
 | |
| }
 | |
| 
 | |
| QPoint EdgeButton::prepareRippleStartPosition() const {
 | |
| 	return mapFromGlobal(QCursor::pos()) - _rippleRect.topLeft();
 | |
| }
 | |
| 
 | |
| class ButtonBar final : public Ui::RpWidget {
 | |
| public:
 | |
| 	ButtonBar(
 | |
| 		not_null<Ui::RpWidget*> parent,
 | |
| 		const style::color &bg);
 | |
| 
 | |
| private:
 | |
| 	QImage _roundedBg;
 | |
| 
 | |
| };
 | |
| 
 | |
| ButtonBar::ButtonBar(
 | |
| 	not_null<Ui::RpWidget*> parent,
 | |
| 	const style::color &bg)
 | |
| : RpWidget(parent) {
 | |
| 	sizeValue(
 | |
| 	) | rpl::start_with_next([=](const QSize &size) {
 | |
| 		const auto children = RpWidget::children();
 | |
| 		if (children.empty()) {
 | |
| 			return;
 | |
| 		}
 | |
| 		const auto widgets = ranges::views::all(
 | |
| 			children
 | |
| 		) | ranges::views::filter([](not_null<const QObject*> object) {
 | |
| 			return object->isWidgetType();
 | |
| 		}) | ranges::views::transform([](not_null<QObject*> object) {
 | |
| 			return static_cast<Ui::RpWidget*>(object.get());
 | |
| 		}) | ranges::to_vector;
 | |
| 
 | |
| 		const auto residualWidth = size.width()
 | |
| 			- ranges::accumulate(widgets, 0, ranges::plus(), &QWidget::width);
 | |
| 		const auto step = residualWidth / float(widgets.size() - 1);
 | |
| 
 | |
| 		auto left = 0.;
 | |
| 		for (const auto &widget : widgets) {
 | |
| 			widget->moveToLeft(int(left), 0);
 | |
| 			left += widget->width() + step;
 | |
| 		}
 | |
| 
 | |
| 		auto result = QImage(
 | |
| 			size * cIntRetinaFactor(),
 | |
| 			QImage::Format_ARGB32_Premultiplied);
 | |
| 		result.setDevicePixelRatio(cIntRetinaFactor());
 | |
| 		result.fill(bg->c);
 | |
| 
 | |
| 		_roundedBg = Images::Round(
 | |
| 			std::move(result),
 | |
| 			ImageRoundRadius::Large);
 | |
| 	}, lifetime());
 | |
| 
 | |
| 	paintRequest(
 | |
| 	) | rpl::start_with_next([=] {
 | |
| 		Painter p(this);
 | |
| 
 | |
| 		p.drawImage(QPoint(), _roundedBg);
 | |
| 	}, lifetime());
 | |
| }
 | |
| 
 | |
| PhotoEditorControls::PhotoEditorControls(
 | |
| 	not_null<Ui::RpWidget*> parent,
 | |
| 	std::shared_ptr<Controllers> controllers,
 | |
| 	const PhotoModifications modifications,
 | |
| 	bool doneControls)
 | |
| : RpWidget(parent)
 | |
| , _bg(st::roundedBg)
 | |
| , _buttonHeight(st::photoEditorButtonBarHeight)
 | |
| , _transformButtons(base::make_unique_q<ButtonBar>(this, _bg))
 | |
| , _paintTopButtons(base::make_unique_q<ButtonBar>(this, _bg))
 | |
| , _paintBottomButtons(base::make_unique_q<ButtonBar>(this, _bg))
 | |
| , _transformCancel(base::make_unique_q<EdgeButton>(
 | |
| 	_transformButtons,
 | |
| 	tr::lng_cancel(tr::now),
 | |
| 	_buttonHeight,
 | |
| 	true,
 | |
| 	_bg,
 | |
| 	st::activeButtonFg,
 | |
| 	st::photoEditorRotateButton.ripple))
 | |
| , _rotateButton(base::make_unique_q<Ui::IconButton>(
 | |
| 	_transformButtons,
 | |
| 	st::photoEditorRotateButton))
 | |
| , _flipButton(base::make_unique_q<Ui::IconButton>(
 | |
| 	_transformButtons,
 | |
| 	st::photoEditorFlipButton))
 | |
| , _paintModeButton(base::make_unique_q<Ui::IconButton>(
 | |
| 	_transformButtons,
 | |
| 	st::photoEditorPaintModeButton))
 | |
| , _transformDone(base::make_unique_q<EdgeButton>(
 | |
| 	_transformButtons,
 | |
| 	tr::lng_box_done(tr::now),
 | |
| 	_buttonHeight,
 | |
| 	false,
 | |
| 	_bg,
 | |
| 	st::lightButtonFg,
 | |
| 	st::photoEditorRotateButton.ripple))
 | |
| , _paintCancel(base::make_unique_q<EdgeButton>(
 | |
| 	_paintBottomButtons,
 | |
| 	tr::lng_cancel(tr::now),
 | |
| 	_buttonHeight,
 | |
| 	true,
 | |
| 	_bg,
 | |
| 	st::activeButtonFg,
 | |
| 	st::photoEditorRotateButton.ripple))
 | |
| , _undoButton(base::make_unique_q<Ui::IconButton>(
 | |
| 	_paintTopButtons,
 | |
| 	st::photoEditorUndoButton))
 | |
| , _redoButton(base::make_unique_q<Ui::IconButton>(
 | |
| 	_paintTopButtons,
 | |
| 	st::photoEditorRedoButton))
 | |
| , _paintModeButtonActive(base::make_unique_q<Ui::IconButton>(
 | |
| 	_paintBottomButtons,
 | |
| 	st::photoEditorPaintModeButton))
 | |
| , _stickersButton(controllers->stickersPanelController
 | |
| 		? base::make_unique_q<Ui::IconButton>(
 | |
| 			_paintBottomButtons,
 | |
| 			st::photoEditorStickersButton)
 | |
| 		: nullptr)
 | |
| , _paintDone(base::make_unique_q<EdgeButton>(
 | |
| 	_paintBottomButtons,
 | |
| 	tr::lng_box_done(tr::now),
 | |
| 	_buttonHeight,
 | |
| 	false,
 | |
| 	_bg,
 | |
| 	st::lightButtonFg,
 | |
| 	st::photoEditorRotateButton.ripple)) {
 | |
| 
 | |
| 	{
 | |
| 		const auto &padding = st::photoEditorButtonBarPadding;
 | |
| 		const auto w = st::photoEditorButtonBarWidth
 | |
| 			- padding.left()
 | |
| 			- padding.right();
 | |
| 		_transformButtons->resize(w, _buttonHeight);
 | |
| 		_paintBottomButtons->resize(w, _buttonHeight);
 | |
| 		_paintTopButtons->resize(w, _buttonHeight);
 | |
| 	}
 | |
| 
 | |
| 	{
 | |
| 		const auto icon = &st::photoEditorPaintIconActive;
 | |
| 		_paintModeButtonActive->setIconOverride(icon, icon);
 | |
| 	}
 | |
| 	_paintModeButtonActive->setAttribute(Qt::WA_TransparentForMouseEvents);
 | |
| 
 | |
| 	sizeValue(
 | |
| 	) | rpl::start_with_next([=](const QSize &size) {
 | |
| 		if (size.isEmpty()) {
 | |
| 			return;
 | |
| 		}
 | |
| 
 | |
| 		const auto buttonsTop = bottomButtonsTop();
 | |
| 
 | |
| 		const auto ¤t = _transformButtons->isHidden()
 | |
| 			? _paintBottomButtons
 | |
| 			: _transformButtons;
 | |
| 
 | |
| 		current->moveToLeft(
 | |
| 			(size.width() - current->width()) / 2,
 | |
| 			buttonsTop);
 | |
| 	}, lifetime());
 | |
| 
 | |
| 	_mode.changes(
 | |
| 	) | rpl::start_with_next([=](const PhotoEditorMode &mode) {
 | |
| 		if (mode.mode == PhotoEditorMode::Mode::Out) {
 | |
| 			return;
 | |
| 		}
 | |
| 		const auto animated = (_paintBottomButtons->isVisible()
 | |
| 				== _transformButtons->isVisible())
 | |
| 			? anim::type::instant
 | |
| 			: anim::type::normal;
 | |
| 		showAnimated(mode.mode, animated);
 | |
| 	}, lifetime());
 | |
| 
 | |
| 	_paintBottomButtons->positionValue(
 | |
| 	) | rpl::start_with_next([=](const QPoint &containerPos) {
 | |
| 		_paintTopButtons->moveToLeft(
 | |
| 			containerPos.x(),
 | |
| 			containerPos.y()
 | |
| 				- st::photoEditorControlsCenterSkip
 | |
| 				- _paintTopButtons->height());
 | |
| 	}, _paintBottomButtons->lifetime());
 | |
| 
 | |
| 	_paintBottomButtons->shownValue(
 | |
| 	) | rpl::start_with_next([=](bool shown) {
 | |
| 		_paintTopButtons->setVisible(shown);
 | |
| 	}, _paintBottomButtons->lifetime());
 | |
| 
 | |
| 	controllers->undoController->setPerformRequestChanges(rpl::merge(
 | |
| 		_undoButton->clicks() | rpl::map_to(Undo::Undo),
 | |
| 		_redoButton->clicks() | rpl::map_to(Undo::Redo),
 | |
| 		_keyPresses.events(
 | |
| 		) | rpl::filter([=](not_null<QKeyEvent*> e) {
 | |
| 			using Mode = PhotoEditorMode::Mode;
 | |
| 			return (e->matches(QKeySequence::Undo)
 | |
| 					&& !_undoButton->isHidden()
 | |
| 					&& !_undoButton->testAttribute(
 | |
| 						Qt::WA_TransparentForMouseEvents)
 | |
| 					&& (_mode.current().mode == Mode::Paint))
 | |
| 				|| (e->matches(QKeySequence::Redo)
 | |
| 					&& !_redoButton->isHidden()
 | |
| 					&& !_redoButton->testAttribute(
 | |
| 						Qt::WA_TransparentForMouseEvents)
 | |
| 					&& (_mode.current().mode == Mode::Paint));
 | |
| 		}) | rpl::map([=](not_null<QKeyEvent*> e) {
 | |
| 			return e->matches(QKeySequence::Undo) ? Undo::Undo : Undo::Redo;
 | |
| 		})));
 | |
| 
 | |
| 	controllers->undoController->canPerformChanges(
 | |
| 	) | rpl::start_with_next([=](const UndoController::EnableRequest &r) {
 | |
| 		const auto isUndo = (r.command == Undo::Undo);
 | |
| 		const auto &button = isUndo ? _undoButton : _redoButton;
 | |
| 		button->setAttribute(Qt::WA_TransparentForMouseEvents, !r.enable);
 | |
| 		if (!r.enable) {
 | |
| 			button->clearState();
 | |
| 		}
 | |
| 
 | |
| 		button->setIconOverride(r.enable
 | |
| 			? nullptr
 | |
| 			: isUndo
 | |
| 			? &st::photoEditorUndoButtonInactive
 | |
| 			: &st::photoEditorRedoButtonInactive);
 | |
| 	}, lifetime());
 | |
| 
 | |
| 	if (_stickersButton) {
 | |
| 		using ShowRequest = StickersPanelController::ShowRequest;
 | |
| 
 | |
| 		controllers->stickersPanelController->setShowRequestChanges(
 | |
| 			rpl::merge(
 | |
| 				_mode.value(
 | |
| 				) | rpl::map_to(ShowRequest::HideFast),
 | |
| 				_stickersButton->clicks(
 | |
| 				) | rpl::map_to(ShowRequest::ToggleAnimated)
 | |
| 			));
 | |
| 
 | |
| 		controllers->stickersPanelController->setMoveRequestChanges(
 | |
| 			_paintBottomButtons->positionValue(
 | |
| 			) | rpl::map([=](const QPoint &containerPos) {
 | |
| 				return QPoint(
 | |
| 					(x() + width()) / 2,
 | |
| 					y() + containerPos.y() + _stickersButton->y());
 | |
| 			}));
 | |
| 
 | |
| 		controllers->stickersPanelController->panelShown(
 | |
| 		) | rpl::start_with_next([=](bool shown) {
 | |
| 			const auto icon = shown
 | |
| 				? &st::photoEditorStickersIconActive
 | |
| 				: nullptr;
 | |
| 			_stickersButton->setIconOverride(icon, icon);
 | |
| 		}, _stickersButton->lifetime());
 | |
| 	}
 | |
| 
 | |
| 	rpl::single(rpl::empty) | rpl::skip(
 | |
| 		modifications.flipped ? 0 : 1
 | |
| 	) | rpl::then(
 | |
| 		_flipButton->clicks() | rpl::to_empty
 | |
| 	) | rpl::start_with_next([=] {
 | |
| 		_flipped = !_flipped;
 | |
| 		const auto icon = _flipped ? &st::photoEditorFlipIconActive : nullptr;
 | |
| 		_flipButton->setIconOverride(icon, icon);
 | |
| 	}, _flipButton->lifetime());
 | |
| 
 | |
| }
 | |
| 
 | |
| rpl::producer<int> PhotoEditorControls::rotateRequests() const {
 | |
| 	return _rotateButton->clicks() | rpl::map_to(90);
 | |
| }
 | |
| 
 | |
| rpl::producer<> PhotoEditorControls::flipRequests() const {
 | |
| 	return _flipButton->clicks() | rpl::to_empty;
 | |
| }
 | |
| 
 | |
| rpl::producer<> PhotoEditorControls::paintModeRequests() const {
 | |
| 	return _paintModeButton->clicks() | rpl::to_empty;
 | |
| }
 | |
| 
 | |
| rpl::producer<> PhotoEditorControls::doneRequests() const {
 | |
| 	return rpl::merge(
 | |
| 		_transformDone->clicks() | rpl::to_empty,
 | |
| 		_paintDone->clicks() | rpl::to_empty,
 | |
| 		_keyPresses.events(
 | |
| 		) | rpl::filter([=](not_null<QKeyEvent*> e) {
 | |
| 			const auto key = e->key();
 | |
| 			return ((key == Qt::Key_Enter) || (key == Qt::Key_Return))
 | |
| 				&& !_toggledBarAnimation.animating();
 | |
| 		}) | rpl::to_empty);
 | |
| }
 | |
| 
 | |
| rpl::producer<> PhotoEditorControls::cancelRequests() const {
 | |
| 	return rpl::merge(
 | |
| 		_transformCancel->clicks() | rpl::to_empty,
 | |
| 		_paintCancel->clicks() | rpl::to_empty,
 | |
| 		_keyPresses.events(
 | |
| 		) | rpl::filter([=](not_null<QKeyEvent*> e) {
 | |
| 			const auto key = e->key();
 | |
| 			return (key == Qt::Key_Escape)
 | |
| 				&& !_toggledBarAnimation.animating();
 | |
| 		}) | rpl::to_empty);
 | |
| }
 | |
| 
 | |
| int PhotoEditorControls::bottomButtonsTop() const {
 | |
| 	return height()
 | |
| 		- st::photoEditorControlsBottomSkip
 | |
| 		- _transformButtons->height();
 | |
| }
 | |
| 
 | |
| void PhotoEditorControls::showAnimated(
 | |
| 		PhotoEditorMode::Mode mode,
 | |
| 		anim::type animated) {
 | |
| 	using Mode = PhotoEditorMode::Mode;
 | |
| 
 | |
| 	const auto duration = st::photoEditorBarAnimationDuration;
 | |
| 
 | |
| 	const auto isTransform = (mode == Mode::Transform);
 | |
| 
 | |
| 	const auto buttonsLeft = (width() - _transformButtons->width()) / 2;
 | |
| 	const auto buttonsTop = bottomButtonsTop();
 | |
| 
 | |
| 	const auto visibleBar = _transformButtons->isVisible()
 | |
| 		? _transformButtons.get()
 | |
| 		: _paintBottomButtons.get();
 | |
| 
 | |
| 	const auto shouldVisibleBar = isTransform
 | |
| 		? _transformButtons.get()
 | |
| 		: _paintBottomButtons.get(); // Mode::Paint
 | |
| 
 | |
| 	const auto computeTop = [=](float64 progress) {
 | |
| 		return anim::interpolate(buttonsTop, height() * 2, progress);
 | |
| 	};
 | |
| 
 | |
| 	const auto showShouldVisibleBar = [=] {
 | |
| 		_toggledBarAnimation.stop();
 | |
| 		auto callback = [=](float64 value) {
 | |
| 			shouldVisibleBar->moveToLeft(buttonsLeft, computeTop(value));
 | |
| 		};
 | |
| 		if (animated == anim::type::instant) {
 | |
| 			callback(1.);
 | |
| 		} else {
 | |
| 			_toggledBarAnimation.start(
 | |
| 				std::move(callback),
 | |
| 				1.,
 | |
| 				0.,
 | |
| 				duration);
 | |
| 		}
 | |
| 	};
 | |
| 
 | |
| 	auto animationCallback = [=](float64 value) {
 | |
| 		if (shouldVisibleBar == visibleBar) {
 | |
| 			showShouldVisibleBar();
 | |
| 			return;
 | |
| 		}
 | |
| 		visibleBar->moveToLeft(buttonsLeft, computeTop(value));
 | |
| 
 | |
| 		if (value == 1.) {
 | |
| 			shouldVisibleBar->show();
 | |
| 			shouldVisibleBar->moveToLeft(buttonsLeft, computeTop(1.));
 | |
| 			visibleBar->hide();
 | |
| 
 | |
| 			showShouldVisibleBar();
 | |
| 		}
 | |
| 	};
 | |
| 
 | |
| 	if (animated == anim::type::instant) {
 | |
| 		animationCallback(1.);
 | |
| 	} else {
 | |
| 		_toggledBarAnimation.start(
 | |
| 			std::move(animationCallback),
 | |
| 			0.,
 | |
| 			1.,
 | |
| 			duration);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void PhotoEditorControls::applyMode(const PhotoEditorMode &mode) {
 | |
| 	_mode = mode;
 | |
| }
 | |
| 
 | |
| rpl::producer<QPoint> PhotoEditorControls::colorLinePositionValue() const {
 | |
| 	return rpl::merge(
 | |
| 		geometryValue() | rpl::to_empty,
 | |
| 		_paintTopButtons->geometryValue() | rpl::to_empty
 | |
| 	) | rpl::map([=] {
 | |
| 		const auto r = _paintTopButtons->geometry();
 | |
| 		return mapToParent(r.topLeft())
 | |
| 			+ QPoint(r.width() / 2, r.height() / 2);
 | |
| 	});
 | |
| }
 | |
| 
 | |
| rpl::producer<bool> PhotoEditorControls::colorLineShownValue() const {
 | |
| 	return _paintTopButtons->shownValue();
 | |
| }
 | |
| 
 | |
| bool PhotoEditorControls::handleKeyPress(not_null<QKeyEvent*> e) const {
 | |
| 	_keyPresses.fire(std::move(e));
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| bool PhotoEditorControls::animating() const {
 | |
| 	return _toggledBarAnimation.animating();
 | |
| }
 | |
| 
 | |
| } // namespace Editor
 | 
