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