1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
3
4// cSpell: ignore resizer
5
6global ResizeState {
7 out property <length> handle-size: 8px;
8}
9
10component ResizeHandle inherits Rectangle {
11 in property <bool> show-handle: true;
12 in property <color> my-color: Colors.black;
13 in property <MouseCursor> mouse-cursor;
14 out property <bool> resizing <=> ta.pressed;
15
16 callback resize(/* width */ length, /* height */ length, /* done? */ bool);
17 callback resize-done(/* width */ length, /* height */ length);
18
19 // Internal!
20 in-out property <length> new-width;
21 in-out property <length> new-height;
22 in-out property <length> new-x;
23 in-out property <length> new-y;
24
25 width: ResizeState.handle-size;
26 height: ResizeState.handle-size;
27
28 background: root.show-handle ? Colors.white : Colors.transparent;
29 border-color: root.show-handle ? root.my-color : Colors.transparent;
30
31 border-width: 1px;
32
33 ta := TouchArea {
34 private property <length> x-offset: self.mouse-x - self.pressed-x;
35 private property <length> y-offset: self.mouse-y - self.pressed-y;
36
37 mouse-cursor <=> root.mouse-cursor;
38
39 moved() => {
40 root.resize(x-offset, y-offset, false);
41 }
42
43 pointer-event(event) => {
44 if event.button == PointerEventButton.left && event.kind == PointerEventKind.up {
45 root.resize(x-offset, y-offset, true);
46 }
47 }
48 }
49}
50
51export component Resizer {
52 in property <length> x-position;
53 in property <length> y-position;
54
55 in property <bool> is-resizable: true;
56 in property <bool> is-moveable: false;
57 in property <color> color: Colors.black;
58 out property <bool> resizing: n.resizing || ne.resizing || e.resizing || se.resizing || s.resizing || sw.resizing || w.resizing || nw.resizing;
59 out property <bool> moving: resize-area.moving;
60 out property <length> handle-size: ResizeState.handle-size;
61
62 callback update-geometry(/* x */ length, /* y */ length, /* width */ length, /* height */ length, /* done */ bool);
63 callback double-clicked(/* x */ length, /* y */ length, /* modifiers */ KeyboardModifiers);
64
65 // Private!
66 in-out property <length> top: self.y-position;
67 in-out property <length> bottom: self.y-position + self.height;
68 in-out property <length> left: self.x-position;
69 in-out property <length> right: self.x-position + self.width;
70
71 resize-area := Rectangle {
72 in-out property <bool> moving: false;
73
74 width <=> root.width;
75 height <=> root.height;
76
77 @children
78
79 if root.is-moveable: TouchArea {
80 private property <length> x-offset: self.mouse-x - self.pressed-x;
81 private property <length> y-offset: self.mouse-y - self.pressed-y;
82 private property <KeyboardModifiers> modifiers;
83 private property <bool> has-moved;
84
85 mouse-cursor: MouseCursor.move;
86
87 double-clicked() => {
88 root.double-clicked(self.mouse-x, self.mouse-y, self.modifiers);
89 }
90
91 moved() => {
92 root.update-geometry(root.x-position + x-offset, root.y-position + y-offset, root.width, root.height, false);
93 self.has-moved = true;
94 }
95
96 pointer-event(event) => {
97 resize-area.moving = self.pressed;
98 self.modifiers = event.modifiers;
99 if self.has-moved && event.button == PointerEventButton.left && event.kind == PointerEventKind.up {
100 root.update-geometry(root.x-position + x-offset, root.y-position + y-offset, root.width, root.height, true);
101 self.has-moved = false;
102 }
103 }
104 }
105 }
106
107 Rectangle {
108 visible: is-resizable;
109
110 n := ResizeHandle {
111 show-handle: false;
112 resize(x-offset, y-offset, done) => {
113 self.new-width = root.width;
114 self.new-height = Math.max(0px, y-offset * -1.0 + root.height);
115 self.new-x = root.left;
116 self.new-y = root.bottom - self.new-height;
117 root.update-geometry(self.new-x, self.new-y, self.new-width, self.new-height, done);
118 }
119
120 mouse-cursor: MouseCursor.n-resize;
121 x: (root.handle-size / 2.0);
122 y: -(root.handle-size / 2.0);
123 width: root.width - root.handle-size;
124 }
125
126 ne := ResizeHandle {
127 my-color: root.color;
128 resize(x-offset, y-offset, done) => {
129 self.new-width = Math.max(0px, x-offset + root.width);
130 self.new-height = Math.max(0px, y-offset * -1.0 + root.height);
131 self.new-x = root.left;
132 self.new-y = root.bottom - self.new-height;
133 root.update-geometry(self.new-x, self.new-y, self.new-width, self.new-height, done);
134 }
135 mouse-cursor: MouseCursor.ne-resize;
136 x: root.width - (root.handle-size / 2.0);
137 y: -(root.handle-size / 2.0);
138 }
139
140 e := ResizeHandle {
141 show-handle: false;
142 resize(x-offset, y-offset, done) => {
143 self.new-width = Math.max(0px, x-offset + root.width);
144 self.new-height = root.height;
145 self.new-x = root.left;
146 self.new-y = root.top;
147 root.update-geometry(self.new-x, self.new-y, self.new-width, self.new-height, done);
148 }
149 mouse-cursor: MouseCursor.e-resize;
150 x: root.width - (root.handle-size / 2.0);
151 y: root.handle-size / 2.0;
152 height: root.height - root.handle-size;
153 }
154
155 se := ResizeHandle {
156 my-color: root.color;
157 resize(x-offset, y-offset, done) => {
158 self.new-width = Math.max(0px, x-offset + root.width);
159 self.new-height = Math.max(0px, y-offset + root.height);
160 self.new-x = root.left;
161 self.new-y = root.top;
162 root.update-geometry(self.new-x, self.new-y, self.new-width, self.new-height, done);
163 }
164 mouse-cursor: MouseCursor.se-resize;
165 x: root.width - (root.handle-size / 2.0);
166 y: root.height - (root.handle-size / 2.0);
167 }
168
169 s := ResizeHandle {
170 show-handle: false;
171 resize(x-offset, y-offset, done) => {
172 self.new-width = root.width;
173 self.new-height = Math.max(0px, y-offset + root.height);
174 self.new-x = root.left;
175 self.new-y = root.top;
176 root.update-geometry(self.new-x, self.new-y, self.new-width, self.new-height, done);
177 }
178 mouse-cursor: MouseCursor.s-resize;
179 x: root.handle-size / 2.0;
180 y: root.height - (root.handle-size / 2.0);
181 width: root.width - root.handle-size;
182 }
183
184 sw := ResizeHandle {
185 my-color: root.color;
186 resize(x-offset, y-offset, done) => {
187 self.new-width = Math.max(0px, x-offset * -1.0 + root.width);
188 self.new-height = Math.max(0px, y-offset + root.height);
189 self.new-x = root.right - self.new-width;
190 self.new-y = root.top;
191 root.update-geometry(self.new-x, self.new-y, self.new-width, self.new-height, done);
192 }
193 mouse-cursor: MouseCursor.sw-resize;
194 x: -(root.handle-size / 2.0);
195 y: root.height - (root.handle-size / 2.0);
196 }
197
198 w := ResizeHandle {
199 show-handle: false;
200 resize(x-offset, y-offset, done) => {
201 self.new-width = Math.max(0px, x-offset * -1.0 + root.width);
202 self.new-height = root.height;
203 self.new-x = root.right - self.new-width;
204 self.new-y = root.top;
205 root.update-geometry(self.new-x, self.new-y, self.new-width, self.new-height, done);
206 }
207 mouse-cursor: MouseCursor.w-resize;
208 x: -(root.handle-size / 2.0);
209 y: (root.handle-size / 2.0);
210 height: root.height - root.handle-size;
211 }
212
213 nw := ResizeHandle {
214 my-color: root.color;
215 resize(x-offset, y-offset, done) => {
216 self.new-width = Math.max(0px, x-offset * -1.0 + root.width);
217 self.new-height = Math.max(0px, y-offset * -1.0 + root.height);
218 self.new-x = root.right - self.new-width;
219 self.new-y = root.bottom - self.new-height;
220 root.update-geometry(self.new-x, self.new-y, self.new-width, self.new-height, done);
221 }
222 mouse-cursor: MouseCursor.nw-resize;
223 x: -(root.handle-size / 2.0);
224 y: -(root.handle-size / 2.0);
225 }
226 }
227}
228