-
Notifications
You must be signed in to change notification settings - Fork 68
/
Copy pathSpBoxLayout.class.st
484 lines (382 loc) · 14.5 KB
/
SpBoxLayout.class.st
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
"
A layout that will arrange presenters in a box, vertically (top to bottom) or horizontally (left to right).
Elements can be added at the begining or at the end of the box (see `SpBoxLayout>>#add:` and `SpBoxLayout>>#addLast:` method families).
## Discussion
The box layout is the main layout in Spec, because its versatility and ease to use. While one may think its functionality is limited, the fact that it can be composed with other types of layouts or other instances of itself (other boxes), will let users do almost everything they need.
The basic usage is, however, very simple:
```Smalltalk
SpBoxLayout newTopToBottom
add: aPresenter;
add: otherPresenter;
yourself.
```
This will arrange `aPresenter` and `otherPresenter` vertically, assigning to each presenter as much `height` as they can receive (usually 50% of the box).
Of course, being able to do _just that_ would be very limiting, but then is where layout properties start to be important.
### The expand property (`SpBoxLayout>>#add:expand:`)
With the previous example, let's define a some real presenters:
```Smalltalk
| presenter |
presenter := SpPresenter new.
presenter layout: (SpBoxLayout newTopToBottom
add: presenter newTextInput;
add: presenter newText;
yourself).
presenter openWithSpec
```
What's happened when we opened it? Well, the box layout was filled with an input text and a text area, but the height assigned to the first presenter is not what we would have expected, since even if text input is smaller, area height assigned is still half.
The property expand fixes this problem, by telling the layout not to assign more height than what is explicitly needed by the contained presenter:
```Smalltalk
| presenter |
presenter := SpPresenter new.
presenter layout: (SpBoxLayout newTopToBottom
add: presenter newTextInput expand: false;
add: presenter newText;
yourself).
presenter openWithSpec
```
Now the presenter will behave as one would expect (This code is in `SpBoxLayout class>>#exampleExpand`).
### Adding presenters **last**.
Now, consider this problem: You want to add something *at the end* of your presenter (for example, you want to add a button, making a dialog):
```Smalltalk
| presenter |
presenter := SpPresenter new.
presenter layout: (SpBoxLayout newTopToBottom
add: 'Enter text' expand: false;
add: presenter newTextInput expand: false;
add: (presenter newButton label: 'Ok') expand: false;
yourself).
presenter openWithSpec
```
But you want that button to be shown at the end. This is why we use the `addLast:` method, who will instruct the layout to add this presenter at the end.
```Smalltalk
| presenter |
presenter := SpPresenter new.
presenter layout: (SpBoxLayout newTopToBottom
add: 'Enter text' expand: false;
add: presenter newTextInput expand: false;
addLast: (presenter newButton label: 'Ok') expand: false;
yourself).
presenter openWithSpec
```
That makes the presenter work as expected (This code is in SpBoxLayout class>>#exampleAddLast).
### Composing layouts
The real power of a layout comes when you understand they can be composed (with other instances of themselves or any other kind, children of `SpExecutableLayout`).
For example, taking the previous example, let's say you want to add two buttons (Ok, Cancel), aligned horizontaly. To accomplish that, you add a new layout with the buttons:
```Smalltalk
| presenter |
presenter := SpPresenter new.
presenter layout: (SpBoxLayout newTopToBottom
add: 'Enter text' expand: false;
add: presenter newTextInput expand: false;
addLast: (SpBoxLayout newLeftToRight
addLast: (presenter newButton label: 'Ok') expand: false;
addLast: (presenter newButton label: 'Cancel') expand: false;
yourself)
expand: false;
yourself).
presenter openWithSpec
```
Now, the presenter will show buttons at bottom-right corner (This code is in `SpBoxLayout class>>#exampleComposite`).
"
Class {
#name : 'SpBoxLayout',
#superclass : 'SpDirectionableLayout',
#traits : 'SpTAlignable',
#classTraits : 'SpTAlignable classTrait',
#instVars : [
'spacing',
'borderWidth',
'homogeneous'
],
#category : 'Spec2-Layout-Box',
#package : 'Spec2-Layout',
#tag : 'Box'
}
{ #category : 'private' }
SpBoxLayout >> adapterName [
^ #BoxAdapter
]
{ #category : 'api - adding' }
SpBoxLayout >> add: aPresenterLayoutOrSymbol [
"Adds `aPresenterLayoutOrSymbol` to the list of presenters to be arranged in the layout.
`aPresenterLayoutOrSymboll` can be
- any instance of `SpPresenter` hierarchy,
- another layout or
- a Symbol, matching the name of the instance variable who will contain the element to add."
self
add: aPresenterLayoutOrSymbol
withConstraints: [ :constraints | ]
]
{ #category : 'api - adding' }
SpBoxLayout >> add: aPresenterLayoutOrSymbol expand: shouldExpand [
"Adds `aPresenterLayoutOrSymbol` to the list of presenters to be arranged in the layout, setting
also the expand property.
`aPresenterLayoutOrSymboll` can be
- any instance of `SpPresenter` hierarchy,
- another layout or
- a Symbol, matching the name of the instance variable who will contain the element to add.
`shouldExpand` is a boolean indicating whether the new child is to be given extra space allocated
to the box."
^ self
add: aPresenterLayoutOrSymbol
withConstraints: [ :constraints |
constraints expand: shouldExpand ]
]
{ #category : 'private' }
SpBoxLayout >> add: aName expand: shouldExpand fill: shouldFill [
self add: aName withConstraints: [ :constraints |
constraints
expand: shouldExpand;
fill: shouldFill ]
]
{ #category : 'private' }
SpBoxLayout >> add: aName expand: shouldExpand fill: shouldFill padding: aNumber [
"
aName - the presenter to be added to box
expand - true if the new child is to be given extra space allocated to box .
The extra space will be divided evenly between all children that use this option
fill - true if space given to child by the expand option is actually allocated to child ,
rather than just padding it. This parameter has no effect if expand is set to false.
padding - extra space in pixels to put between this child and its neighbors, over and above
the global amount specified by “spacing” property. If child is a widget at one of
the reference ends of box , then padding pixels are also put between child and the
reference edge of box"
self
add: aName
withConstraints: [ :constraints |
constraints
expand: shouldExpand;
fill: shouldFill;
padding: aNumber ]
]
{ #category : 'api - adding' }
SpBoxLayout >> add: aPresenterLayoutOrSymbol height: anInteger [
"Add a presenter and assign a specific height.
This constraint is applied just when using a Vertical (Top to Bottom) layout,
otherwise is ignored.
Please notice that 99% of the time you DO NOT NEED (and you do not want) to use
a fixed constraint like this one.
Instead, consider using `SpBoxLayout>>#add:expand:` method. "
self
add: aPresenterLayoutOrSymbol
withConstraints: [ :constraints | constraints height: anInteger ].
]
{ #category : 'api - adding' }
SpBoxLayout >> add: aPresenterLayoutOrSymbol layout: aSelector [
"Adds `aPresenterLayoutOrSymbol` to the list of presenters to be arranged in the layout, setting
also the layout to use with the added presenter.
`aPresenterLayoutOrSymbol` can be
- any instance of `SpPresenter` hierarchy,
- another layout or
- a Symbol, matching the name of the instance variable who will contain the element to add.
`aSelector` is a selector that has to be in the class side of `aPresenterLayoutOrSymbol` instance, and it will answer the layout to use."
^ self
add: aPresenterLayoutOrSymbol
withConstraints: [ :constraints |
constraints spec: aSelector ]
]
{ #category : 'api - adding' }
SpBoxLayout >> add: aPresenterLayoutOrSymbol width: anInteger [
"Add a presenter and assign a specific width.
This constraint is applied just when using an Horizontal (Left to Right) layout,
otherwise is ignored.
Please notice that 99% of the time you DO NOT NEED (and you do not want) to use
a fixed constraint like this one.
Instead, consider using `SpBoxLayout>>#add:expand:` method. "
self
add: aPresenterLayoutOrSymbol
withConstraints: [ :constraints | constraints width: anInteger ].
]
{ #category : 'api - adding' }
SpBoxLayout >> add: aPresenterLayoutOrSymbol withConstraints: aBlock [
"Adds `aPresenterLayoutOrSymbol` to the list of presenters to be arranged in the layout, updating
the constraints directly.
`aPresenterLayoutOrSymboll` can be
- any instance of `SpPresenter` hierarchy,
- another layout or
- a Symbol, matching the name of the instance variable who will contain the element to add.
`aBlock` receives one argument (an instance of `SpBoxConstraints`), to be modified directly.
NOTICE that the direct usage of this method is NOT recommended. Consider using add:/add:expand:
instead"
super add: aPresenterLayoutOrSymbol withConstraints: aBlock.
self announceChildAdded: aPresenterLayoutOrSymbol
]
{ #category : 'api - adding' }
SpBoxLayout >> addLast: aName [
"Adds `aPresenterLayoutOrSymbol` to the **end of the list** of presenters to be arranged
in the layout.
`aPresenterLayoutOrSymboll` can be
- any instance of `SpPresenter` hierarchy,
- another layout or
- a Symbol, matching the name of the instance variable who will contain the element to add."
self flag: #doNotUse.
self
addLast: aName
withConstraints: [ :constraints | ]
]
{ #category : 'api - adding' }
SpBoxLayout >> addLast: aName expand: shouldExpand [
"Adds `aPresenterLayoutOrSymbol` to the **end of the list** of presenters to be arranged
in the layout, setting also the expand property.
`aPresenterLayoutOrSymboll` can be
- any instance of `SpPresenter` hierarchy,
- another layout or
- a Symbol, matching the name of the instance variable who will contain the element to add.
`shouldExpand` is a boolean indicating whether the new child is to be given extra space allocated
to the box."
self flag: #doNotUse.
self
addLast: aName
withConstraints: [ :constraints |
constraints expand: shouldExpand ]
]
{ #category : 'private' }
SpBoxLayout >> addLast: aName expand: shouldExpand fill: shouldFill [
self flag: #doNotUse.
self
addLast: aName
withConstraints: [ :constraints |
constraints
expand: shouldExpand;
fill: shouldFill ]
]
{ #category : 'private' }
SpBoxLayout >> addLast: aName expand: shouldExpand fill: shouldFill padding: aNumber [
self flag: #doNotUse.
self
addLast: aName
withConstraints: [ :constraints |
constraints
expand: shouldExpand;
fill: shouldFill;
padding: aNumber ]
]
{ #category : 'api - adding' }
SpBoxLayout >> addLast: aName withConstraints: aBlock [
"Adds `aPresenterLayoutOrSymbol` to the **end of the list** of presenters to be arranged
in the layout, updating the constraints directly.
`aPresenterLayoutOrSymboll` can be
- any instance of `SpPresenter` hierarchy,
- another layout or
- a Symbol, matching the name of the instance variable who will contain the element to add.
`aBlock` receives one argument (an instance of `SpBoxConstraints`), to be modified directly.
NOTICE that the direct usage of this method is NOT recommended. Consider using add:/add:expand:
instead"
self flag: #doNotUse.
self
add: aName
withConstraints: [ :constraints |
constraints bePlacedAtEnd.
aBlock value: constraints ]
]
{ #category : 'api' }
SpBoxLayout >> beHomogeneous [
"When a layout us set as homogeneous, all its components have assigned same space, regardless
their own expand or size settings."
self homogeneous: true
]
{ #category : 'api' }
SpBoxLayout >> beNotHomogeneous [
"When a layout us set as not homogeneous, components are ordered respecting their own
properties about expand and/or size assignment.
This is the default."
self homogeneous: false
]
{ #category : 'api' }
SpBoxLayout >> borderWidth [
"Answer the border width defined for the layout (default is zero)"
^ borderWidth
]
{ #category : 'api' }
SpBoxLayout >> borderWidth: aNumber [
"Set the border width to be used by the layout"
borderWidth := aNumber.
self withAdapterDo: [ :anAdapter | anAdapter updateBorderWidth ]
]
{ #category : 'private' }
SpBoxLayout >> constraintsClass [
^ SpBoxConstraints
]
{ #category : 'accessing' }
SpBoxLayout >> direction [
^ direction
]
{ #category : 'private' }
SpBoxLayout >> homogeneous: aBoolean [
homogeneous := aBoolean
]
{ #category : 'initialization' }
SpBoxLayout >> initialize [
super initialize.
self beNotHomogeneous.
self spacing: 0.
self borderWidth: 0
]
{ #category : 'testing' }
SpBoxLayout >> isHomogeneous [
"Answer whether the layout should distribute children homogeneous or not"
^ homogeneous
]
{ #category : 'testing' }
SpBoxLayout >> isHorizontal [
"Answer true if the layout direction is horizontal"
^ self direction = SpLayoutDirection horizontal
]
{ #category : 'testing' }
SpBoxLayout >> isVertical [
"Answer true if the layout direction is vertical"
^ self direction = SpLayoutDirection vertical
]
{ #category : 'private' }
SpBoxLayout >> presenterAt: index [
^ children keyAtIndex: index
]
{ #category : 'api - adding' }
SpBoxLayout >> removeAll [
"Remove all presenters added to this layout."
children ifEmpty: [ ^ self ].
children keysDo: [ :each |
each withAdapterDo: [ :anAdapter | anAdapter unsubscribe ] ].
children removeAll.
self withAdapterDo: [ :anAdapter | anAdapter removeAll ]
]
{ #category : 'api - adding' }
SpBoxLayout >> replace: aPresenter with: otherPresenter [
"Replace aPresenter with otherPresenter.
Original constraints will be preserved."
self
replace: aPresenter
with: otherPresenter
withConstraints: (self constraintsFor: aPresenter)
]
{ #category : 'api - adding' }
SpBoxLayout >> replace: aPresenter with: otherPresenter withConstraints: constraints [
"Replace aPresenter with otherPresenter"
children
replaceKey: aPresenter
with: otherPresenter.
self withAdapterDo: [ :anAdapter |
anAdapter
replace: aPresenter
with: otherPresenter
withConstraints: constraints ]
]
{ #category : 'api - adding' }
SpBoxLayout >> replaceAtIndex: index with: aPresenter [
"Replace the presenter in `index` with otherPresenter.
Original constraints will be preserved."
self
replace: (self presenterAt: index)
with: aPresenter
]
{ #category : 'api' }
SpBoxLayout >> spacing [
"Answer the spacing between elements of the layout (default is zero)"
^ spacing
]
{ #category : 'api' }
SpBoxLayout >> spacing: aNumber [
"Set the spacing between elements of the layout"
spacing := aNumber.
self withAdapterDo: [ :anAdapter | anAdapter updateSpacing ]
]