]> git.mxchange.org Git - simgear.git/blob - simgear/canvas/layout/Layout.cxx
Off-by-one error in the OSG_VERSION_LESS_THAN macro
[simgear.git] / simgear / canvas / layout / Layout.cxx
1 // Basic class for canvas layouts
2 //
3 // Copyright (C) 2014  Thomas Geymayer <tomgey@gmail.com>
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Library General Public
7 // License as published by the Free Software Foundation; either
8 // version 2 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 // Library General Public License for more details.
14 //
15 // You should have received a copy of the GNU Library General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
18
19 #include "Layout.hxx"
20 #include <simgear/debug/logstream.hxx>
21
22 namespace simgear
23 {
24 namespace canvas
25 {
26
27   //----------------------------------------------------------------------------
28   void Layout::removeItem(const LayoutItemRef& item)
29   {
30     size_t i = 0;
31     while( LayoutItemRef child = itemAt(i) )
32     {
33       if( item == child )
34         return (void)takeAt(i);
35
36       ++i;
37     }
38   }
39
40   //----------------------------------------------------------------------------
41   void Layout::clear()
42   {
43     while( itemAt(0) )
44       takeAt(0);
45   }
46
47   //----------------------------------------------------------------------------
48   SGRecti Layout::alignmentRect(const SGRecti& geom) const
49   {
50     return alignment() == AlignFill
51          // Without explicit alignment (default == AlignFill) use the whole
52          // available space and let the layout and its items distribute the
53          // excess space.
54          ? geom
55
56          // Otherwise align according to flags.
57          : LayoutItem::alignmentRect(geom);
58   }
59
60   //----------------------------------------------------------------------------
61   void Layout::ItemData::reset()
62   {
63     layout_item = 0;
64     size_hint   = 0;
65     min_size    = 0;
66     max_size    = 0;
67     padding_orig= 0;
68     padding     = 0;
69     size        = 0;
70     stretch     = 0;
71     visible     = false;
72     has_align   = false;
73     has_hfw     = false;
74     done        = false;
75   }
76
77   //----------------------------------------------------------------------------
78   int Layout::ItemData::hfw(int w) const
79   {
80     if( has_hfw )
81       return layout_item->heightForWidth(w);
82     else
83       return layout_item->sizeHint().y();
84   }
85
86   //----------------------------------------------------------------------------
87   int Layout::ItemData::mhfw(int w) const
88   {
89     if( has_hfw )
90       return layout_item->minimumHeightForWidth(w);
91     else
92       return layout_item->minimumSize().y();
93   }
94
95   //----------------------------------------------------------------------------
96   Layout::Layout():
97     _num_not_done(0),
98     _sum_stretch(0),
99     _space_stretch(0),
100     _space_left(0)
101   {
102
103   }
104
105   //----------------------------------------------------------------------------
106   void Layout::contentsRectChanged(const SGRecti& rect)
107   {
108     doLayout(rect);
109
110     _flags &= ~LAYOUT_DIRTY;
111   }
112
113   //----------------------------------------------------------------------------
114   void Layout::distribute(std::vector<ItemData>& items, const ItemData& space)
115   {
116     const int num_children = static_cast<int>(items.size());
117     _num_not_done = 0;
118
119     SG_LOG( SG_GUI,
120             SG_DEBUG,
121             "Layout::distribute(" << space.size << "px for "
122                                   << num_children << " items, s.t."
123                                   << " min=" << space.min_size
124                                   << ", hint=" << space.size_hint
125                                   << ", max=" << space.max_size << ")" );
126
127     if( space.size < space.min_size )
128     {
129       // TODO
130       SG_LOG( SG_GUI, SG_WARN, "Layout: not enough size (not implemented)");
131     }
132     else if( space.size < space.max_size )
133     {
134       _sum_stretch = 0;
135       _space_stretch = 0;
136
137       bool less_then_hint = space.size < space.size_hint;
138
139       // Give min_size/size_hint to all items
140       _space_left = space.size
141                   - (less_then_hint ? space.min_size : space.size_hint);
142       for(int i = 0; i < num_children; ++i)
143       {
144         ItemData& d = items[i];
145         if( !d.visible )
146           continue;
147
148         d.size = less_then_hint ? d.min_size : d.size_hint;
149         d.padding = d.padding_orig;
150         d.done = d.size >= (less_then_hint ? d.size_hint : d.max_size);
151
152         SG_LOG(
153           SG_GUI,
154           SG_DEBUG,
155           i << ") initial=" << d.size
156             << ", min=" << d.min_size
157             << ", hint=" << d.size_hint
158             << ", max=" << d.max_size
159         );
160
161         if( d.done )
162           continue;
163         _num_not_done += 1;
164
165         if( d.stretch > 0 )
166         {
167           _sum_stretch += d.stretch;
168           _space_stretch += d.size;
169         }
170       }
171
172       // Distribute remaining space to increase the size of each item up to its
173       // size_hint/max_size
174       while( _space_left > 0 )
175       {
176         if( _num_not_done <= 0 )
177         {
178           SG_LOG(SG_GUI, SG_WARN, "space left, but no more items?");
179           break;
180         }
181
182         int space_per_element = std::max(1, _space_left / _num_not_done);
183
184         SG_LOG(SG_GUI, SG_DEBUG, "space/element=" << space_per_element);
185
186         for(int i = 0; i < num_children; ++i)
187         {
188           ItemData& d = items[i];
189           if( !d.visible )
190             continue;
191
192           SG_LOG(
193             SG_GUI,
194             SG_DEBUG,
195             i << ") left=" << _space_left
196               << ", not_done=" << _num_not_done
197               << ", sum=" << _sum_stretch
198               << ", stretch=" << _space_stretch
199               << ", stretch/unit=" << _space_stretch / std::max(1, _sum_stretch)
200           );
201
202           if( d.done )
203             continue;
204
205           if( _sum_stretch > 0 && d.stretch <= 0 )
206             d.done = true;
207           else
208           {
209             int target_size = 0;
210             int max_size = less_then_hint ? d.size_hint : d.max_size;
211
212             if( _sum_stretch > 0 )
213             {
214               target_size = (d.stretch * (_space_left + _space_stretch))
215                           / _sum_stretch;
216
217               // Item would be smaller than minimum size or larger than maximum
218               // size, so just keep bounded size and ignore stretch factor
219               if( target_size <= d.size || target_size >= max_size )
220               {
221                 d.done = true;
222                 _sum_stretch -= d.stretch;
223                 _space_stretch -= d.size;
224
225                 if( target_size >= max_size )
226                   target_size = max_size;
227                 else
228                   target_size = d.size;
229               }
230               else
231                 _space_stretch += target_size - d.size;
232             }
233             else
234             {
235               // Give space evenly to all remaining elements in this round
236               target_size = d.size + std::min(_space_left, space_per_element);
237
238               if( target_size >= max_size )
239               {
240                 d.done = true;
241                 target_size = max_size;
242               }
243             }
244
245             int old_size = d.size;
246             d.size = target_size;
247             _space_left -= d.size - old_size;
248           }
249
250           if( d.done )
251           {
252             _num_not_done -= 1;
253
254             if( _sum_stretch <= 0 && d.stretch > 0 )
255               // Distribute remaining space evenly to all non-stretchable items
256               // in a new round
257               break;
258           }
259         }
260       }
261     }
262     else
263     {
264       _space_left = space.size - space.max_size;
265       int num_align = 0;
266       for(int i = 0; i < num_children; ++i)
267       {
268         if( !items[i].visible )
269           continue;
270
271         _num_not_done += 1;
272
273         if( items[i].has_align )
274           num_align += 1;
275       }
276
277       SG_LOG(
278         SG_GUI,
279         SG_DEBUG,
280         "Distributing excess space:"
281              " not_done=" << _num_not_done
282          << ", num_align=" << num_align
283          << ", space_left=" << _space_left
284       );
285
286       for(int i = 0; i < num_children; ++i)
287       {
288         ItemData& d = items[i];
289         if( !d.visible )
290           continue;
291
292         d.padding = d.padding_orig;
293         d.size = d.max_size;
294
295         int space_add = 0;
296
297         if( d.has_align )
298         {
299           // Equally distribute superfluous space and let each child items
300           // alignment handle the exact usage.
301           space_add = _space_left / num_align;
302           num_align -= 1;
303
304           d.size += space_add;
305         }
306         else if( num_align <= 0 )
307         {
308           // Add superfluous space as padding
309           space_add = _space_left
310                     // Padding after last child...
311                     / (_num_not_done + 1);
312           _num_not_done -= 1;
313
314           d.padding += space_add;
315         }
316
317         _space_left -= space_add;
318       }
319     }
320
321     SG_LOG(SG_GUI, SG_DEBUG, "distribute:");
322     for(int i = 0; i < num_children; ++i)
323     {
324       ItemData const& d = items[i];
325       if( d.visible )
326         SG_LOG(SG_GUI, SG_DEBUG, i << ") pad=" << d.padding
327                                    << ", size= " << d.size);
328       else
329         SG_LOG(SG_GUI, SG_DEBUG, i << ") [hidden]");
330     }
331   }
332
333 } // namespace canvas
334 } // namespace simgear