908 lines
		
	
	
		
			No EOL
		
	
	
		
			27 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			908 lines
		
	
	
		
			No EOL
		
	
	
		
			27 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
| This file is part of Telegram Desktop,
 | |
| the official desktop version of Telegram messaging app, see https://telegram.org
 | |
| 
 | |
| Telegram Desktop is free software: you can redistribute it and/or modify
 | |
| it under the terms of the GNU General Public License as published by
 | |
| the Free Software Foundation, either version 3 of the License, or
 | |
| (at your option) any later version.
 | |
| 
 | |
| It is distributed in the hope that it will be useful,
 | |
| but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 | |
| GNU General Public License for more details.
 | |
| 
 | |
| In addition, as a special exception, the copyright holders give permission
 | |
| to link the code of portions of this program with the OpenSSL library.
 | |
| 
 | |
| Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
 | |
| Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
 | |
| */
 | |
| #include "stdafx.h"
 | |
| #include "boxes/editcolorbox.h"
 | |
| 
 | |
| #include "lang.h"
 | |
| #include "styles/style_boxes.h"
 | |
| #include "ui/widgets/shadow.h"
 | |
| #include "styles/style_mediaview.h"
 | |
| #include "ui/widgets/input_fields.h"
 | |
| 
 | |
| class EditColorBox::Picker : public TWidget {
 | |
| public:
 | |
| 	Picker(QWidget *parent, QColor color);
 | |
| 
 | |
| 	float64 valueX() const {
 | |
| 		return _x;
 | |
| 	}
 | |
| 	float64 valueY() const {
 | |
| 		return _y;
 | |
| 	}
 | |
| 
 | |
| 	base::Observable<void> &changed() {
 | |
| 		return _changed;
 | |
| 	}
 | |
| 	void setHSV(int hue, int saturation, int brightness);
 | |
| 	void setRGB(int red, int green, int blue);
 | |
| 
 | |
| protected:
 | |
| 	void paintEvent(QPaintEvent *e);
 | |
| 
 | |
| 	void mousePressEvent(QMouseEvent *e);
 | |
| 	void mouseMoveEvent(QMouseEvent *e);
 | |
| 	void mouseReleaseEvent(QMouseEvent *e);
 | |
| 
 | |
| private:
 | |
| 	void setFromColor(QColor color);
 | |
| 	QCursor generateCursor();
 | |
| 
 | |
| 	void preparePalette();
 | |
| 	void updateCurrentPoint(QPoint localPosition);
 | |
| 
 | |
| 	QColor _topleft;
 | |
| 	QColor _topright;
 | |
| 	QColor _bottomleft;
 | |
| 	QColor _bottomright;
 | |
| 
 | |
| 	QImage _palette;
 | |
| 	bool _paletteInvalidated = false;
 | |
| 	float64 _x = 0.;
 | |
| 	float64 _y = 0.;
 | |
| 
 | |
| 	bool _choosing = false;
 | |
| 	base::Observable<void> _changed;
 | |
| 
 | |
| };
 | |
| 
 | |
| QCursor EditColorBox::Picker::generateCursor() {
 | |
| 	auto diameter = convertScale(16);
 | |
| 	auto line = convertScale(1);
 | |
| 	auto size = ((diameter + 2 * line) >= 32) ? 64 : 32;
 | |
| 	auto cursor = QImage(QSize(size, size) * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
 | |
| 	cursor.setDevicePixelRatio(cRetinaFactor());
 | |
| 	cursor.fill(Qt::transparent);
 | |
| 	{
 | |
| 		Painter p(&cursor);
 | |
| 		PainterHighQualityEnabler hq(p);
 | |
| 
 | |
| 		p.setBrush(Qt::NoBrush);
 | |
| 		auto pen = QPen(Qt::white);
 | |
| 		pen.setWidth(3 * line);
 | |
| 		p.setPen(pen);
 | |
| 		p.drawEllipse((size - diameter) / 2, (size - diameter) / 2, diameter, diameter);
 | |
| 		pen = QPen(Qt::black);
 | |
| 		pen.setWidth(line);
 | |
| 		p.setPen(pen);
 | |
| 		p.drawEllipse((size - diameter) / 2, (size - diameter) / 2, diameter, diameter);
 | |
| 	}
 | |
| 	return QCursor(QPixmap::fromImage(cursor));
 | |
| }
 | |
| 
 | |
| EditColorBox::Picker::Picker(QWidget *parent, QColor color) : TWidget(parent) {
 | |
| 	setCursor(generateCursor());
 | |
| 
 | |
| 	auto size = QSize(st::colorPickerSize, st::colorPickerSize);
 | |
| 	resize(size);
 | |
| 
 | |
| 	_palette = QImage(size * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
 | |
| 
 | |
| 	setFromColor(color);
 | |
| }
 | |
| 
 | |
| void EditColorBox::Picker::paintEvent(QPaintEvent *e) {
 | |
| 	Painter p(this);
 | |
| 
 | |
| 	preparePalette();
 | |
| 
 | |
| 	p.drawImage(0, 0, _palette);
 | |
| 
 | |
| 	auto left = anim::color(_topleft, _bottomleft, _y);
 | |
| 	auto right = anim::color(_topright, _bottomright, _y);
 | |
| 	auto color = anim::color(left, right, _x);
 | |
| 	auto lightness = 0.2989 * color.redF() + 0.5870 * color.greenF() + 0.1140 * color.blueF();
 | |
| 	auto pen = QPen((lightness > 0.6) ? QColor(0, 0, 0) : QColor(255, 255, 255));
 | |
| 	pen.setWidth(st::colorPickerMarkLine);
 | |
| 	p.setPen(pen);
 | |
| 	p.setBrush(Qt::NoBrush);
 | |
| 
 | |
| 	auto x = anim::interpolate(0, width() - 1, _x);
 | |
| 	auto y = anim::interpolate(0, height() - 1, _y);
 | |
| 	PainterHighQualityEnabler hq(p);
 | |
| 
 | |
| 	p.drawEllipse(QRect(x - st::colorPickerMarkRadius, y - st::colorPickerMarkRadius, 2 * st::colorPickerMarkRadius, 2 * st::colorPickerMarkRadius));
 | |
| }
 | |
| 
 | |
| void EditColorBox::Picker::mousePressEvent(QMouseEvent *e) {
 | |
| 	_choosing = true;
 | |
| 	updateCurrentPoint(e->pos());
 | |
| }
 | |
| 
 | |
| void EditColorBox::Picker::mouseMoveEvent(QMouseEvent *e) {
 | |
| 	if (_choosing) {
 | |
| 		updateCurrentPoint(e->pos());
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void EditColorBox::Picker::mouseReleaseEvent(QMouseEvent *e) {
 | |
| 	_choosing = false;
 | |
| }
 | |
| 
 | |
| void EditColorBox::Picker::preparePalette() {
 | |
| 	if (!_paletteInvalidated) return;
 | |
| 	_paletteInvalidated = false;
 | |
| 
 | |
| 	auto size = _palette.width();
 | |
| 	auto ints = reinterpret_cast<uint32*>(_palette.bits());
 | |
| 	auto intsAddPerLine = (_palette.bytesPerLine() - size * sizeof(uint32)) / sizeof(uint32);
 | |
| 
 | |
| 	constexpr auto Large = 1024 * 1024;
 | |
| 	constexpr auto LargeBit = 20; // n / Large == (n >> LargeBit)
 | |
| 	auto part = Large / size;
 | |
| 
 | |
| 	auto topleft = anim::shifted(_topleft);
 | |
| 	auto topright = anim::shifted(_topright);
 | |
| 	auto bottomleft = anim::shifted(_bottomleft);
 | |
| 	auto bottomright = anim::shifted(_bottomright);
 | |
| 
 | |
| 	auto y_accumulated = 0;
 | |
| 	for (auto y = 0; y != size; ++y, y_accumulated += part) {
 | |
| 		auto y_ratio = y_accumulated >> (LargeBit - 8); // (y_accumulated * 256) / Large;
 | |
| 		// 0 <= y_accumulated < Large
 | |
| 		// 0 <= y_ratio < 256
 | |
| 
 | |
| 		auto top_ratio = 255 - y_ratio;
 | |
| 		auto bottom_ratio = y_ratio;
 | |
| 
 | |
| 		auto left = anim::reshifted(bottomleft * bottom_ratio + topleft * top_ratio);
 | |
| 		auto right = anim::reshifted(bottomright * bottom_ratio + topright * top_ratio);
 | |
| 
 | |
| 		auto x_accumulated = 0;
 | |
| 		for (auto x = 0; x != size; ++x, x_accumulated += part) {
 | |
| 			auto x_ratio = x_accumulated >> (LargeBit - 8); // (x_accumulated * 256) / Large;
 | |
| 			// 0 <= x_accumulated < Large
 | |
| 			// 0 <= x_ratio < 256
 | |
| 
 | |
| 			auto left_ratio = 255 - x_ratio;
 | |
| 			auto right_ratio = x_ratio;
 | |
| 
 | |
| 			*ints++ = anim::unshifted(left * left_ratio + right * right_ratio);
 | |
| 		}
 | |
| 		ints += intsAddPerLine;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void EditColorBox::Picker::updateCurrentPoint(QPoint localPosition) {
 | |
| 	auto x = snap(localPosition.x(), 0, width()) / float64(width());
 | |
| 	auto y = snap(localPosition.y(), 0, height()) / float64(height());
 | |
| 	if (_x != x || _y != y) {
 | |
| 		_x = x;
 | |
| 		_y = y;
 | |
| 		update();
 | |
| 		_changed.notify();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void EditColorBox::Picker::setHSV(int hue, int saturation, int brightness) {
 | |
| 	_topleft = QColor(255, 255, 255);
 | |
| 	_topright.setHsv(qMax(0, hue), 255, 255);
 | |
| 	_topright = _topright.toRgb();
 | |
| 	_bottomleft = _bottomright = QColor(0, 0, 0);
 | |
| 
 | |
| 	_paletteInvalidated = true;
 | |
| 	update();
 | |
| 
 | |
| 	_x = snap(saturation / 255., 0., 1.);
 | |
| 	_y = 1. - snap(brightness / 255., 0., 1.);
 | |
| }
 | |
| 
 | |
| void EditColorBox::Picker::setRGB(int red, int green, int blue) {
 | |
| 	setFromColor(QColor(red, green, blue));
 | |
| }
 | |
| 
 | |
| void EditColorBox::Picker::setFromColor(QColor color) {
 | |
| 	setHSV(color.hsvHue(), color.hsvSaturation(), color.value());
 | |
| }
 | |
| 
 | |
| class EditColorBox::Slider : public TWidget {
 | |
| public:
 | |
| 	enum class Direction {
 | |
| 		Horizontal,
 | |
| 		Vertical,
 | |
| 	};
 | |
| 	enum class Type {
 | |
| 		Hue,
 | |
| 		Opacity,
 | |
| 	};
 | |
| 	Slider(QWidget *parent, Direction direction, Type type, QColor color);
 | |
| 
 | |
| 	base::Observable<void> &changed() {
 | |
| 		return _changed;
 | |
| 	}
 | |
| 	float64 value() const {
 | |
| 		return _value;
 | |
| 	}
 | |
| 	void setValue(float64 value) {
 | |
| 		_value = snap(value, 0., 1.);
 | |
| 		update();
 | |
| 	}
 | |
| 	void setHSV(int hue, int saturation, int brightness);
 | |
| 	void setRGB(int red, int green, int blue);
 | |
| 	void setAlpha(int alpha);
 | |
| 
 | |
| protected:
 | |
| 	void paintEvent(QPaintEvent *e) override;
 | |
| 	void resizeEvent(QResizeEvent *e) override;
 | |
| 
 | |
| 	void mousePressEvent(QMouseEvent *e) override;
 | |
| 	void mouseMoveEvent(QMouseEvent *e) override;
 | |
| 	void mouseReleaseEvent(QMouseEvent *e) override;
 | |
| 
 | |
| private:
 | |
| 	float64 valueFromColor(QColor color) const;
 | |
| 	float64 valueFromHue(int hue) const;
 | |
| 	bool isHorizontal() const {
 | |
| 		return (_direction == Direction::Horizontal);
 | |
| 	}
 | |
| 	void colorUpdated();
 | |
| 	void prepareMinSize();
 | |
| 	void generatePixmap();
 | |
| 	void updatePixmapFromMask();
 | |
| 	void updateCurrentPoint(QPoint localPosition);
 | |
| 
 | |
| 	Direction _direction = Direction::Horizontal;
 | |
| 	Type _type = Type::Hue;
 | |
| 
 | |
| 	QColor _color;
 | |
| 	float64 _value = 0;
 | |
| 
 | |
| 	QImage _mask;
 | |
| 	QPixmap _pixmap;
 | |
| 	QBrush _transparent;
 | |
| 
 | |
| 	bool _choosing = false;
 | |
| 	base::Observable<void> _changed;
 | |
| 
 | |
| };
 | |
| 
 | |
| EditColorBox::Slider::Slider(QWidget *parent, Direction direction, Type type, QColor color) : TWidget(parent)
 | |
| , _direction(direction)
 | |
| , _type(type)
 | |
| , _color(color.red(), color.green(), color.blue())
 | |
| , _value(valueFromColor(color))
 | |
| , _transparent((_type == Type::Hue) ? QBrush() : style::transparentPlaceholderBrush()) {
 | |
| 	prepareMinSize();
 | |
| }
 | |
| 
 | |
| void EditColorBox::Slider::prepareMinSize() {
 | |
| 	auto minSize = st::colorSliderSkip + st::colorSliderWidth + st::colorSliderSkip;
 | |
| 	resize(minSize, minSize);
 | |
| }
 | |
| 
 | |
| void EditColorBox::Slider::paintEvent(QPaintEvent *e) {
 | |
| 	Painter p(this);
 | |
| 	auto to = rect().marginsRemoved(QMargins(st::colorSliderSkip, st::colorSliderSkip, st::colorSliderSkip, st::colorSliderSkip));
 | |
| 	Ui::Shadow::paint(p, to, width(), st::defaultRoundShadow);
 | |
| 	if (_type == Type::Opacity) {
 | |
| 		p.fillRect(to, _transparent);
 | |
| 	}
 | |
| 	p.drawPixmap(to, _pixmap, _pixmap.rect());
 | |
| 	if (isHorizontal()) {
 | |
| 		auto x = st::colorSliderSkip + qRound(_value * to.width());
 | |
| 		st::colorSliderArrowTop.paint(p, x - st::colorSliderArrowTop.width() / 2, 0, width());
 | |
| 		st::colorSliderArrowBottom.paint(p, x - st::colorSliderArrowBottom.width() / 2, height() - st::colorSliderArrowBottom.height(), width());
 | |
| 	} else {
 | |
| 		auto y = st::colorSliderSkip + qRound(_value * to.height());
 | |
| 		st::colorSliderArrowLeft.paint(p, 0, y - st::colorSliderArrowLeft.height() / 2, width());
 | |
| 		st::colorSliderArrowRight.paint(p, width() - st::colorSliderArrowRight.width(), y - st::colorSliderArrowRight.height() / 2, width());
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void EditColorBox::Slider::resizeEvent(QResizeEvent *e) {
 | |
| 	generatePixmap();
 | |
| 	update();
 | |
| }
 | |
| 
 | |
| void EditColorBox::Slider::mousePressEvent(QMouseEvent *e) {
 | |
| 	_choosing = true;
 | |
| 	updateCurrentPoint(e->pos());
 | |
| }
 | |
| 
 | |
| void EditColorBox::Slider::mouseMoveEvent(QMouseEvent *e) {
 | |
| 	if (_choosing) {
 | |
| 		updateCurrentPoint(e->pos());
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void EditColorBox::Slider::mouseReleaseEvent(QMouseEvent *e) {
 | |
| 	_choosing = false;
 | |
| }
 | |
| 
 | |
| void EditColorBox::Slider::generatePixmap() {
 | |
| 	auto size = (isHorizontal() ? width() : height()) * cIntRetinaFactor();
 | |
| 	auto image = QImage(size, cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
 | |
| 	image.setDevicePixelRatio(cRetinaFactor());
 | |
| 	auto ints = reinterpret_cast<uint32*>(image.bits());
 | |
| 	auto intsPerLine = image.bytesPerLine() / sizeof(uint32);
 | |
| 	auto intsPerLineAdded = intsPerLine - size;
 | |
| 
 | |
| 	constexpr auto Large = 1024 * 1024;
 | |
| 	constexpr auto LargeBit = 20; // n / Large == (n >> LargeBit)
 | |
| 	auto part = Large / size;
 | |
| 
 | |
| 	if (_type == Type::Hue) {
 | |
| 		QColor color;
 | |
| 		for (auto x = 0; x != size; ++x) {
 | |
| 			color.setHsv(x * 360 / size, 255, 255);
 | |
| 			auto value = anim::getPremultiplied(color.toRgb());
 | |
| 			for (auto y = 0; y != cIntRetinaFactor(); ++y) {
 | |
| 				ints[y * intsPerLine] = value;
 | |
| 			}
 | |
| 			++ints;
 | |
| 		}
 | |
| 		if (!isHorizontal()) {
 | |
| 			image = std::move(image).transformed(QTransform(0, -1, 1, 0, 0, 0));
 | |
| 		}
 | |
| 		_pixmap = App::pixmapFromImageInPlace(std::move(image));
 | |
| 	} else {
 | |
| 		auto color = anim::shifted(QColor(255, 255, 255, 255));
 | |
| 		auto transparent = anim::shifted(QColor(255, 255, 255, 0));
 | |
| 		for (auto y = 0; y != cIntRetinaFactor(); ++y) {
 | |
| 			auto x_accumulated = 0;
 | |
| 			for (auto x = 0; x != size; ++x, x_accumulated += part) {
 | |
| 				auto x_ratio = x_accumulated >> (LargeBit - 8);
 | |
| 				// 0 <= x_accumulated < Large
 | |
| 				// 0 <= x_ratio < 256
 | |
| 
 | |
| 				*ints++ = anim::unshifted(color * x_ratio + transparent * (255 - x_ratio));
 | |
| 			}
 | |
| 			ints += intsPerLineAdded;
 | |
| 		}
 | |
| 		if (!isHorizontal()) {
 | |
| 			image = std::move(image).transformed(QTransform(0, -1, 1, 0, 0, 0));
 | |
| 		}
 | |
| 		_mask = std::move(image);
 | |
| 		updatePixmapFromMask();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void EditColorBox::Slider::setHSV(int hue, int saturation, int brightness) {
 | |
| 	if (_type == Type::Hue) {
 | |
| 		// hue == 360 converts to 0 if done in general way
 | |
| 		_value = valueFromHue(hue);
 | |
| 		update();
 | |
| 	} else {
 | |
| 		_color.setHsv(hue, saturation, brightness);
 | |
| 		colorUpdated();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void EditColorBox::Slider::setRGB(int red, int green, int blue) {
 | |
| 	_color.setRgb(red, green, blue);
 | |
| 	colorUpdated();
 | |
| }
 | |
| 
 | |
| void EditColorBox::Slider::colorUpdated() {
 | |
| 	if (_type == Type::Hue) {
 | |
| 		_value = valueFromColor(_color);
 | |
| 	} else if (!_mask.isNull()) {
 | |
| 		updatePixmapFromMask();
 | |
| 	}
 | |
| 	update();
 | |
| }
 | |
| 
 | |
| float64 EditColorBox::Slider::valueFromColor(QColor color) const {
 | |
| 	return (_type == Type::Hue) ? valueFromHue(color.hsvHue()) : color.alphaF();
 | |
| }
 | |
| 
 | |
| float64 EditColorBox::Slider::valueFromHue(int hue) const {
 | |
| 	return (1. - snap(hue, 0, 360) / 360.);
 | |
| }
 | |
| 
 | |
| void EditColorBox::Slider::setAlpha(int alpha) {
 | |
| 	if (_type == Type::Opacity) {
 | |
| 		_value = snap(alpha, 0, 255) / 255.;
 | |
| 		update();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void EditColorBox::Slider::updatePixmapFromMask() {
 | |
| 	_pixmap = App::pixmapFromImageInPlace(style::colorizeImage(_mask, _color));
 | |
| }
 | |
| 
 | |
| void EditColorBox::Slider::updateCurrentPoint(QPoint localPosition) {
 | |
| 	auto coord = (isHorizontal() ? localPosition.x() : localPosition.y()) - st::colorSliderSkip;
 | |
| 	auto maximum = (isHorizontal() ? width() : height()) - 2 * st::colorSliderSkip;
 | |
| 	auto value = snap(coord, 0, maximum) / float64(maximum);
 | |
| 	if (_value != value) {
 | |
| 		_value = value;
 | |
| 		update();
 | |
| 		_changed.notify();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| class EditColorBox::Field : public Ui::MaskedInputField {
 | |
| public:
 | |
| 	Field(QWidget *parent, const style::InputField &st, const QString &placeholder, int limit, const QString &units = QString());
 | |
| 
 | |
| 	int value() const {
 | |
| 		return getLastText().toInt();
 | |
| 	}
 | |
| 
 | |
| 	void setTextWithFocus(const QString &text) {
 | |
| 		setText(text);
 | |
| 		if (hasFocus()) {
 | |
| 			selectAll();
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| protected:
 | |
| 	void correctValue(const QString &was, int wasCursor, QString &now, int &nowCursor) override;
 | |
| 	void paintAdditionalPlaceholder(Painter &p, TimeMs ms) override;
 | |
| 
 | |
| 	void wheelEvent(QWheelEvent *e) override;
 | |
| 	void keyPressEvent(QKeyEvent *e) override;
 | |
| 
 | |
| private:
 | |
| 	void changeValue(int delta);
 | |
| 
 | |
| 	QString _placeholder, _units;
 | |
| 	int _limit = 0;
 | |
| 	int _digitLimit = 1;
 | |
| 	int _wheelDelta = 0;
 | |
| 
 | |
| };
 | |
| 
 | |
| EditColorBox::Field::Field(QWidget *parent, const style::InputField &st, const QString &placeholder, int limit, const QString &units) : Ui::MaskedInputField(parent, st)
 | |
| , _placeholder(placeholder)
 | |
| , _units(units)
 | |
| , _limit(limit)
 | |
| , _digitLimit(QString::number(_limit).size()) {
 | |
| }
 | |
| 
 | |
| void EditColorBox::Field::correctValue(const QString &was, int wasCursor, QString &now, int &nowCursor) {
 | |
| 	QString newText;
 | |
| 	int oldPos(nowCursor), newPos(-1), oldLen(now.length());
 | |
| 
 | |
| 	newText.reserve(oldLen);
 | |
| 	for (int i = 0; i < oldLen; ++i) {
 | |
| 		if (i == oldPos) {
 | |
| 			newPos = newText.length();
 | |
| 		}
 | |
| 
 | |
| 		QChar ch(now[i]);
 | |
| 		if (ch.isDigit()) {
 | |
| 			newText += ch;
 | |
| 		}
 | |
| 		if (newText.size() >= _digitLimit) {
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 	if (newPos < 0 || newPos > newText.size()) {
 | |
| 		newPos = newText.size();
 | |
| 	}
 | |
| 	if (newText.toInt() > _limit) {
 | |
| 		newText = QString::number(_limit);
 | |
| 		newPos = newText.size();
 | |
| 	}
 | |
| 	if (newText != now) {
 | |
| 		now = newText;
 | |
| 		setText(now);
 | |
| 		startPlaceholderAnimation();
 | |
| 		nowCursor = -1;
 | |
| 	}
 | |
| 	if (newPos != nowCursor) {
 | |
| 		nowCursor = newPos;
 | |
| 		setCursorPosition(nowCursor);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void EditColorBox::Field::paintAdditionalPlaceholder(Painter &p, TimeMs ms) {
 | |
| 	p.setFont(_st.font);
 | |
| 	p.setPen(_st.placeholderFg);
 | |
| 	auto inner = QRect(_st.textMargins.right(), _st.textMargins.top(), width() - 2 * _st.textMargins.right(), height() - _st.textMargins.top() - _st.textMargins.bottom());
 | |
| 	p.drawText(inner, _placeholder, style::al_topleft);
 | |
| 	if (!_units.isEmpty()) {
 | |
| 		p.drawText(inner, _units, style::al_topright);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void EditColorBox::Field::wheelEvent(QWheelEvent *e) {
 | |
| 	if (!hasFocus()) {
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	auto deltaX = e->angleDelta().x(), deltaY = e->angleDelta().y();
 | |
| 	if (cPlatform() == dbipMac || cPlatform() == dbipMacOld) {
 | |
| 		deltaY *= -1;
 | |
| 	} else {
 | |
| 		deltaX *= -1;
 | |
| 	}
 | |
| 	_wheelDelta += (qAbs(deltaX) > qAbs(deltaY)) ? deltaX : deltaY;
 | |
| 
 | |
| 	constexpr auto step = 5;
 | |
| 	if (auto delta = _wheelDelta / step) {
 | |
| 		_wheelDelta -= delta * step;
 | |
| 		changeValue(delta);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void EditColorBox::Field::changeValue(int delta) {
 | |
| 	auto currentValue = value();
 | |
| 	auto newValue = snap(currentValue + delta, 0, _limit);
 | |
| 	if (newValue != currentValue) {
 | |
| 		setText(QString::number(newValue));
 | |
| 		setFocus();
 | |
| 		selectAll();
 | |
| 		emit changed();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void EditColorBox::Field::keyPressEvent(QKeyEvent *e) {
 | |
| 	if (e->key() == Qt::Key_Up) {
 | |
| 		changeValue(1);
 | |
| 	} else if (e->key() == Qt::Key_Down) {
 | |
| 		changeValue(-1);
 | |
| 	} else {
 | |
| 		MaskedInputField::keyPressEvent(e);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| class EditColorBox::ResultField : public Ui::MaskedInputField {
 | |
| public:
 | |
| 	ResultField(QWidget *parent, const style::InputField &st);
 | |
| 
 | |
| 	void setTextWithFocus(const QString &text) {
 | |
| 		setText(text);
 | |
| 		if (hasFocus()) {
 | |
| 			selectAll();
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| protected:
 | |
| 	void correctValue(const QString &was, int wasCursor, QString &now, int &nowCursor) override;
 | |
| 	void paintAdditionalPlaceholder(Painter &p, TimeMs ms) override;
 | |
| 
 | |
| };
 | |
| 
 | |
| EditColorBox::ResultField::ResultField(QWidget *parent, const style::InputField &st) : Ui::MaskedInputField(parent, st) {
 | |
| }
 | |
| 
 | |
| void EditColorBox::ResultField::correctValue(const QString &was, int wasCursor, QString &now, int &nowCursor) {
 | |
| 	QString newText;
 | |
| 	int oldPos(nowCursor), newPos(-1), oldLen(now.length());
 | |
| 
 | |
| 	newText.reserve(oldLen);
 | |
| 	for (int i = 0; i < oldLen; ++i) {
 | |
| 		if (i == oldPos) {
 | |
| 			newPos = newText.length();
 | |
| 		}
 | |
| 
 | |
| 		QChar ch(now[i]);
 | |
| 		auto code = ch.unicode();
 | |
| 		if ((code >= '0' && code <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')) {
 | |
| 			newText += ch;
 | |
| 		}
 | |
| 		if (newText.size() >= 8) {
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 	if (newPos < 0 || newPos > newText.size()) {
 | |
| 		newPos = newText.size();
 | |
| 	}
 | |
| 	if (newText != now) {
 | |
| 		now = newText;
 | |
| 		setText(now);
 | |
| 		startPlaceholderAnimation();
 | |
| 		nowCursor = -1;
 | |
| 	}
 | |
| 	if (newPos != nowCursor) {
 | |
| 		nowCursor = newPos;
 | |
| 		setCursorPosition(nowCursor);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void EditColorBox::ResultField::paintAdditionalPlaceholder(Painter &p, TimeMs ms) {
 | |
| 	p.setFont(_st.font);
 | |
| 	p.setPen(_st.placeholderFg);
 | |
| 	p.drawText(QRect(_st.textMargins.right(), _st.textMargins.top(), width(), height() - _st.textMargins.top() - _st.textMargins.bottom()), "#", style::al_topleft);
 | |
| }
 | |
| 
 | |
| EditColorBox::EditColorBox(QWidget*, const QString &title, QColor current) : BoxContent()
 | |
| , _title(title)
 | |
| , _picker(this, current)
 | |
| , _hueSlider(this, Slider::Direction::Vertical, Slider::Type::Hue, current)
 | |
| , _opacitySlider(this, Slider::Direction::Horizontal, Slider::Type::Opacity, current)
 | |
| , _hueField(this, st::colorValueInput, "H", 360, QString() + QChar(176)) // degree character
 | |
| , _saturationField(this, st::colorValueInput, "S", 100, "%")
 | |
| , _brightnessField(this, st::colorValueInput, "B", 100, "%")
 | |
| , _redField(this, st::colorValueInput, "R", 255)
 | |
| , _greenField(this, st::colorValueInput, "G", 255)
 | |
| , _blueField(this, st::colorValueInput, "B", 255)
 | |
| , _result(this, st::colorResultInput)
 | |
| , _transparent(style::transparentPlaceholderBrush())
 | |
| , _current(current)
 | |
| , _new(current) {
 | |
| }
 | |
| 
 | |
| void EditColorBox::prepare() {
 | |
| 	setTitle(_title);
 | |
| 
 | |
| 	connect(_hueField, SIGNAL(changed()), this, SLOT(onFieldChanged()));
 | |
| 	connect(_saturationField, SIGNAL(changed()), this, SLOT(onFieldChanged()));
 | |
| 	connect(_brightnessField, SIGNAL(changed()), this, SLOT(onFieldChanged()));
 | |
| 	connect(_redField, SIGNAL(changed()), this, SLOT(onFieldChanged()));
 | |
| 	connect(_greenField, SIGNAL(changed()), this, SLOT(onFieldChanged()));
 | |
| 	connect(_blueField, SIGNAL(changed()), this, SLOT(onFieldChanged()));
 | |
| 	connect(_result, SIGNAL(changed()), this, SLOT(onFieldChanged()));
 | |
| 
 | |
| 	connect(_hueField, SIGNAL(submitted(bool)), this, SLOT(onFieldSubmitted()));
 | |
| 	connect(_saturationField, SIGNAL(submitted(bool)), this, SLOT(onFieldSubmitted()));
 | |
| 	connect(_brightnessField, SIGNAL(submitted(bool)), this, SLOT(onFieldSubmitted()));
 | |
| 	connect(_redField, SIGNAL(submitted(bool)), this, SLOT(onFieldSubmitted()));
 | |
| 	connect(_greenField, SIGNAL(submitted(bool)), this, SLOT(onFieldSubmitted()));
 | |
| 	connect(_blueField, SIGNAL(submitted(bool)), this, SLOT(onFieldSubmitted()));
 | |
| 	connect(_result, SIGNAL(submitted(bool)), this, SLOT(onFieldSubmitted()));
 | |
| 
 | |
| 	addButton(lang(lng_settings_save), [this] { saveColor(); });
 | |
| 	addButton(lang(lng_cancel), [this] { closeBox(); });
 | |
| 
 | |
| 	auto height = st::colorEditSkip + st::colorPickerSize + st::colorEditSkip + st::colorSliderWidth + st::colorEditSkip;
 | |
| 	setDimensions(st::colorEditWidth, height);
 | |
| 
 | |
| 	subscribe(_picker->changed(), [this] { updateFromControls(); });
 | |
| 	subscribe(_hueSlider->changed(), [this] { updateFromControls(); });
 | |
| 	subscribe(_opacitySlider->changed(), [this] { updateFromControls(); });
 | |
| 	updateFromControls();
 | |
| }
 | |
| 
 | |
| void EditColorBox::setInnerFocus() {
 | |
| 	_result->setFocus();
 | |
| 	_result->selectAll();
 | |
| }
 | |
| 
 | |
| void EditColorBox::onFieldChanged() {
 | |
| 	auto emitter = sender();
 | |
| 	auto checkHSVSender = [this, emitter](QObject *field) {
 | |
| 		if (emitter == field) {
 | |
| 			updateFromHSVFields();
 | |
| 		}
 | |
| 	};
 | |
| 	auto checkRGBSender = [this, emitter](QObject *field) {
 | |
| 		if (emitter == field) {
 | |
| 			updateFromRGBFields();
 | |
| 		}
 | |
| 	};
 | |
| 	checkHSVSender(_hueField);
 | |
| 	checkHSVSender(_saturationField);
 | |
| 	checkHSVSender(_brightnessField);
 | |
| 	checkRGBSender(_redField);
 | |
| 	checkRGBSender(_greenField);
 | |
| 	checkRGBSender(_blueField);
 | |
| 	if (emitter == _result) {
 | |
| 		updateFromResultField();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void EditColorBox::onFieldSubmitted() {
 | |
| 	Ui::MaskedInputField *fields[] = {
 | |
| 		_hueField,
 | |
| 		_saturationField,
 | |
| 		_brightnessField,
 | |
| 		_redField,
 | |
| 		_greenField,
 | |
| 		_blueField,
 | |
| 		_result
 | |
| 	};
 | |
| 	for (auto i = 0, count = int(base::array_size(fields)); i + 1 != count; ++i) {
 | |
| 		if (fields[i]->hasFocus()) {
 | |
| 			fields[i + 1]->setFocus();
 | |
| 			fields[i + 1]->selectAll();
 | |
| 			return;
 | |
| 		}
 | |
| 	}
 | |
| 	if (_result->hasFocus()) {
 | |
| 		saveColor();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void EditColorBox::saveColor() {
 | |
| 	_cancelCallback = base::lambda<void()>();
 | |
| 	if (_saveCallback) {
 | |
| 		_saveCallback(_new.toRgb());
 | |
| 	}
 | |
| 	closeBox();
 | |
| }
 | |
| 
 | |
| void EditColorBox::updateHSVFields() {
 | |
| 	auto hue = qRound((1. - _hueSlider->value()) * 360);
 | |
| 	auto saturation = qRound(_picker->valueX() * 255);
 | |
| 	auto brightness = qRound((1. - _picker->valueY()) * 255);
 | |
| 	auto alpha = qRound(_opacitySlider->value() * 255);
 | |
| 	_hueField->setTextWithFocus(QString::number(hue));
 | |
| 	_saturationField->setTextWithFocus(QString::number(percentFromByte(saturation)));
 | |
| 	_brightnessField->setTextWithFocus(QString::number(percentFromByte(brightness)));
 | |
| }
 | |
| 
 | |
| void EditColorBox::updateRGBFields() {
 | |
| 	_redField->setTextWithFocus(QString::number(_new.red()));
 | |
| 	_greenField->setTextWithFocus(QString::number(_new.green()));
 | |
| 	_blueField->setTextWithFocus(QString::number(_new.blue()));
 | |
| }
 | |
| 
 | |
| void EditColorBox::updateResultField() {
 | |
| 	auto text = QString();
 | |
| 	auto addHex = [&text](int value) {
 | |
| 		if (value >= 0 && value <= 9) {
 | |
| 			text.append('0' + value);
 | |
| 		} else if (value >= 10 && value <= 15) {
 | |
| 			text.append('a' + (value - 10));
 | |
| 		}
 | |
| 	};
 | |
| 	auto addValue = [addHex](int value) {
 | |
| 		addHex(value / 16);
 | |
| 		addHex(value % 16);
 | |
| 	};
 | |
| 	addValue(_new.red());
 | |
| 	addValue(_new.green());
 | |
| 	addValue(_new.blue());
 | |
| 	if (_new.alpha() != 255) {
 | |
| 		addValue(_new.alpha());
 | |
| 	}
 | |
| 	_result->setTextWithFocus(text);
 | |
| }
 | |
| 
 | |
| void EditColorBox::resizeEvent(QResizeEvent *e) {
 | |
| 	auto fullwidth = _picker->width() + 2 * (st::colorEditSkip - st::colorSliderSkip) + _hueSlider->width() + st::colorSampleSize.width();
 | |
| 	auto left = (width() - fullwidth) / 2;
 | |
| 	_picker->moveToLeft(left, st::colorEditSkip);
 | |
| 	_hueSlider->setGeometryToLeft(_picker->x() + _picker->width() + st::colorEditSkip - st::colorSliderSkip, st::colorEditSkip - st::colorSliderSkip, _hueSlider->width(), st::colorPickerSize + 2 * st::colorSliderSkip);
 | |
| 	_opacitySlider->setGeometryToLeft(_picker->x() - st::colorSliderSkip, _picker->y() + _picker->height() + st::colorEditSkip - st::colorSliderSkip, _picker->width() + 2 * st::colorSliderSkip, _opacitySlider->height());
 | |
| 	auto fieldLeft = _hueSlider->x() + _hueSlider->width() - st::colorSliderSkip + st::colorEditSkip;
 | |
| 	auto fieldWidth = st::colorSampleSize.width();
 | |
| 	auto fieldHeight = _hueField->height();
 | |
| 	_newRect = QRect(fieldLeft, st::colorEditSkip, fieldWidth, st::colorSampleSize.height());
 | |
| 	_currentRect = _newRect.translated(0, st::colorSampleSize.height());
 | |
| 	_hueField->setGeometryToLeft(fieldLeft, _currentRect.y() + _currentRect.height() + st::colorFieldSkip, fieldWidth, fieldHeight);
 | |
| 	_saturationField->setGeometryToLeft(fieldLeft, _hueField->y() + _hueField->height(), fieldWidth, fieldHeight);
 | |
| 	_brightnessField->setGeometryToLeft(fieldLeft, _saturationField->y() + _saturationField->height(), fieldWidth, fieldHeight);
 | |
| 	_redField->setGeometryToLeft(fieldLeft, _brightnessField->y() + _brightnessField->height() + st::colorFieldSkip, fieldWidth, fieldHeight);
 | |
| 	_greenField->setGeometryToLeft(fieldLeft, _redField->y() + _redField->height(), fieldWidth, fieldHeight);
 | |
| 	_blueField->setGeometryToLeft(fieldLeft, _greenField->y() + _greenField->height(), fieldWidth, fieldHeight);
 | |
| 	_result->setGeometryToLeft(fieldLeft - (st::colorEditSkip + st::colorSliderWidth), _opacitySlider->y() + _opacitySlider->height() - st::colorSliderSkip - _result->height(), fieldWidth + (st::colorEditSkip + st::colorSliderWidth), fieldHeight);
 | |
| }
 | |
| 
 | |
| void EditColorBox::paintEvent(QPaintEvent *e) {
 | |
| 	BoxContent::paintEvent(e);
 | |
| 
 | |
| 	Painter p(this);
 | |
| 	Ui::Shadow::paint(p, _picker->geometry(), width(), st::defaultRoundShadow);
 | |
| 
 | |
| 	Ui::Shadow::paint(p, QRect(_newRect.x(), _newRect.y(), _newRect.width(), _newRect.height() + _currentRect.height()), width(), st::defaultRoundShadow);
 | |
| 	if (_new.alphaF() < 1.) {
 | |
| 		p.fillRect(myrtlrect(_newRect), _transparent);
 | |
| 	}
 | |
| 	p.fillRect(myrtlrect(_newRect), _new);
 | |
| 	if (_current.alphaF() < 1.) {
 | |
| 		p.fillRect(myrtlrect(_currentRect), _transparent);
 | |
| 	}
 | |
| 	p.fillRect(myrtlrect(_currentRect), _current);
 | |
| }
 | |
| 
 | |
| void EditColorBox::mousePressEvent(QMouseEvent *e) {
 | |
| 	if (myrtlrect(_currentRect).contains(e->pos())) {
 | |
| 		updateFromColor(_current);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void EditColorBox::updateFromColor(QColor color) {
 | |
| 	_new = color;
 | |
| 	updateControlsFromColor();
 | |
| 	updateRGBFields();
 | |
| 	updateHSVFields();
 | |
| 	updateResultField();
 | |
| 	update();
 | |
| }
 | |
| 
 | |
| void EditColorBox::updateFromControls() {
 | |
| 	auto hue = qRound((1. - _hueSlider->value()) * 360);
 | |
| 	auto saturation = qRound(_picker->valueX() * 255);
 | |
| 	auto brightness = qRound((1. - _picker->valueY()) * 255);
 | |
| 	auto alpha = qRound(_opacitySlider->value() * 255);
 | |
| 	setHSV(hue, saturation, brightness, alpha);
 | |
| 	updateHSVFields();
 | |
| 	updateControlsFromHSV(hue, saturation, brightness);
 | |
| }
 | |
| 
 | |
| void EditColorBox::updateFromHSVFields() {
 | |
| 	auto hue = _hueField->value();
 | |
| 	auto saturation = percentToByte(_saturationField->value());
 | |
| 	auto brightness = percentToByte(_brightnessField->value());
 | |
| 	auto alpha = qRound(_opacitySlider->value() * 255);
 | |
| 	setHSV(hue, saturation, brightness, alpha);
 | |
| 	updateControlsFromHSV(hue, saturation, brightness);
 | |
| }
 | |
| 
 | |
| void EditColorBox::updateFromRGBFields() {
 | |
| 	auto red = _redField->value();
 | |
| 	auto blue = _blueField->value();
 | |
| 	auto green = _greenField->value();
 | |
| 	auto alpha = qRound(_opacitySlider->value() * 255);
 | |
| 	setRGB(red, green, blue, alpha);
 | |
| 	updateResultField();
 | |
| }
 | |
| 
 | |
| void EditColorBox::updateFromResultField() {
 | |
| 	auto text = _result->getLastText();
 | |
| 	if (text.size() != 6 && text.size() != 8) {
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	auto fromHex = [](QChar hex) {
 | |
| 		auto code = hex.unicode();
 | |
| 		if (code >= 'A' && code <= 'F') {
 | |
| 			return (code - 'A' + 10);
 | |
| 		} else if (code >= 'a' && code <= 'f') {
 | |
| 			return (code - 'a' + 10);
 | |
| 		}
 | |
| 		return code - '0';
 | |
| 	};
 | |
| 	auto fromChars = [fromHex](QChar a, QChar b) {
 | |
| 		return fromHex(a) * 0x10 + fromHex(b);
 | |
| 	};
 | |
| 	auto red = fromChars(text[0], text[1]);
 | |
| 	auto green = fromChars(text[2], text[3]);
 | |
| 	auto blue = fromChars(text[4], text[5]);
 | |
| 	auto alpha = (text.size() == 8) ? fromChars(text[6], text[7]) : 255;
 | |
| 	setRGB(red, green, blue, alpha);
 | |
| 	updateRGBFields();
 | |
| }
 | |
| 
 | |
| void EditColorBox::updateControlsFromHSV(int hue, int saturation, int brightness) {
 | |
| 	_picker->setHSV(hue, saturation, brightness);
 | |
| 	_hueSlider->setHSV(hue, saturation, brightness);
 | |
| 	_opacitySlider->setHSV(hue, saturation, brightness);
 | |
| }
 | |
| 
 | |
| void EditColorBox::updateControlsFromColor() {
 | |
| 	auto red = _new.red();
 | |
| 	auto green = _new.green();
 | |
| 	auto blue = _new.blue();
 | |
| 	auto alpha = _new.alpha();
 | |
| 	_picker->setRGB(red, green, blue);
 | |
| 	_hueSlider->setRGB(red, green, blue);
 | |
| 	_opacitySlider->setRGB(red, green, blue);
 | |
| 	_opacitySlider->setAlpha(alpha);
 | |
| }
 | |
| 
 | |
| void EditColorBox::setHSV(int hue, int saturation, int value, int alpha) {
 | |
| 	_new.setHsv(hue, saturation, value, alpha);
 | |
| 	updateRGBFields();
 | |
| 	updateResultField();
 | |
| 	update();
 | |
| }
 | |
| 
 | |
| void EditColorBox::setRGB(int red, int green, int blue, int alpha) {
 | |
| 	_new.setRgb(red, green, blue, alpha);
 | |
| 	updateControlsFromColor();
 | |
| 	updateHSVFields();
 | |
| 	update();
 | |
| } | 
