]> git.mxchange.org Git - simgear.git/blob - simgear/canvas/layout/Layout.cxx
canvas::Layout: support height-for-width layouting.
[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::update()
29   {
30     if( !(_flags & (LAYOUT_DIRTY | SIZE_INFO_DIRTY)) )
31       return;
32
33     doLayout(_geometry);
34
35     _flags &= ~LAYOUT_DIRTY;
36   }
37
38   //----------------------------------------------------------------------------
39   void Layout::invalidate()
40   {
41     LayoutItem::invalidate();
42     _flags |= LAYOUT_DIRTY;
43   }
44
45   //----------------------------------------------------------------------------
46   void Layout::setGeometry(const SGRecti& geom)
47   {
48     if( geom == _geometry )
49       return;
50
51     _geometry = geom;
52     _flags |= LAYOUT_DIRTY;
53
54     update();
55   }
56
57   //----------------------------------------------------------------------------
58   void Layout::removeItem(const LayoutItemRef& item)
59   {
60     size_t i = 0;
61     while( LayoutItemRef child = itemAt(i) )
62     {
63       if( item == child )
64         return (void)takeAt(i);
65
66       ++i;
67     }
68   }
69
70   //----------------------------------------------------------------------------
71   void Layout::ItemData::reset()
72   {
73     layout_item = 0;
74     size_hint   = 0;
75     min_size    = 0;
76     max_size    = 0;
77     padding_orig= 0;
78     padding     = 0;
79     size        = 0;
80     stretch     = 0;
81     has_hfw     = false;
82     done        = false;
83   }
84
85   //----------------------------------------------------------------------------
86   int Layout::ItemData::hfw(int w) const
87   {
88     if( has_hfw )
89       return layout_item->heightForWidth(w);
90     else
91       return layout_item->sizeHint().y();
92   }
93
94   //----------------------------------------------------------------------------
95   int Layout::ItemData::mhfw(int w) const
96   {
97     if( has_hfw )
98       return layout_item->minimumHeightForWidth(w);
99     else
100       return layout_item->minimumSize().y();
101   }
102
103   //----------------------------------------------------------------------------
104   void Layout::safeAdd(int& a, int b)
105   {
106     if( SGLimits<int>::max() - b < a )
107       a = SGLimits<int>::max();
108     else
109       a += b;
110   }
111
112   //----------------------------------------------------------------------------
113   void Layout::distribute(std::vector<ItemData>& items, const ItemData& space)
114   {
115     const int num_children = static_cast<int>(items.size());
116     _num_not_done = num_children;
117
118     SG_LOG( SG_GUI,
119             SG_DEBUG,
120             "Layout::distribute(" << num_children << " items)" );
121
122     if( space.size < space.min_size )
123     {
124       // TODO
125       SG_LOG( SG_GUI, SG_WARN, "Layout: not enough size (not implemented)");
126     }
127     else if( space.size < space.max_size )
128     {
129       _sum_stretch = 0;
130       _space_stretch = 0;
131
132       bool less_then_hint = space.size < space.size_hint;
133
134       // Give min_size/size_hint to all items
135       _space_left = space.size
136                   - (less_then_hint ? space.min_size : space.size_hint);
137       for(int i = 0; i < num_children; ++i)
138       {
139         ItemData& d = items[i];
140         d.size = less_then_hint ? d.min_size : d.size_hint;
141         d.padding = d.padding_orig;
142         d.done = d.size >= (less_then_hint ? d.size_hint : d.max_size);
143
144         SG_LOG(
145           SG_GUI,
146           SG_DEBUG,
147           i << ") initial=" << d.size
148             << ", min=" << d.min_size
149             << ", hint=" << d.size_hint
150             << ", max=" << d.max_size
151         );
152
153         if( d.done )
154         {
155           _num_not_done -= 1;
156           continue;
157         }
158
159         if( d.stretch > 0 )
160         {
161           _sum_stretch += d.stretch;
162           _space_stretch += d.size;
163         }
164       }
165
166       // Distribute remaining space to increase the size of each item up to its
167       // size_hint/max_size
168       while( _space_left > 0 )
169       {
170         if( _num_not_done <= 0 )
171         {
172           SG_LOG(SG_GUI, SG_WARN, "space left, but no more items?");
173           break;
174         }
175
176         int space_per_element = std::max(1, _space_left / _num_not_done);
177
178         SG_LOG(SG_GUI, SG_DEBUG, "space/element=" << space_per_element);
179
180         for(int i = 0; i < num_children; ++i)
181         {
182           ItemData& d = items[i];
183
184           SG_LOG(
185             SG_GUI,
186             SG_DEBUG,
187             i << ") left=" << _space_left
188               << ", not_done=" << _num_not_done
189               << ", sum=" << _sum_stretch
190               << ", stretch=" << _space_stretch
191               << ", stretch/unit=" << _space_stretch / std::max(1, _sum_stretch)
192           );
193
194           if( d.done )
195             continue;
196
197           if( _sum_stretch > 0 && d.stretch <= 0 )
198             d.done = true;
199           else
200           {
201             int target_size = 0;
202             int max_size = less_then_hint ? d.size_hint : d.max_size;
203
204             if( _sum_stretch > 0 )
205             {
206               target_size = (d.stretch * (_space_left + _space_stretch))
207                           / _sum_stretch;
208
209               // Item would be smaller than minimum size or larger than maximum
210               // size, so just keep bounded size and ignore stretch factor
211               if( target_size <= d.size || target_size >= max_size )
212               {
213                 d.done = true;
214                 _sum_stretch -= d.stretch;
215                 _space_stretch -= d.size;
216
217                 if( target_size >= max_size )
218                   target_size = max_size;
219                 else
220                   target_size = d.size;
221               }
222               else
223                 _space_stretch += target_size - d.size;
224             }
225             else
226             {
227               // Give space evenly to all remaining elements in this round
228               target_size = d.size + std::min(_space_left, space_per_element);
229
230               if( target_size >= max_size )
231               {
232                 d.done = true;
233                 target_size = max_size;
234               }
235             }
236
237             int old_size = d.size;
238             d.size = target_size;
239             _space_left -= d.size - old_size;
240           }
241
242           if( d.done )
243           {
244             _num_not_done -= 1;
245
246             if( _sum_stretch <= 0 && d.stretch > 0 )
247               // Distribute remaining space evenly to all non-stretchable items
248               // in a new round
249               break;
250           }
251         }
252       }
253     }
254     else
255     {
256       _space_left = space.size - space.max_size;
257       for(int i = 0; i < num_children; ++i)
258       {
259         ItemData& d = items[i];
260         d.size = d.max_size;
261
262         // Add superfluous space as padding
263         d.padding = d.padding_orig + _space_left
264                                    // Padding after last child...
265                                    / (_num_not_done + 1);
266
267         _space_left -= d.padding - d.padding_orig;
268         _num_not_done -= 1;
269       }
270     }
271
272     SG_LOG(SG_GUI, SG_DEBUG, "distribute:");
273     for(int i = 0; i < num_children; ++i)
274     {
275       ItemData const& d = items[i];
276       SG_LOG( SG_GUI,
277               SG_DEBUG,
278               i << ") pad=" << d.padding
279                 << ", size = " << d.size );
280     }
281   }
282
283 } // namespace canvas
284 } // namespace simgear