1 // This file is part of Desktop App Toolkit,
2 // a set of libraries for developing nice desktop applications.
3 //
4 // For license and copyright information please follow this link:
5 // https://github.com/desktop-app/legal/blob/master/LEGAL
6 //
7 #pragma once
8 
9 #include "base/unique_qptr.h"
10 #include "ui/style/style_core_direction.h"
11 #include "ui/ui_utility.h"
12 
13 #include <rpl/event_stream.h>
14 #include <rpl/map.h>
15 #include <rpl/distinct_until_changed.h>
16 
17 #include <QtWidgets/QWidget>
18 #include <QtCore/QPointer>
19 #include <QtGui/QtEvents>
20 
21 namespace Ui {
22 
23 void ToggleChildrenVisibility(not_null<QWidget*> widget, bool visible);
24 
25 } // namespace Ui
26 
27 class TWidget;
28 
29 template <typename Base>
30 class TWidgetHelper : public Base {
31 public:
32 	using Base::Base;
33 
getMargins()34 	virtual QMargins getMargins() const {
35 		return QMargins();
36 	}
37 
hideChildren()38 	void hideChildren() {
39 		Ui::ToggleChildrenVisibility(this, false);
40 	}
showChildren()41 	void showChildren() {
42 		Ui::ToggleChildrenVisibility(this, true);
43 	}
44 
45 	void moveToLeft(int x, int y, int outerw = 0) {
46 		auto margins = getMargins();
47 		x -= margins.left();
48 		y -= margins.top();
49 		Base::move(style::RightToLeft() ? ((outerw > 0 ? outerw : Base::parentWidget()->width()) - x - Base::width()) : x, y);
50 	}
51 	void moveToRight(int x, int y, int outerw = 0) {
52 		auto margins = getMargins();
53 		x -= margins.right();
54 		y -= margins.top();
55 		Base::move(style::RightToLeft() ? x : ((outerw > 0 ? outerw : Base::parentWidget()->width()) - x - Base::width()), y);
56 	}
57 	void setGeometryToLeft(int x, int y, int w, int h, int outerw = 0) {
58 		auto margins = getMargins();
59 		x -= margins.left();
60 		y -= margins.top();
61 		w -= margins.left() - margins.right();
62 		h -= margins.top() - margins.bottom();
63 		Base::setGeometry(style::RightToLeft() ? ((outerw > 0 ? outerw : Base::parentWidget()->width()) - x - w) : x, y, w, h);
64 	}
65 	void setGeometryToRight(int x, int y, int w, int h, int outerw = 0) {
66 		auto margins = getMargins();
67 		x -= margins.right();
68 		y -= margins.top();
69 		w -= margins.left() - margins.right();
70 		h -= margins.top() - margins.bottom();
71 		Base::setGeometry(style::RightToLeft() ? x : ((outerw > 0 ? outerw : Base::parentWidget()->width()) - x - w), y, w, h);
72 	}
myrtlpoint(int x,int y)73 	QPoint myrtlpoint(int x, int y) const {
74 		return style::rtlpoint(x, y, Base::width());
75 	}
myrtlpoint(const QPoint point)76 	QPoint myrtlpoint(const QPoint point) const {
77 		return style::rtlpoint(point, Base::width());
78 	}
myrtlrect(int x,int y,int w,int h)79 	QRect myrtlrect(int x, int y, int w, int h) const {
80 		return style::rtlrect(x, y, w, h, Base::width());
81 	}
myrtlrect(const QRect & rect)82 	QRect myrtlrect(const QRect &rect) const {
83 		return style::rtlrect(rect, Base::width());
84 	}
rtlupdate(const QRect & rect)85 	void rtlupdate(const QRect &rect) {
86 		Base::update(myrtlrect(rect));
87 	}
rtlupdate(int x,int y,int w,int h)88 	void rtlupdate(int x, int y, int w, int h) {
89 		Base::update(myrtlrect(x, y, w, h));
90 	}
91 
mapFromGlobal(const QPoint & point)92 	QPoint mapFromGlobal(const QPoint &point) const {
93 		return Base::mapFromGlobal(point);
94 	}
mapToGlobal(const QPoint & point)95 	QPoint mapToGlobal(const QPoint &point) const {
96 		return Base::mapToGlobal(point);
97 	}
mapFromGlobal(const QRect & rect)98 	QRect mapFromGlobal(const QRect &rect) const {
99 		return QRect(mapFromGlobal(rect.topLeft()), rect.size());
100 	}
mapToGlobal(const QRect & rect)101 	QRect mapToGlobal(const QRect &rect) const {
102 		return QRect(mapToGlobal(rect.topLeft()), rect.size());
103 	}
104 
105 protected:
106 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
enterEvent(QEnterEvent * e)107 	void enterEvent(QEnterEvent *e) final override {
108 		if (auto parent = tparent()) {
109 			parent->leaveToChildEvent(e, this);
110 		}
111 		return enterEventHook(e);
112 	}
113 #else // Qt >= 6.0.0
114 	void enterEvent(QEvent *e) final override {
115 		if (auto parent = tparent()) {
116 			parent->leaveToChildEvent(e, this);
117 		}
118 		return enterEventHook(static_cast<QEnterEvent*>(e));
119 	}
120 #endif // Qt < 6.0.0
enterEventHook(QEnterEvent * e)121 	virtual void enterEventHook(QEnterEvent *e) {
122 		return Base::enterEvent(e);
123 	}
124 
leaveEvent(QEvent * e)125 	void leaveEvent(QEvent *e) final override {
126 		if (auto parent = tparent()) {
127 			parent->enterFromChildEvent(e, this);
128 		}
129 		return leaveEventHook(e);
130 	}
leaveEventHook(QEvent * e)131 	virtual void leaveEventHook(QEvent *e) {
132 		return Base::leaveEvent(e);
133 	}
134 
135 	// e - from enterEvent() of child TWidget
leaveToChildEvent(QEvent * e,QWidget * child)136 	virtual void leaveToChildEvent(QEvent *e, QWidget *child) {
137 	}
138 
139 	// e - from leaveEvent() of child TWidget
enterFromChildEvent(QEvent * e,QWidget * child)140 	virtual void enterFromChildEvent(QEvent *e, QWidget *child) {
141 	}
142 
143 private:
tparent()144 	TWidget *tparent() {
145 		return qobject_cast<TWidget*>(Base::parentWidget());
146 	}
tparent()147 	const TWidget *tparent() const {
148 		return qobject_cast<const TWidget*>(Base::parentWidget());
149 	}
150 
151 	template <typename OtherBase>
152 	friend class TWidgetHelper;
153 
154 };
155 
156 class TWidget : public TWidgetHelper<QWidget> {
157 	// The Q_OBJECT meta info is used for qobject_cast!
158 	Q_OBJECT
159 
160 public:
161 	TWidget(QWidget *parent = nullptr) : TWidgetHelper<QWidget>(parent) {
162 	}
163 
164 	// Get the size of the widget as it should be.
165 	// Negative return value means no default width.
naturalWidth()166 	virtual int naturalWidth() const {
167 		return -1;
168 	}
169 
170 	// Count new height for width=newWidth and resize to it.
resizeToWidth(int newWidth)171 	void resizeToWidth(int newWidth) {
172 		auto margins = getMargins();
173 		auto fullWidth = margins.left() + newWidth + margins.right();
174 		auto fullHeight = margins.top() + resizeGetHeight(newWidth) + margins.bottom();
175 		auto newSize = QSize(fullWidth, fullHeight);
176 		if (newSize != size()) {
177 			resize(newSize);
178 			update();
179 		}
180 	}
181 
182 	// Resize to minimum of natural width and available width.
resizeToNaturalWidth(int newWidth)183 	void resizeToNaturalWidth(int newWidth) {
184 		auto maxWidth = naturalWidth();
185 		resizeToWidth((maxWidth >= 0) ? qMin(newWidth, maxWidth) : newWidth);
186 	}
187 
rectNoMargins()188 	QRect rectNoMargins() const {
189 		return rect().marginsRemoved(getMargins());
190 	}
191 
widthNoMargins()192 	int widthNoMargins() const {
193 		return rectNoMargins().width();
194 	}
195 
heightNoMargins()196 	int heightNoMargins() const {
197 		return rectNoMargins().height();
198 	}
199 
bottomNoMargins()200 	int bottomNoMargins() const {
201 		auto rectWithoutMargins = rectNoMargins();
202 		return y() + rectWithoutMargins.y() + rectWithoutMargins.height();
203 	}
204 
sizeNoMargins()205 	QSize sizeNoMargins() const {
206 		return rectNoMargins().size();
207 	}
208 
209 	// Updates the area that is visible inside the scroll container.
setVisibleTopBottom(int visibleTop,int visibleBottom)210 	void setVisibleTopBottom(int visibleTop, int visibleBottom) {
211 		auto max = height();
212 		visibleTopBottomUpdated(
213 			std::clamp(visibleTop, 0, max),
214 			std::clamp(visibleBottom, 0, max));
215 	}
216 
217 protected:
setChildVisibleTopBottom(TWidget * child,int visibleTop,int visibleBottom)218 	void setChildVisibleTopBottom(
219 			TWidget *child,
220 			int visibleTop,
221 			int visibleBottom) {
222 		if (child) {
223 			auto top = child->y();
224 			child->setVisibleTopBottom(
225 				visibleTop - top,
226 				visibleBottom - top);
227 		}
228 	}
229 
230 	// Resizes content and counts natural widget height for the desired width.
resizeGetHeight(int newWidth)231 	virtual int resizeGetHeight(int newWidth) {
232 		return heightNoMargins();
233 	}
234 
visibleTopBottomUpdated(int visibleTop,int visibleBottom)235 	virtual void visibleTopBottomUpdated(
236 		int visibleTop,
237 		int visibleBottom) {
238 	}
239 
240 };
241 
242 namespace Ui {
243 
244 class RpWidget;
245 
246 void ResizeFitChild(not_null<RpWidget*> parent, not_null<RpWidget*> child);
247 
248 template <typename Widget>
249 using RpWidgetParent = std::conditional_t<
250 	std::is_same_v<Widget, QWidget>,
251 	TWidget,
252 	TWidgetHelper<Widget>>;
253 
254 template <typename Widget, typename Traits>
255 class RpWidgetBase;
256 
257 class RpWidgetWrap {
258 public:
259 	virtual QWidget *rpWidget() = 0;
260 	virtual const QWidget *rpWidget() const = 0;
261 
262 	rpl::producer<not_null<QEvent*>> events() const;
263 	rpl::producer<QRect> geometryValue() const;
264 	rpl::producer<QSize> sizeValue() const;
265 	rpl::producer<int> heightValue() const;
266 	rpl::producer<int> widthValue() const;
267 	rpl::producer<QPoint> positionValue() const;
268 	rpl::producer<int> leftValue() const;
269 	rpl::producer<int> topValue() const;
270 	virtual rpl::producer<int> desiredHeightValue() const;
271 	rpl::producer<bool> shownValue() const;
272 	rpl::producer<QRect> paintRequest() const;
273 	rpl::producer<> alive() const;
274 	rpl::producer<> windowDeactivateEvents() const;
275 	rpl::producer<> macWindowDeactivateEvents() const;
276 
277 	template <typename Error, typename Generator>
showOn(rpl::producer<bool,Error,Generator> && shown)278 	void showOn(rpl::producer<bool, Error, Generator> &&shown) {
279 		std::move(
280 			shown
281 		) | rpl::start_with_next([this](bool visible) {
282 			callSetVisible(visible);
283 		}, lifetime());
284 	}
285 
286 	rpl::lifetime &lifetime();
287 
288 	virtual ~RpWidgetWrap() = default;
289 
290 protected:
291 	bool handleEvent(QEvent *event);
292 	virtual bool eventHook(QEvent *event) = 0;
293 
294 private:
295 	template <typename Widget, typename Traits>
296 	friend class RpWidgetBase;
297 
298 	struct EventStreams {
299 		rpl::event_stream<not_null<QEvent*>> events;
300 		rpl::event_stream<QRect> geometry;
301 		rpl::event_stream<QRect> paint;
302 		rpl::event_stream<bool> shown;
303 		rpl::event_stream<> alive;
304 	};
305 	struct Initer {
306 		Initer(QWidget *parent, bool setZeroGeometry);
307 	};
308 
309 	virtual void callSetVisible(bool visible) = 0;
310 
311 	void visibilityChangedHook(bool wasVisible, bool nowVisible);
312 	EventStreams &eventStreams() const;
313 
314 	mutable std::unique_ptr<EventStreams> _eventStreams;
315 	rpl::lifetime _lifetime;
316 
317 };
318 
319 struct RpWidgetDefaultTraits {
320 	static constexpr bool kSetZeroGeometry = true;
321 };
322 
323 template <typename Widget, typename Traits = RpWidgetDefaultTraits>
324 class RpWidgetBase
325 	: public RpWidgetParent<Widget>
326 	, public RpWidgetWrap {
327 	using Self = RpWidgetBase<Widget, Traits>;
328 	using Parent = RpWidgetParent<Widget>;
329 
330 public:
331 	using Parent::Parent;
332 
rpWidget()333 	QWidget *rpWidget() final override {
334 		return this;
335 	}
rpWidget()336 	const QWidget *rpWidget() const final override {
337 		return this;
338 	}
setVisible(bool visible)339 	void setVisible(bool visible) final override {
340 		auto wasVisible = !this->isHidden();
341 		setVisibleHook(visible);
342 		visibilityChangedHook(wasVisible, !this->isHidden());
343 	}
344 
~RpWidgetBase()345 	~RpWidgetBase() {
346 		base::take(_lifetime);
347 		base::take(_eventStreams);
348 	}
349 
350 protected:
event(QEvent * event)351 	bool event(QEvent *event) final override {
352 		return handleEvent(event);
353 	}
eventHook(QEvent * event)354 	bool eventHook(QEvent *event) override {
355 		return Parent::event(event);
356 	}
setVisibleHook(bool visible)357 	virtual void setVisibleHook(bool visible) {
358 		Parent::setVisible(visible);
359 	}
360 
361 private:
callSetVisible(bool visible)362 	void callSetVisible(bool visible) final override {
363 		Self::setVisible(visible); // Save one virtual method invocation.
364 	}
365 
366 	Initer _initer = { this, Traits::kSetZeroGeometry };
367 
368 };
369 
370 class RpWidget : public RpWidgetBase<QWidget> {
371 public:
372 	using RpWidgetBase<QWidget>::RpWidgetBase;
373 
374 };
375 
376 } // namespace Ui
377