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