on xenial help sign in
Implement window controls overlay activatable with 3 fingers tap (LP: #1489020)
commit55a3c1bc3a84c2b9da2f80edaec3c8ecc3133e84 authorLukáš Tinkl <lukas.tinkl@canonical.com> dateFri 17 Jun 2016, 01:14 (10 years ago) parent89c06a4a1abd6c
21 files changed+905−93
plugins/Ubuntu/Gestures/TouchGestureArea.cpp+7−9 plugins/Ubuntu/Gestures/TouchGestureArea.h+7−7 qml/Components/PanelState/PanelState.qml+1 qml/Components/WindowControlButtons.qml+21−27 qml/Components/graphics/window-close.svg+164−14 qml/Components/graphics/window-maximize.svg+164−12 qml/Components/graphics/window-minimize.svg+164−13 qml/Components/graphics/window-window.svg+41 qml/Panel/Panel.qml+5−2 qml/Stages/DecoratedWindow.qml+2 qml/Stages/DesktopStage.qml+20−2 qml/Stages/ResizeGrip.qml+46 qml/Stages/TabletSideStageTouchGesture.qml+3 qml/Stages/TabletStage.qml−1 qml/Stages/WindowControlsOverlay.qml+219 qml/Stages/WindowDecoration.qml+13−1 qml/Stages/WindowResizeArea.qml+4−4 qml/Stages/graphics/arrows-centre.png qml/Stages/graphics/arrows.png tests/qmltests/Stages/SurfaceManagerControls.qml+1−1 tests/qmltests/Stages/tst_DesktopStage.qml+23
plugins/Ubuntu/Gestures/TouchGestureArea.cpp+7−9
@@ -375,7 +375,7 @@ void TouchGestureArea::unownedTouchEvent_waitingForMoreTouches(QTouchEvent *even
375375 }
376376 }
377377
378− if (m_candidateTouches.count() == 0) {
378+ if (m_candidateTouches.isEmpty()) {
379379 setInternalStatus(InternalStatus::WaitingForTouch);
380380 }
381381}
@@ -439,7 +439,7 @@ void TouchGestureArea::unownedTouchEvent_rejected(QTouchEvent *event)
439439 }
440440 }
441441
442− if (m_watchedTouches.count() == 0) {
442+ if (m_watchedTouches.isEmpty()) {
443443 setInternalStatus(InternalStatus::WaitingForTouch);
444444 }
445445}
@@ -527,7 +527,7 @@ void TouchGestureArea::updateTouchPoints(QTouchEvent *touchEvent)
527527 if (updateable) {
528528 if (!dragging() && m_status == InternalStatus::Recognized) {
529529 bool allWantDrag = !m_liveTouchPoints.isEmpty();
530− Q_FOREACH(auto point, m_liveTouchPoints) {
530+ Q_FOREACH(const auto &point, m_liveTouchPoints) {
531531 allWantDrag &= point->dragging();
532532 }
533533 // only dragging if all points are dragging.
@@ -646,11 +646,9 @@ bool TouchGestureArea::dragging() const
646646QQmlListProperty<GestureTouchPoint> TouchGestureArea::touchPoints()
647647{
648648 return QQmlListProperty<GestureTouchPoint>(this,
649− 0,
650− nullptr,
651− TouchGestureArea::touchPoint_count,
652− TouchGestureArea::touchPoint_at,
653− 0);
649+ nullptr,
650+ TouchGestureArea::touchPoint_count,
651+ TouchGestureArea::touchPoint_at);
654652}
655653
656654int TouchGestureArea::minimumTouchPoints() const
@@ -721,7 +719,7 @@ void TouchGestureArea::rejectGesture()
721719 }
722720 m_candidateTouches.clear();
723721
724− if (m_watchedTouches.count() == 0) {
722+ if (m_watchedTouches.isEmpty()) {
725723 setInternalStatus(InternalStatus::WaitingForTouch);
726724 } else {
727725 setInternalStatus(InternalStatus::Rejected);
plugins/Ubuntu/Gestures/TouchGestureArea.h+7−7
@@ -105,8 +105,8 @@ private:
105105/*
106106 An area that detects multi-finger gestures.
107107
108− We can use this to detect gestures contstrained by a minimim and/or maximum number of touch points.
109− This components uses the touch registry to apply for ownership of touch points.
108+ We can use this to detect gestures constrained by a minimum and/or maximum number of touch points.
109+ This component uses the touch registry to apply for ownership of touch points.
110110 This way we can use the component in conjuntion with the directional drag area to compete for ownwership
111111 or gestures; unlike the MultiPointTouchArea.
112112 */
@@ -136,7 +136,7 @@ public:
136136 Recognized,
137137 Rejected
138138 };
139− TouchGestureArea(QQuickItem* parent = NULL);
139+ TouchGestureArea(QQuickItem* parent = nullptr);
140140 ~TouchGestureArea();
141141
142142 bool event(QEvent *e) override;
@@ -164,10 +164,10 @@ Q_SIGNALS:
164164
165165 void touchPointsUpdated();
166166 void draggingChanged(bool dragging);
167− void minimumTouchPointsChanged(bool value);
168− void maximumTouchPointsChanged(bool value);
169− void recognitionPeriodChanged(bool value);
170− void releaseRejectPeriodChanged(bool value);
167+ void minimumTouchPointsChanged(int value);
168+ void maximumTouchPointsChanged(int value);
169+ void recognitionPeriodChanged(int value);
170+ void releaseRejectPeriodChanged(int value);
171171
172172 void pressed(const QList<QObject*>& points);
173173 void released(const QList<QObject*>& points);
qml/Components/PanelState/PanelState.qml+1
@@ -22,6 +22,7 @@ QtObject {
2222
2323 property string title: ""
2424 property bool buttonsVisible: false
25+ property bool buttonsAlwaysVisible: false
2526 property bool closeButtonShown: true
2627 property bool dropShadow: false
2728 property int panelHeight: 0
qml/Components/WindowControlButtons.qml+21−27
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2014-2015 Canonical, Ltd.
2+ * Copyright (C) 2014-2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -19,11 +19,17 @@ import Ubuntu.Components 1.3
1919
2020Row {
2121 id: root
22− spacing: units.gu(1)
22+ spacing: overlayShown ? units.gu(2) : units.gu(1)
23+ Behavior on spacing {
24+ UbuntuNumberAnimation {}
25+ }
2326
2427 // to be set from outside
28+ property Item target
2529 property bool active: false
30+ property bool windowIsMaximized: false
2631 property bool closeButtonShown: true
32+ property bool overlayShown
2733
2834 signal closeClicked()
2935 signal minimizeClicked()
@@ -41,20 +47,16 @@ Row {
4147 visible: root.closeButtonShown
4248
4349 Rectangle {
44− anchors.centerIn: parent
45− width: units.gu(2)
46− height: units.gu(2)
50+ anchors.fill: parent
4751 radius: height / 2
4852 color: theme.palette.normal.negative
49− visible: parent.containsMouse
53+ visible: parent.containsMouse && !overlayShown
5054 }
5155 Icon {
52− width: height
53− height: parent.height *.5
54− anchors.centerIn: parent
56+ anchors.fill: parent
57+ anchors.margins: units.dp(3)
5558 source: "graphics/window-close.svg"
5659 color: root.active ? "white" : UbuntuColors.slate
57− keyColor: "black"
5860 }
5961 }
6062
@@ -67,20 +69,16 @@ Row {
6769 onClicked: root.minimizeClicked()
6870
6971 Rectangle {
70− anchors.centerIn: parent
71− width: units.gu(2)
72− height: units.gu(2)
72+ anchors.fill: parent
7373 radius: height / 2
7474 color: root.active ? UbuntuColors.graphite : UbuntuColors.ash
75− visible: parent.containsMouse
75+ visible: parent.containsMouse && !overlayShown
7676 }
7777 Icon {
78− width: height
79− height: parent.height *.5
80− anchors.centerIn: parent
78+ anchors.fill: parent
79+ anchors.margins: units.dp(3)
8180 source: "graphics/window-minimize.svg"
8281 color: root.active ? "white" : UbuntuColors.slate
83− keyColor: "black"
8482 }
8583 }
8684
@@ -102,20 +100,16 @@ Row {
102100 }
103101
104102 Rectangle {
105− anchors.centerIn: parent
106− width: units.gu(2)
107− height: units.gu(2)
103+ anchors.fill: parent
108104 radius: height / 2
109105 color: root.active ? UbuntuColors.graphite : UbuntuColors.ash
110− visible: parent.containsMouse
106+ visible: parent.containsMouse && !overlayShown
111107 }
112108 Icon {
113− width: height
114− height: parent.height *.5
115− anchors.centerIn: parent
116− source: "graphics/window-maximize.svg"
109+ anchors.fill: parent
110+ anchors.margins: units.dp(3)
111+ source: root.windowIsMaximized ? "graphics/window-window.svg" : "graphics/window-maximize.svg"
117112 color: root.active ? "white" : UbuntuColors.slate
118− keyColor: "black"
119113 }
120114 }
121115}
qml/Components/graphics/window-close.svg+164−14
@@ -1,14 +1,164 @@
1−<?xml version="1.0" encoding="iso-8859-1"?>
2−<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
3−<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
4−<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
5− width="94.926px" height="94.926px" viewBox="0 0 94.926 94.926" style="enable-background:new 0 0 94.926 94.926;"
6− xml:space="preserve">
7−<g>
8− <path d="M55.931,47.463L94.306,9.09c0.826-0.827,0.826-2.167,0-2.994L88.833,0.62C88.436,0.224,87.896,0,87.335,0
9− c-0.562,0-1.101,0.224-1.498,0.62L47.463,38.994L9.089,0.62c-0.795-0.795-2.202-0.794-2.995,0L0.622,6.096
10− c-0.827,0.827-0.827,2.167,0,2.994l38.374,38.373L0.622,85.836c-0.827,0.827-0.827,2.167,0,2.994l5.473,5.476
11− c0.397,0.396,0.936,0.62,1.498,0.62s1.1-0.224,1.497-0.62l38.374-38.374l38.374,38.374c0.397,0.396,0.937,0.62,1.498,0.62
12− s1.101-0.224,1.498-0.62l5.473-5.476c0.826-0.827,0.826-2.167,0-2.994L55.931,47.463z"/>
13−</g>
14−</svg>
1+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2+<!-- Created with Inkscape (http://www.inkscape.org/) -->
3+
4+<svg
5+ xmlns:dc="http://purl.org/dc/elements/1.1/"
6+ xmlns:cc="http://creativecommons.org/ns#"
7+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
8+ xmlns:svg="http://www.w3.org/2000/svg"
9+ xmlns="http://www.w3.org/2000/svg"
10+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
11+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
12+ width="96"
13+ height="96"
14+ id="svg4874"
15+ version="1.1"
16+ inkscape:version="0.91+devel r"
17+ viewBox="0 0 96 96.000001"
18+ sodipodi:docname="window-close.svg">
19+ <defs
20+ id="defs4876" />
21+ <sodipodi:namedview
22+ id="base"
23+ pagecolor="#ffffff"
24+ bordercolor="#666666"
25+ borderopacity="1.0"
26+ inkscape:pageopacity="0.0"
27+ inkscape:pageshadow="2"
28+ inkscape:zoom="6.9749997"
29+ inkscape:cx="17.584231"
30+ inkscape:cy="74.637978"
31+ inkscape:document-units="px"
32+ inkscape:current-layer="g4780"
33+ showgrid="true"
34+ showborder="true"
35+ fit-margin-top="0"
36+ fit-margin-left="0"
37+ fit-margin-right="0"
38+ fit-margin-bottom="0"
39+ inkscape:snap-bbox="true"
40+ inkscape:bbox-paths="true"
41+ inkscape:bbox-nodes="true"
42+ inkscape:snap-bbox-edge-midpoints="true"
43+ inkscape:snap-bbox-midpoints="true"
44+ inkscape:object-paths="true"
45+ inkscape:snap-intersection-paths="true"
46+ inkscape:object-nodes="true"
47+ inkscape:snap-smooth-nodes="true"
48+ inkscape:snap-midpoints="true"
49+ inkscape:snap-object-midpoints="true"
50+ inkscape:snap-center="true"
51+ showguides="true"
52+ inkscape:guide-bbox="true"
53+ inkscape:snap-global="true">
54+ <inkscape:grid
55+ type="xygrid"
56+ id="grid5451"
57+ empspacing="8" />
58+ <sodipodi:guide
59+ orientation="1,0"
60+ position="8,-8.0000001"
61+ id="guide4063" />
62+ <sodipodi:guide
63+ orientation="1,0"
64+ position="4,-8.0000001"
65+ id="guide4065" />
66+ <sodipodi:guide
67+ orientation="0,1"
68+ position="-8,88.000001"
69+ id="guide4067" />
70+ <sodipodi:guide
71+ orientation="0,1"
72+ position="-8,92.000001"
73+ id="guide4069" />
74+ <sodipodi:guide
75+ orientation="0,1"
76+ position="104,4"
77+ id="guide4071" />
78+ <sodipodi:guide
79+ orientation="0,1"
80+ position="-5,8.0000001"
81+ id="guide4073" />
82+ <sodipodi:guide
83+ orientation="1,0"
84+ position="92,-8.0000001"
85+ id="guide4075" />
86+ <sodipodi:guide
87+ orientation="1,0"
88+ position="88,-8.0000001"
89+ id="guide4077" />
90+ <sodipodi:guide
91+ orientation="0,1"
92+ position="-8,84.000001"
93+ id="guide4074" />
94+ <sodipodi:guide
95+ orientation="1,0"
96+ position="12,-8.0000001"
97+ id="guide4076" />
98+ <sodipodi:guide
99+ orientation="0,1"
100+ position="-5,12"
101+ id="guide4078" />
102+ <sodipodi:guide
103+ orientation="1,0"
104+ position="84,-9.0000001"
105+ id="guide4080" />
106+ <sodipodi:guide
107+ position="48,-8.0000001"
108+ orientation="1,0"
109+ id="guide4170" />
110+ <sodipodi:guide
111+ position="-8,48"
112+ orientation="0,1"
113+ id="guide4172" />
114+ </sodipodi:namedview>
115+ <metadata
116+ id="metadata4879">
117+ <rdf:RDF>
118+ <cc:Work
119+ rdf:about="">
120+ <dc:format>image/svg+xml</dc:format>
121+ <dc:type
122+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
123+ <dc:title />
124+ </cc:Work>
125+ </rdf:RDF>
126+ </metadata>
127+ <g
128+ inkscape:label="Layer 1"
129+ inkscape:groupmode="layer"
130+ id="layer1"
131+ transform="translate(67.857146,-78.50504)">
132+ <g
133+ transform="matrix(0,-1,-1,0,373.50506,516.50504)"
134+ id="g4845"
135+ style="display:inline">
136+ <g
137+ inkscape:export-ydpi="90"
138+ inkscape:export-xdpi="90"
139+ inkscape:export-filename="next01.png"
140+ transform="matrix(-0.9996045,0,0,1,575.94296,-611.00001)"
141+ id="g4778"
142+ inkscape:label="Layer 1">
143+ <g
144+ transform="matrix(-1,0,0,1,575.99999,611)"
145+ id="g4780"
146+ style="display:inline">
147+ <rect
148+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:none;stroke-width:4;marker:none;enable-background:accumulate"
149+ id="rect4782"
150+ width="96.037987"
151+ height="96"
152+ x="-438.00244"
153+ y="345.36221"
154+ transform="scale(-1,1)" />
155+ <path
156+ style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:none;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3.99999976;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
157+ d="M 14.828125 12.001953 L 12 14.828125 L 45.171875 48 L 12 81.171875 L 14.828125 83.998047 L 48 50.828125 L 81.169922 83.998047 L 84 81.171875 L 50.828125 48 L 84 14.828125 L 81.169922 12.001953 L 48 45.171875 L 14.828125 12.001953 z "
158+ transform="matrix(0,-1,-1.0003957,0,438.00245,441.36222)"
159+ id="path4159" />
160+ </g>
161+ </g>
162+ </g>
163+ </g>
164+</svg>
qml/Components/graphics/window-maximize.svg+164−12
@@ -1,12 +1,164 @@
1−<?xml version="1.0" encoding="iso-8859-1"?>
2−<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
3−<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
4−<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
5− width="459px" height="459px" viewBox="0 0 459 459" style="enable-background:new 0 0 459 459;" xml:space="preserve">
6−<g>
7− <g id="check-box-outline-blank">
8− <path d="M408,51v357H51V51H408 M408,0H51C22.95,0,0,22.95,0,51v357c0,28.05,22.95,51,51,51h357c28.05,0,51-22.95,51-51V51
9− C459,22.95,436.05,0,408,0L408,0z"/>
10− </g>
11−</g>
12−</svg>
1+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2+<!-- Created with Inkscape (http://www.inkscape.org/) -->
3+
4+<svg
5+ xmlns:dc="http://purl.org/dc/elements/1.1/"
6+ xmlns:cc="http://creativecommons.org/ns#"
7+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
8+ xmlns:svg="http://www.w3.org/2000/svg"
9+ xmlns="http://www.w3.org/2000/svg"
10+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
11+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
12+ width="96"
13+ height="96"
14+ id="svg4874"
15+ version="1.1"
16+ inkscape:version="0.91+devel r"
17+ viewBox="0 0 96 96.000001"
18+ sodipodi:docname="window-maximize.svg">
19+ <defs
20+ id="defs4876" />
21+ <sodipodi:namedview
22+ id="base"
23+ pagecolor="#ffffff"
24+ bordercolor="#666666"
25+ borderopacity="1.0"
26+ inkscape:pageopacity="0.0"
27+ inkscape:pageshadow="2"
28+ inkscape:zoom="13.623046"
29+ inkscape:cx="24.473234"
30+ inkscape:cy="67.851922"
31+ inkscape:document-units="px"
32+ inkscape:current-layer="g4780"
33+ showgrid="true"
34+ showborder="true"
35+ fit-margin-top="0"
36+ fit-margin-left="0"
37+ fit-margin-right="0"
38+ fit-margin-bottom="0"
39+ inkscape:snap-bbox="true"
40+ inkscape:bbox-paths="true"
41+ inkscape:bbox-nodes="true"
42+ inkscape:snap-bbox-edge-midpoints="true"
43+ inkscape:snap-bbox-midpoints="true"
44+ inkscape:object-paths="true"
45+ inkscape:snap-intersection-paths="true"
46+ inkscape:object-nodes="true"
47+ inkscape:snap-smooth-nodes="true"
48+ inkscape:snap-midpoints="true"
49+ inkscape:snap-object-midpoints="true"
50+ inkscape:snap-center="true"
51+ showguides="true"
52+ inkscape:guide-bbox="true"
53+ inkscape:snap-global="true">
54+ <inkscape:grid
55+ type="xygrid"
56+ id="grid5451"
57+ empspacing="8" />
58+ <sodipodi:guide
59+ orientation="1,0"
60+ position="8,-8.0000001"
61+ id="guide4063" />
62+ <sodipodi:guide
63+ orientation="1,0"
64+ position="4,-8.0000001"
65+ id="guide4065" />
66+ <sodipodi:guide
67+ orientation="0,1"
68+ position="-8,88.000001"
69+ id="guide4067" />
70+ <sodipodi:guide
71+ orientation="0,1"
72+ position="-8,92.000001"
73+ id="guide4069" />
74+ <sodipodi:guide
75+ orientation="0,1"
76+ position="104,4"
77+ id="guide4071" />
78+ <sodipodi:guide
79+ orientation="0,1"
80+ position="-5,8.0000001"
81+ id="guide4073" />
82+ <sodipodi:guide
83+ orientation="1,0"
84+ position="92,-8.0000001"
85+ id="guide4075" />
86+ <sodipodi:guide
87+ orientation="1,0"
88+ position="88,-8.0000001"
89+ id="guide4077" />
90+ <sodipodi:guide
91+ orientation="0,1"
92+ position="-8,84.000001"
93+ id="guide4074" />
94+ <sodipodi:guide
95+ orientation="1,0"
96+ position="12,-8.0000001"
97+ id="guide4076" />
98+ <sodipodi:guide
99+ orientation="0,1"
100+ position="-5,12"
101+ id="guide4078" />
102+ <sodipodi:guide
103+ orientation="1,0"
104+ position="84,-9.0000001"
105+ id="guide4080" />
106+ <sodipodi:guide
107+ position="48,-8.0000001"
108+ orientation="1,0"
109+ id="guide4170" />
110+ <sodipodi:guide
111+ position="-8,48"
112+ orientation="0,1"
113+ id="guide4172" />
114+ </sodipodi:namedview>
115+ <metadata
116+ id="metadata4879">
117+ <rdf:RDF>
118+ <cc:Work
119+ rdf:about="">
120+ <dc:format>image/svg+xml</dc:format>
121+ <dc:type
122+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
123+ <dc:title />
124+ </cc:Work>
125+ </rdf:RDF>
126+ </metadata>
127+ <g
128+ inkscape:label="Layer 1"
129+ inkscape:groupmode="layer"
130+ id="layer1"
131+ transform="translate(67.857146,-78.50504)">
132+ <g
133+ transform="matrix(0,-1,-1,0,373.50506,516.50504)"
134+ id="g4845"
135+ style="display:inline">
136+ <g
137+ inkscape:export-ydpi="90"
138+ inkscape:export-xdpi="90"
139+ inkscape:export-filename="next01.png"
140+ transform="matrix(-0.9996045,0,0,1,575.94296,-611.00001)"
141+ id="g4778"
142+ inkscape:label="Layer 1">
143+ <g
144+ transform="matrix(-1,0,0,1,575.99999,611)"
145+ id="g4780"
146+ style="display:inline">
147+ <rect
148+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:none;stroke-width:4;marker:none;enable-background:accumulate"
149+ id="rect4782"
150+ width="96.037987"
151+ height="96"
152+ x="-438.00244"
153+ y="345.36221"
154+ transform="scale(-1,1)" />
155+ <path
156+ style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.23618174;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
157+ d="m 425.99805,429.36328 h -2.11914 -69.91016 v -72.00195 h 72.0293 z m -4.23633,-4.23633 v -63.52929 h -63.55664 v 63.52929 z"
158+ id="rect4198"
159+ inkscape:connector-curvature="0" />
160+ </g>
161+ </g>
162+ </g>
163+ </g>
164+</svg>
qml/Components/graphics/window-minimize.svg+164−13
@@ -1,13 +1,164 @@
1−<?xml version="1.0" encoding="iso-8859-1"?>
2−<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
3−<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
4−<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
5− width="121.805px" height="121.804px" viewBox="0 0 121.805 121.804" style="enable-background:new 0 0 121.805 121.804;"
6− xml:space="preserve">
7−<g>
8− <g>
9− <path d="M7.308,68.211h107.188c4.037,0,7.309-3.272,7.309-7.31c0-4.037-3.271-7.309-7.309-7.309H7.308
10− C3.272,53.593,0,56.865,0,60.902C0,64.939,3.272,68.211,7.308,68.211z"/>
11− </g>
12−</g>
13−</svg>
1+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2+<!-- Created with Inkscape (http://www.inkscape.org/) -->
3+
4+<svg
5+ xmlns:dc="http://purl.org/dc/elements/1.1/"
6+ xmlns:cc="http://creativecommons.org/ns#"
7+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
8+ xmlns:svg="http://www.w3.org/2000/svg"
9+ xmlns="http://www.w3.org/2000/svg"
10+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
11+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
12+ width="96"
13+ height="96"
14+ id="svg4874"
15+ version="1.1"
16+ inkscape:version="0.91+devel r"
17+ viewBox="0 0 96 96.000001"
18+ sodipodi:docname="window-minimize.svg">
19+ <defs
20+ id="defs4876" />
21+ <sodipodi:namedview
22+ id="base"
23+ pagecolor="#ffffff"
24+ bordercolor="#666666"
25+ borderopacity="1.0"
26+ inkscape:pageopacity="0.0"
27+ inkscape:pageshadow="2"
28+ inkscape:zoom="13.623046"
29+ inkscape:cx="24.473234"
30+ inkscape:cy="61.97952"
31+ inkscape:document-units="px"
32+ inkscape:current-layer="g4780"
33+ showgrid="true"
34+ showborder="true"
35+ fit-margin-top="0"
36+ fit-margin-left="0"
37+ fit-margin-right="0"
38+ fit-margin-bottom="0"
39+ inkscape:snap-bbox="true"
40+ inkscape:bbox-paths="true"
41+ inkscape:bbox-nodes="true"
42+ inkscape:snap-bbox-edge-midpoints="true"
43+ inkscape:snap-bbox-midpoints="true"
44+ inkscape:object-paths="true"
45+ inkscape:snap-intersection-paths="true"
46+ inkscape:object-nodes="true"
47+ inkscape:snap-smooth-nodes="true"
48+ inkscape:snap-midpoints="true"
49+ inkscape:snap-object-midpoints="true"
50+ inkscape:snap-center="true"
51+ showguides="true"
52+ inkscape:guide-bbox="true"
53+ inkscape:snap-global="true">
54+ <inkscape:grid
55+ type="xygrid"
56+ id="grid5451"
57+ empspacing="8" />
58+ <sodipodi:guide
59+ orientation="1,0"
60+ position="8,-8.0000001"
61+ id="guide4063" />
62+ <sodipodi:guide
63+ orientation="1,0"
64+ position="4,-8.0000001"
65+ id="guide4065" />
66+ <sodipodi:guide
67+ orientation="0,1"
68+ position="-8,88.000001"
69+ id="guide4067" />
70+ <sodipodi:guide
71+ orientation="0,1"
72+ position="-8,92.000001"
73+ id="guide4069" />
74+ <sodipodi:guide
75+ orientation="0,1"
76+ position="104,4"
77+ id="guide4071" />
78+ <sodipodi:guide
79+ orientation="0,1"
80+ position="-5,8.0000001"
81+ id="guide4073" />
82+ <sodipodi:guide
83+ orientation="1,0"
84+ position="92,-8.0000001"
85+ id="guide4075" />
86+ <sodipodi:guide
87+ orientation="1,0"
88+ position="88,-8.0000001"
89+ id="guide4077" />
90+ <sodipodi:guide
91+ orientation="0,1"
92+ position="-8,84.000001"
93+ id="guide4074" />
94+ <sodipodi:guide
95+ orientation="1,0"
96+ position="12,-8.0000001"
97+ id="guide4076" />
98+ <sodipodi:guide
99+ orientation="0,1"
100+ position="-5,12"
101+ id="guide4078" />
102+ <sodipodi:guide
103+ orientation="1,0"
104+ position="84,-9.0000001"
105+ id="guide4080" />
106+ <sodipodi:guide
107+ position="48,-8.0000001"
108+ orientation="1,0"
109+ id="guide4170" />
110+ <sodipodi:guide
111+ position="-8,48"
112+ orientation="0,1"
113+ id="guide4172" />
114+ </sodipodi:namedview>
115+ <metadata
116+ id="metadata4879">
117+ <rdf:RDF>
118+ <cc:Work
119+ rdf:about="">
120+ <dc:format>image/svg+xml</dc:format>
121+ <dc:type
122+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
123+ <dc:title />
124+ </cc:Work>
125+ </rdf:RDF>
126+ </metadata>
127+ <g
128+ inkscape:label="Layer 1"
129+ inkscape:groupmode="layer"
130+ id="layer1"
131+ transform="translate(67.857146,-78.50504)">
132+ <g
133+ transform="matrix(0,-1,-1,0,373.50506,516.50504)"
134+ id="g4845"
135+ style="display:inline">
136+ <g
137+ inkscape:export-ydpi="90"
138+ inkscape:export-xdpi="90"
139+ inkscape:export-filename="next01.png"
140+ transform="matrix(-0.9996045,0,0,1,575.94296,-611.00001)"
141+ id="g4778"
142+ inkscape:label="Layer 1">
143+ <g
144+ transform="matrix(-1,0,0,1,575.99999,611)"
145+ id="g4780"
146+ style="display:inline">
147+ <rect
148+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:none;stroke-width:4;marker:none;enable-background:accumulate"
149+ id="rect4782"
150+ width="96.037987"
151+ height="96"
152+ x="-438.00244"
153+ y="345.36221"
154+ transform="scale(-1,1)" />
155+ <path
156+ style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.00079155;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
157+ d="m 387.98242,357.36133 v 72 h 4.00196 v -72 z"
158+ id="path4219"
159+ inkscape:connector-curvature="0" />
160+ </g>
161+ </g>
162+ </g>
163+ </g>
164+</svg>
qml/Components/graphics/window-window.svgnew file+41
@@ -0,0 +1,41 @@
1+<?xml version="1.0" encoding="utf-8"?>
2+<!-- Generator: Adobe Illustrator 19.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
3+<svg version="1.1"
4+ id="svg4874" inkscape:version="0.91+devel r" sodipodi:docname="window-maximize.svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg"
5+ xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 96 96"
6+ style="enable-background:new 0 0 96 96;" xml:space="preserve">
7+<style type="text/css">
8+ .st0{fill:none;}
9+ .st1{fill:#808080;}
10+</style>
11+<sodipodi:namedview bordercolor="#666666" borderopacity="1.0" fit-margin-bottom="0" fit-margin-left="0" fit-margin-right="0" fit-margin-top="0" id="base" inkscape:bbox-nodes="true" inkscape:bbox-paths="true" inkscape:current-layer="g4780" inkscape:cx="24.473234" inkscape:cy="67.851922" inkscape:document-units="px" inkscape:guide-bbox="true" inkscape:object-nodes="true" inkscape:object-paths="true" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:snap-bbox="true" inkscape:snap-bbox-edge-midpoints="true" inkscape:snap-bbox-midpoints="true" inkscape:snap-center="true" inkscape:snap-global="true" inkscape:snap-intersection-paths="true" inkscape:snap-midpoints="true" inkscape:snap-object-midpoints="true" inkscape:snap-smooth-nodes="true" inkscape:zoom="13.623046" pagecolor="#ffffff" showborder="true" showgrid="true" showguides="true">
12+ <inkscape:grid empspacing="8" id="grid5451" type="xygrid"></inkscape:grid>
13+ <sodipodi:guide id="guide4063" orientation="1,0" position="8,-8.0000001"></sodipodi:guide>
14+ <sodipodi:guide id="guide4065" orientation="1,0" position="4,-8.0000001"></sodipodi:guide>
15+ <sodipodi:guide id="guide4067" orientation="0,1" position="-8,88.000001"></sodipodi:guide>
16+ <sodipodi:guide id="guide4069" orientation="0,1" position="-8,92.000001"></sodipodi:guide>
17+ <sodipodi:guide id="guide4071" orientation="0,1" position="104,4"></sodipodi:guide>
18+ <sodipodi:guide id="guide4073" orientation="0,1" position="-5,8.0000001"></sodipodi:guide>
19+ <sodipodi:guide id="guide4075" orientation="1,0" position="92,-8.0000001"></sodipodi:guide>
20+ <sodipodi:guide id="guide4077" orientation="1,0" position="88,-8.0000001"></sodipodi:guide>
21+ <sodipodi:guide id="guide4074" orientation="0,1" position="-8,84.000001"></sodipodi:guide>
22+ <sodipodi:guide id="guide4076" orientation="1,0" position="12,-8.0000001"></sodipodi:guide>
23+ <sodipodi:guide id="guide4078" orientation="0,1" position="-5,12"></sodipodi:guide>
24+ <sodipodi:guide id="guide4080" orientation="1,0" position="84,-9.0000001"></sodipodi:guide>
25+ <sodipodi:guide id="guide4170" orientation="1,0" position="48,-8.0000001"></sodipodi:guide>
26+ <sodipodi:guide id="guide4172" orientation="0,1" position="-8,48"></sodipodi:guide>
27+</sodipodi:namedview>
28+<g id="layer1" transform="translate(67.857146,-78.50504)" inkscape:groupmode="layer" inkscape:label="Layer 1">
29+ <g id="g4845" transform="matrix(0,-1,-1,0,373.50506,516.50504)">
30+
31+ <g id="g4778" transform="matrix(-0.9996045,0,0,1,575.94296,-611.00001)" inkscape:export-filename="next01.png" inkscape:export-xdpi="90" inkscape:export-ydpi="90" inkscape:label="Layer 1">
32+ <g id="g4780" transform="matrix(-1,0,0,1,575.99999,611)">
33+
34+ <rect id="rect4782" x="340.5" y="424.9" transform="matrix(-2.535182e-06 -1 1 -2.535182e-06 -4.8782 781.8463)" class="st0" width="96" height="-63"/>
35+ <path id="rect4198" inkscape:connector-curvature="0" class="st1" d="M408,429.4h-2.1H369v-72h39V429.4z M403.8,425.1v-63.5
36+ h-30.5v63.5H403.8z"/>
37+ </g>
38+ </g>
39+ </g>
40+</g>
41+</svg>
qml/Panel/Panel.qml+5−2
@@ -125,8 +125,11 @@ Item {
125125 bottomMargin: units.gu(0.5)
126126 }
127127 height: indicators.minimizedPanelHeight - anchors.topMargin - anchors.bottomMargin
128− visible: PanelState.buttonsVisible && parent.containsMouse && !root.locked && !callHint.visible
129− active: PanelState.buttonsVisible
128+
129+ visible: ((PanelState.buttonsVisible && parent.containsMouse) || PanelState.buttonsAlwaysVisible)
130+ && !root.locked && !callHint.visible
131+ active: PanelState.buttonsVisible || PanelState.buttonsAlwaysVisible
132+ windowIsMaximized: true
130133 onCloseClicked: PanelState.closeClicked()
131134 onMinimizeClicked: PanelState.minimizeClicked()
132135 onMaximizeClicked: PanelState.restoreClicked()
qml/Stages/DecoratedWindow.qml+2
@@ -49,6 +49,8 @@ FocusScope {
4949 readonly property int widthIncrement: !counterRotate ? applicationWindow.widthIncrement : applicationWindow.heightIncrement
5050 readonly property int heightIncrement: !counterRotate ? applicationWindow.heightIncrement : applicationWindow.widthIncrement
5151
52+ property alias overlayShown: decoration.overlayShown
53+
5254 signal closeClicked()
5355 signal maximizeClicked()
5456 signal maximizeHorizontallyClicked()
qml/Stages/DesktopStage.qml+20−2
@@ -316,6 +316,7 @@ AbstractStage {
316316 property bool visuallyMinimized: false
317317
318318 readonly property var surface: model.surface
319+ readonly property alias resizeArea: resizeArea
319320
320321 function claimFocus() {
321322 if (spread.state == "altTab") {
@@ -345,13 +346,13 @@ AbstractStage {
345346 return;
346347
347348 if (focus) {
348− priv.focusedAppDelegate = appDelegate;
349−
350349 // If we're orphan (!parent) it means this stage is no longer the current one
351350 // and will be deleted shortly. So we should no longer have a say over the model
352351 if (root.parent) {
353352 topLevelSurfaceList.raiseId(model.id);
354353 }
354+
355+ priv.focusedAppDelegate = appDelegate;
355356 } else if (!focus && priv.focusedAppDelegate === appDelegate) {
356357 priv.focusedAppDelegate = null;
357358 // FIXME: No idea why the Binding{} doens't update when focusedAppDelegate turns null
@@ -600,9 +601,19 @@ AbstractStage {
600601 when: index == spread.highlightedIndex && spread.ready
601602 }
602603
604+ Binding {
605+ target: PanelState
606+ property: "buttonsAlwaysVisible"
607+ value: appDelegate && appDelegate.maximized && touchControls.overlayShown
608+ }
609+
603610 WindowResizeArea {
604611 id: resizeArea
605612 objectName: "windowResizeArea"
613+
614+ // workaround so that it chooses the correct resize borders when you drag from a corner ResizeGrip
615+ anchors.margins: touchControls.overlayShown ? borderThickness/2 : -borderThickness
616+
606617 target: appDelegate
607618 minWidth: units.gu(10)
608619 minHeight: units.gu(10)
@@ -643,6 +654,7 @@ AbstractStage {
643654 surface: model.surface
644655 active: appDelegate.focus
645656 focus: true
657+ overlayShown: touchControls.overlayShown
646658
647659 requestedWidth: appDelegate.requestedWidth
648660 requestedHeight: appDelegate.requestedHeight
@@ -657,6 +669,12 @@ AbstractStage {
657669 onDecorationPressed: { appDelegate.focus = true; }
658670 }
659671
672+ WindowControlsOverlay {
673+ id: touchControls
674+ anchors.fill: appDelegate
675+ target: appDelegate
676+ }
677+
660678 WindowedFullscreenPolicy {
661679 id: fullscreenPolicy
662680 active: true
qml/Stages/ResizeGrip.qmlnew file+46
@@ -0,0 +1,46 @@
1+/*
2+ * Copyright (C) 2016 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import Ubuntu.Components 1.3
19+
20+Rectangle {
21+ width: units.gu(4)
22+ height: width
23+ radius: height / 2
24+ color: theme.palette.normal.background
25+ opacity: 0.95
26+
27+ // to be set from outside
28+ property Item resizeTarget
29+
30+ Image {
31+ source: "graphics/arrows.png"
32+ anchors.centerIn: parent
33+ width: units.gu(2)
34+ height: width
35+ fillMode: Image.PreserveAspectFit
36+ }
37+
38+ MouseArea {
39+ anchors.fill: parent
40+ anchors.margins: -units.gu(1.5)
41+ hoverEnabled: true
42+
43+ Mouse.enabled: resizeTarget
44+ Mouse.forwardTo: resizeTarget
45+ }
46+}
qml/Stages/TabletSideStageTouchGesture.qml+3
@@ -106,6 +106,9 @@ TouchGestureArea {
106106 }
107107
108108 onDragStarted: {
109+ if (!dragComponent)
110+ return;
111+
109112 if (dragComponentProperties) {
110113 priv.dragObject = dragComponent.createObject(root, dragComponentProperties);
111114 } else {
qml/Stages/TabletStage.qml−1
@@ -1035,7 +1035,6 @@ AbstractStage {
10351035 id: triGestureArea
10361036 anchors.fill: parent
10371037 enabled: priv.sideStageEnabled && !spreadView.active
1038− property var dragObject: null
10391038
10401039 property Item spreadDelegate
10411040
qml/Stages/WindowControlsOverlay.qmlnew file+219
@@ -0,0 +1,219 @@
1+/*
2+ * Copyright (C) 2016 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import Ubuntu.Components 1.3
19+import Ubuntu.Gestures 0.1
20+import Unity.Application 0.1
21+import "../Components/PanelState"
22+
23+Item {
24+ id: root
25+ enabled: target && !target.fullscreen
26+
27+ // to be set from outside
28+ property Item target // appDelegate
29+
30+ // to be read from outside
31+ readonly property alias overlayShown: overlay.visible
32+
33+ TouchGestureArea {
34+ id: gestureArea
35+ anchors.fill: parent
36+
37+ // NB: for testing set to 2, not to clash with unity7 touch overlay controls
38+ minimumTouchPoints: 3
39+ maximumTouchPoints: minimumTouchPoints
40+
41+ readonly property bool recognizedPress: status == TouchGestureArea.Recognized &&
42+ touchPoints.length >= minimumTouchPoints &&
43+ touchPoints.length <= maximumTouchPoints
44+ onRecognizedPressChanged: {
45+ if (recognizedPress) {
46+ target.focus = true;
47+ overlayTimer.start();
48+ }
49+ }
50+
51+ readonly property bool recognizedDrag: recognizedPress && dragging
52+ onRecognizedDragChanged: {
53+ if (recognizedDrag) {
54+ priv.handlePressedChanged(true, tp.x, tp.y);
55+ } else if (!moveHandler.containsPress) { // prevent interfering with the central piece drag/move
56+ priv.dragging = false;
57+ }
58+ }
59+
60+ readonly property point tp: recognizedPress ? Qt.point(touchPoints[0].x, touchPoints[0].y) : Qt.point(-1, -1)
61+ onUpdated: {
62+ if (recognizedDrag) {
63+ priv.handlePositionChanged(tp.x, tp.y);
64+ }
65+ }
66+ }
67+
68+ // dismiss timer
69+ Timer {
70+ id: overlayTimer
71+ interval: 2000
72+ repeat: priv.dragging || (priv.resizeArea && priv.resizeArea.dragging)
73+ }
74+
75+ QtObject {
76+ id: priv
77+ property real distanceX
78+ property real distanceY
79+ property bool dragging
80+
81+ readonly property var resizeArea: root.target && root.target.resizeArea ? root.target.resizeArea : null
82+
83+ function handlePressedChanged(pressed, mouseX, mouseY) {
84+ if (pressed) {
85+ var pos = mapToItem(root.target, mouseX, mouseY);
86+ priv.distanceX = pos.x;
87+ priv.distanceY = pos.y;
88+ priv.dragging = true;
89+ } else {
90+ priv.dragging = false;
91+ }
92+ }
93+
94+ function handlePositionChanged(mouseX, mouseY) {
95+ if (priv.dragging) {
96+ var pos = mapToItem(root.target.parent, mouseX, mouseY);
97+ root.target.x = Math.round(pos.x - priv.distanceX);
98+ root.target.y = Math.round(Math.max(pos.y - priv.distanceY, PanelState.panelHeight));
99+ }
100+ }
101+ }
102+
103+ // the visual overlay
104+ Item {
105+ id: overlay
106+ objectName: "windowControlsOverlay"
107+ anchors.fill: parent
108+ enabled: overlayTimer.running
109+ visible: opacity > 0
110+ opacity: enabled ? 0.95 : 0
111+
112+ Behavior on opacity {
113+ UbuntuNumberAnimation {}
114+ }
115+
116+ readonly property bool anyMaximized: target && (target.maximized || target.maximizedLeft || target.maximizedRight)
117+
118+ Image {
119+ source: "graphics/arrows-centre.png"
120+ width: units.gu(10)
121+ height: width
122+ sourceSize: Qt.size(width, height)
123+ anchors.centerIn: parent
124+ visible: target && target.width > units.gu(12) && target.height > units.gu(12)
125+
126+ // move handler
127+ MouseArea {
128+ id: moveHandler
129+ anchors.fill: parent
130+ visible: overlay.visible
131+ enabled: visible
132+ hoverEnabled: true
133+ cursorShape: priv.dragging ? Qt.ClosedHandCursor : (overlay.visible ? Qt.OpenHandCursor : Qt.ArrowCursor)
134+
135+ onPressedChanged: priv.handlePressedChanged(pressed, mouseX, mouseY)
136+ onPositionChanged: priv.handlePositionChanged(mouseX, mouseY)
137+ }
138+
139+ // dismiss area
140+ InverseMouseArea {
141+ anchors.fill: parent
142+ visible: overlay.visible
143+ enabled: visible
144+ onPressed: {
145+ if (gestureArea.recognizedPress || gestureArea.recognizedDrag) {
146+ mouse.accepted = false;
147+ return;
148+ }
149+
150+ overlayTimer.stop();
151+ mouse.accepted = root.contains(Qt.point(mouse.x, mouse.y));
152+ }
153+ propagateComposedEvents: true
154+ }
155+ }
156+
157+ ResizeGrip { // top left
158+ anchors.horizontalCenter: parent.left
159+ anchors.verticalCenter: parent.top
160+ visible: target && !overlay.anyMaximized && !target.maximizedHorizontally && !target.maximizedVertically
161+ resizeTarget: priv.resizeArea
162+ }
163+
164+ ResizeGrip { // top center
165+ anchors.horizontalCenter: parent.horizontalCenter
166+ anchors.verticalCenter: parent.top
167+ rotation: 45
168+ visible: target && !overlay.anyMaximized && !target.maximizedVertically
169+ resizeTarget: priv.resizeArea
170+ }
171+
172+ ResizeGrip { // top right
173+ anchors.horizontalCenter: parent.right
174+ anchors.verticalCenter: parent.top
175+ rotation: 90
176+ visible: target && !overlay.anyMaximized && !target.maximizedHorizontally && !target.maximizedVertically
177+ resizeTarget: priv.resizeArea
178+ }
179+
180+ ResizeGrip { // right
181+ anchors.horizontalCenter: parent.right
182+ anchors.verticalCenter: parent.verticalCenter
183+ rotation: 135
184+ visible: target && !target.maximizedRight && !target.maximized && !target.maximizedHorizontally
185+ resizeTarget: priv.resizeArea
186+ }
187+
188+ ResizeGrip { // bottom right
189+ anchors.horizontalCenter: parent.right
190+ anchors.verticalCenter: parent.bottom
191+ visible: target && !overlay.anyMaximized && !target.maximizedHorizontally && !target.maximizedVertically
192+ resizeTarget: priv.resizeArea
193+ }
194+
195+ ResizeGrip { // bottom center
196+ anchors.horizontalCenter: parent.horizontalCenter
197+ anchors.verticalCenter: parent.bottom
198+ rotation: 45
199+ visible: target && !overlay.anyMaximized && !target.maximizedVertically
200+ resizeTarget: priv.resizeArea
201+ }
202+
203+ ResizeGrip { // bottom left
204+ anchors.horizontalCenter: parent.left
205+ anchors.verticalCenter: parent.bottom
206+ rotation: 90
207+ visible: target && !overlay.anyMaximized && !target.maximizedHorizontally && !target.maximizedVertically
208+ resizeTarget: priv.resizeArea
209+ }
210+
211+ ResizeGrip { // left
212+ anchors.horizontalCenter: parent.left
213+ anchors.verticalCenter: parent.verticalCenter
214+ rotation: 135
215+ visible: target && !target.maximizedLeft && !target.maximized && !target.maximizedHorizontally
216+ resizeTarget: priv.resizeArea
217+ }
218+ }
219+}
qml/Stages/WindowDecoration.qml+13−1
@@ -27,6 +27,8 @@ MouseArea {
2727 property Item target
2828 property alias title: titleLabel.text
2929 property bool active: false
30+ property alias overlayShown: buttons.overlayShown
31+
3032 hoverEnabled: true
3133
3234 signal closeClicked()
@@ -78,11 +80,15 @@ MouseArea {
7880 Row {
7981 anchors {
8082 fill: parent
81− leftMargin: units.gu(1)
83+ leftMargin: overlayShown ? units.gu(5) : units.gu(1)
8284 rightMargin: units.gu(1)
8385 topMargin: units.gu(0.5)
8486 bottomMargin: units.gu(0.5)
8587 }
88+ Behavior on anchors.leftMargin {
89+ UbuntuNumberAnimation {}
90+ }
91+
8692 spacing: units.gu(3)
8793
8894 WindowControlButtons {
@@ -95,6 +101,7 @@ MouseArea {
95101 onMaximizeHorizontallyClicked: root.maximizeHorizontallyClicked();
96102 onMaximizeVerticallyClicked: root.maximizeVerticallyClicked();
97103 closeButtonShown: root.target.application.appId !== "unity8-dash"
104+ target: root.target
98105 }
99106
100107 Label {
@@ -107,6 +114,11 @@ MouseArea {
107114 fontSize: "medium"
108115 font.weight: root.active ? Font.Light : Font.Medium
109116 elide: Text.ElideRight
117+ opacity: overlayShown ? 0 : 1
118+ visible: opacity == 1
119+ Behavior on opacity {
120+ OpacityAnimator { duration: UbuntuAnimation.FastDuration; easing: UbuntuAnimation.StandardEasing }
121+ }
110122 }
111123 }
112124}
qml/Stages/WindowResizeArea.qml+4−4
@@ -22,15 +22,17 @@ import "../Components/PanelState"
2222
2323MouseArea {
2424 id: root
25+
2526 anchors.fill: target
2627 anchors.margins: -borderThickness
2728
2829 hoverEnabled: target && !target.maximized // don't grab the resize under the panel
2930
3031 property var windowStateStorage: WindowStateStorage
32+ readonly property alias dragging: d.dragging
3133
3234 // The target item managed by this. Must be a parent or a sibling
33− // The area will anchor to it and manage move and resize events
35+ // The area will anchor to it and manage resize events
3436 property Item target: null
3537 property string windowId: ""
3638 property int borderThickness: 0
@@ -200,7 +202,7 @@ MouseArea {
200202 property real currentWidth
201203 property real currentHeight
202204
203− property string cursorName: {
205+ readonly property string cursorName: {
204206 if (root.containsMouse || root.pressed) {
205207 if (leftBorder && !topBorder && !bottomBorder) {
206208 return "left_side";
@@ -247,8 +249,6 @@ MouseArea {
247249 }
248250
249251 onPressedChanged: {
250− var pos = mapToItem(target.parent, mouseX, mouseY);
251−
252252 if (pressed) {
253253 d.updateBorders();
254254 resetBordersToMoveTimer.stop();
qml/Stages/graphics/arrows-centre.pngnew file

Binary file not shown.

qml/Stages/graphics/arrows.pngnew file

Binary file not shown.

tests/qmltests/Stages/SurfaceManagerControls.qml+1−1
@@ -21,7 +21,7 @@ import Unity.Application 0.1
2121Column {
2222 id: root
2323
24− Label {text: "Size hints for new surface:"; color: root.textColor; font.bold: true}
24+ Label {text: "Size hints for new surface:"; font.bold: true}
2525
2626 SurfaceManagerField { text: "min width"; propertyName: "newSurfaceMinimumWidth"; }
2727 SurfaceManagerField { text: "max width"; propertyName: "newSurfaceMaximumWidth"; }
tests/qmltests/Stages/tst_DesktopStage.qml+23
@@ -648,6 +648,29 @@ Item {
648648 tryCompare(PanelState, "dropShadow", false);
649649 }
650650
651+ function test_threeFingerTapShowsWindowControls_data() {
652+ return [
653+ { tag: "1 finger", touchIds: [0], result: false },
654+ { tag: "2 finger", touchIds: [0, 1], result: false },
655+ { tag: "3 finger", touchIds: [0, 1, 2], result: true },
656+ { tag: "4 finger", touchIds: [0, 1, 2, 3], result: false },
657+ ];
658+ }
659+
660+ function test_threeFingerTapShowsWindowControls(data) {
661+ var facebookAppDelegate = startApplication("facebook-webapp");
662+ verify(facebookAppDelegate);
663+ var overlay = findChild(facebookAppDelegate, "windowControlsOverlay");
664+ verify(overlay);
665+
666+ multiTouchTap(data.touchIds, facebookAppDelegate);
667+ tryCompare(overlay, "visible", data.result);
668+
669+ if (data.result) { // if shown, try to hide it by clicking outside
670+ mouseClick(desktopStage);
671+ tryCompare(overlay, "visible", false);
672+ }
673+ }
651674 function test_dashHasNoCloseButton() {
652675 var dashAppDelegate = startApplication("unity8-dash");
653676 verify(dashAppDelegate);