mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 17:52:45 +00:00 
			
		
		
		
	LibGUI: Use new layout system for BoxLayout run
This commit is contained in:
		
							parent
							
								
									34c96280d7
								
							
						
					
					
						commit
						b8aa0037a8
					
				
					 1 changed files with 167 additions and 46 deletions
				
			
		|  | @ -1,5 +1,6 @@ | |||
| /*
 | ||||
|  * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> | ||||
|  * Copyright (c) 2022, Frhun <serenitystuff@frhun.de> | ||||
|  * | ||||
|  * SPDX-License-Identifier: BSD-2-Clause | ||||
|  */ | ||||
|  | @ -142,94 +143,211 @@ void BoxLayout::run(Widget& widget) | |||
| 
 | ||||
|     struct Item { | ||||
|         Widget* widget { nullptr }; | ||||
|         int min_size { -1 }; | ||||
|         int max_size { -1 }; | ||||
|         UIDimension min_size { SpecialDimension::Shrink }; | ||||
|         UIDimension max_size { SpecialDimension::Grow }; | ||||
|         UIDimension preferred_size { SpecialDimension::Shrink }; | ||||
|         int size { 0 }; | ||||
|         bool final { false }; | ||||
|     }; | ||||
| 
 | ||||
|     Vector<Item, 32> items; | ||||
|     int opportunistic_growth_item_count = 0; | ||||
|     int opportunistic_growth_items_base_size_total = 0; | ||||
| 
 | ||||
|     for (size_t i = 0; i < m_entries.size(); ++i) { | ||||
|         auto& entry = m_entries[i]; | ||||
|         if (entry.type == Entry::Type::Spacer) { | ||||
|             items.append(Item { nullptr, -1, -1 }); | ||||
|             items.append(Item { nullptr, { SpecialDimension::Shrink }, { SpecialDimension::Grow }, { SpecialDimension::Grow } }); | ||||
|             continue; | ||||
|         } | ||||
|         if (!entry.widget) | ||||
|             continue; | ||||
|         if (!entry.widget->is_visible()) | ||||
|             continue; | ||||
|         auto min_size = entry.widget->min_size(); | ||||
|         auto max_size = entry.widget->max_size(); | ||||
|         auto min_size = entry.widget->effective_min_size().primary_size_for_orientation(orientation()); | ||||
|         auto max_size = entry.widget->max_size().primary_size_for_orientation(orientation()); | ||||
|         auto preferred_size = entry.widget->effective_preferred_size().primary_size_for_orientation(orientation()); | ||||
| 
 | ||||
|         if (entry.widget->is_shrink_to_fit() && entry.widget->layout()) { | ||||
|             auto preferred_size = entry.widget->layout()->preferred_size(); | ||||
|             min_size = max_size = preferred_size; | ||||
|         if (preferred_size == SpecialDimension::OpportunisticGrow) { | ||||
|             opportunistic_growth_item_count++; | ||||
|             opportunistic_growth_items_base_size_total += MUST(min_size.shrink_value()); | ||||
|         } else { | ||||
|             preferred_size = clamp(preferred_size, min_size, max_size); | ||||
|         } | ||||
| 
 | ||||
|         items.append(Item { entry.widget.ptr(), min_size.primary_size_for_orientation(orientation()), max_size.primary_size_for_orientation(orientation()) }); | ||||
|         items.append( | ||||
|             Item { | ||||
|                 entry.widget.ptr(), | ||||
|                 min_size, | ||||
|                 max_size, | ||||
|                 preferred_size }); | ||||
|     } | ||||
| 
 | ||||
|     if (items.is_empty()) | ||||
|         return; | ||||
| 
 | ||||
|     Gfx::IntRect content_rect = widget.content_rect(); | ||||
|     int available_size = content_rect.size().primary_size_for_orientation(orientation()) - spacing() * (items.size() - 1); | ||||
|     int unfinished_items = items.size(); | ||||
| 
 | ||||
|     if (orientation() == Gfx::Orientation::Horizontal) | ||||
|         available_size -= margins().left() + margins().right(); | ||||
|     else | ||||
|         available_size -= margins().top() + margins().bottom(); | ||||
|     int uncommitted_size = content_rect.size().primary_size_for_orientation(orientation()) | ||||
|         - spacing() * (items.size() - 1) | ||||
|         - margins().primary_total_for_orientation(orientation()); | ||||
|     int unfinished_regular_items = items.size() - opportunistic_growth_item_count; | ||||
|     int max_amongst_the_min_sizes = 0; | ||||
|     int max_amongst_the_min_sizes_of_opportunistically_growing_items = 0; | ||||
|     int regular_items_to_layout = 0; | ||||
|     int regular_items_min_size_total = 0; | ||||
| 
 | ||||
|     // Pass 1: Set all items to their minimum size.
 | ||||
|     for (auto& item : items) { | ||||
|         item.size = 0; | ||||
|         if (item.min_size >= 0) | ||||
|             item.size = item.min_size; | ||||
|         available_size -= item.size; | ||||
|         VERIFY(item.min_size.is_one_of(SpecialDimension::Regular, SpecialDimension::Shrink)); | ||||
|         item.size = MUST(item.min_size.shrink_value()); | ||||
|         uncommitted_size -= item.size; | ||||
| 
 | ||||
|         if (item.min_size >= 0 && item.max_size >= 0 && item.min_size == item.max_size) { | ||||
|         if (item.min_size.is_int() && item.max_size.is_int() && item.min_size == item.max_size) { | ||||
|             // Fixed-size items finish immediately in the first pass.
 | ||||
|             item.final = true; | ||||
|             --unfinished_items; | ||||
|             if (item.preferred_size == SpecialDimension::OpportunisticGrow) { | ||||
|                 opportunistic_growth_item_count--; | ||||
|                 opportunistic_growth_items_base_size_total -= MUST(item.min_size.shrink_value()); | ||||
|             } else { | ||||
|                 --unfinished_regular_items; | ||||
|             } | ||||
|         } else if (item.preferred_size != SpecialDimension::OpportunisticGrow && item.widget) { | ||||
|             max_amongst_the_min_sizes = max(max_amongst_the_min_sizes, MUST(item.min_size.shrink_value())); | ||||
|             regular_items_to_layout++; | ||||
|             regular_items_min_size_total += item.size; | ||||
|         } else if (item.preferred_size == SpecialDimension::OpportunisticGrow) { | ||||
|             max_amongst_the_min_sizes_of_opportunistically_growing_items = max(max_amongst_the_min_sizes_of_opportunistically_growing_items, MUST(item.min_size.shrink_value())); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // Pass 2: Distribute remaining available size evenly, respecting each item's maximum size.
 | ||||
|     while (unfinished_items && available_size > 0) { | ||||
|         int slice = available_size / unfinished_items; | ||||
|         // If available_size does not divide evenly by unfinished_items,
 | ||||
|     // Pass 2: Set all non final, non spacer items to the previously encountered maximum min_size of these kind of items
 | ||||
|     // This is done to ensure even growth, if the items don't have the same min_size, which most won't have.
 | ||||
|     // If you are unsure what effect this has, try looking at widget gallery with, and without this, it'll be obvious.
 | ||||
|     if (uncommitted_size > 0) { | ||||
|         int total_growth_if_not_overcommitted = regular_items_to_layout * max_amongst_the_min_sizes - regular_items_min_size_total; | ||||
|         int overcommitment_if_all_same_min_size = total_growth_if_not_overcommitted - uncommitted_size; | ||||
|         for (auto& item : items) { | ||||
|             if (item.final || item.preferred_size == SpecialDimension::OpportunisticGrow || !item.widget) | ||||
|                 continue; | ||||
|             int extra_needed_space = max_amongst_the_min_sizes - item.size; | ||||
| 
 | ||||
|             if (overcommitment_if_all_same_min_size > 0) { | ||||
|                 extra_needed_space -= (overcommitment_if_all_same_min_size * extra_needed_space + (total_growth_if_not_overcommitted - 1)) / (total_growth_if_not_overcommitted); | ||||
|             } | ||||
| 
 | ||||
|             VERIFY(extra_needed_space >= 0); | ||||
|             VERIFY(uncommitted_size >= extra_needed_space); | ||||
| 
 | ||||
|             item.size += extra_needed_space; | ||||
|             if (item.max_size.is_int() && item.size > item.max_size.as_int()) | ||||
|                 item.size = item.max_size.as_int(); | ||||
|             uncommitted_size -= item.size - MUST(item.min_size.shrink_value()); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // Pass 3: Determine final item size for non spacers, and non opportunisticially growing widgets
 | ||||
|     int loop_counter = 0; // This doubles as a safeguard for when the loop below doesn't finish for some reason, and as a mechanism to ensure it runs at least once.
 | ||||
|     // This has to run at least once, to handle the case where the loop for evening out the min sizes was in an overcommitted state,
 | ||||
|     // and gave the Widget a larger size than its preferred size.
 | ||||
|     while (unfinished_regular_items && (uncommitted_size > 0 || loop_counter++ == 0)) { | ||||
|         VERIFY(loop_counter < 100); | ||||
|         int slice = uncommitted_size / unfinished_regular_items; | ||||
|         // If uncommitted_size does not divide evenly by unfinished_regular_items,
 | ||||
|         // there are some extra pixels that have to be distributed.
 | ||||
|         int pixels = available_size - slice * unfinished_items; | ||||
|         available_size = 0; | ||||
|         int pixels = uncommitted_size - slice * unfinished_regular_items; | ||||
|         uncommitted_size = 0; | ||||
| 
 | ||||
|         for (auto& item : items) { | ||||
|             if (item.final) | ||||
|                 continue; | ||||
|             if (!item.widget) | ||||
|                 continue; | ||||
|             if (item.preferred_size == SpecialDimension::OpportunisticGrow) | ||||
|                 continue; | ||||
| 
 | ||||
|             int pixel = pixels ? 1 : 0; | ||||
|             pixels -= pixel; | ||||
|             int item_size_with_full_slice = item.size + slice + pixel; | ||||
|             item.size = item_size_with_full_slice; | ||||
| 
 | ||||
|             if (item.max_size >= 0) | ||||
|                 item.size = min(item.max_size, item_size_with_full_slice); | ||||
|             UIDimension resulting_size { 0 }; | ||||
|             resulting_size = max(item.size, item_size_with_full_slice); | ||||
|             resulting_size = min(resulting_size, item.preferred_size); | ||||
|             resulting_size = min(resulting_size, item.max_size); | ||||
| 
 | ||||
|             // If the slice was more than we needed, return remained to available_size.
 | ||||
|             int remainder_to_give_back = item_size_with_full_slice - item.size; | ||||
|             available_size += remainder_to_give_back; | ||||
| 
 | ||||
|             if (item.max_size >= 0 && item.size == item.max_size) { | ||||
|                 // We've hit the item's max size. Don't give it any more space.
 | ||||
|             if (resulting_size.is_shrink()) { | ||||
|                 // FIXME: Propagate this error, so it is obvious where the mistake is actually made.
 | ||||
|                 if (!item.min_size.is_int()) | ||||
|                     dbgln("BoxLayout: underconstrained widget set to zero size: {} {}", item.widget->class_name(), item.widget->name()); | ||||
|                 resulting_size = MUST(item.min_size.shrink_value()); | ||||
|                 item.final = true; | ||||
|                 --unfinished_items; | ||||
|             } | ||||
| 
 | ||||
|             if (resulting_size.is_grow()) | ||||
|                 resulting_size = item_size_with_full_slice; | ||||
| 
 | ||||
|             item.size = resulting_size.as_int(); | ||||
| 
 | ||||
|             // If the slice was more than we needed, return remainder to available_size.
 | ||||
|             // Note that this will in some cases even return more than the slice size.
 | ||||
|             uncommitted_size += item_size_with_full_slice - item.size; | ||||
| 
 | ||||
|             if (item.final | ||||
|                 || (item.max_size.is_int() && item.max_size.as_int() == item.size) | ||||
|                 || (item.preferred_size.is_int() && item.preferred_size.as_int() == item.size)) { | ||||
|                 item.final = true; | ||||
|                 --unfinished_regular_items; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // Pass 3: Place the widgets.
 | ||||
|     // Pass 4: Even out min_size for opportunistically growing items, analogous to pass 2
 | ||||
|     if (uncommitted_size > 0 && opportunistic_growth_item_count > 0) { | ||||
|         int total_growth_if_not_overcommitted = opportunistic_growth_item_count * max_amongst_the_min_sizes_of_opportunistically_growing_items - opportunistic_growth_items_base_size_total; | ||||
|         int overcommitment_if_all_same_min_size = total_growth_if_not_overcommitted - uncommitted_size; | ||||
|         for (auto& item : items) { | ||||
|             if (item.final || item.preferred_size != SpecialDimension::OpportunisticGrow || !item.widget) | ||||
|                 continue; | ||||
|             int extra_needed_space = max_amongst_the_min_sizes_of_opportunistically_growing_items - item.size; | ||||
| 
 | ||||
|             if (overcommitment_if_all_same_min_size > 0 && total_growth_if_not_overcommitted > 0) { | ||||
|                 extra_needed_space -= (overcommitment_if_all_same_min_size * extra_needed_space + (total_growth_if_not_overcommitted - 1)) / (total_growth_if_not_overcommitted); | ||||
|             } | ||||
| 
 | ||||
|             VERIFY(extra_needed_space >= 0); | ||||
|             VERIFY(uncommitted_size >= extra_needed_space); | ||||
| 
 | ||||
|             item.size += extra_needed_space; | ||||
|             if (item.max_size.is_int() && item.size > item.max_size.as_int()) | ||||
|                 item.size = item.max_size.as_int(); | ||||
|             uncommitted_size -= item.size - MUST(item.min_size.shrink_value()); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     loop_counter = 0; | ||||
|     // Pass 5: Determine the size for the opportunistically growing items.
 | ||||
|     while (opportunistic_growth_item_count > 0 && uncommitted_size > 0) { | ||||
|         VERIFY(loop_counter++ < 200); | ||||
|         int opportunistic_growth_item_extra_size = uncommitted_size / opportunistic_growth_item_count; | ||||
|         int pixels = uncommitted_size - opportunistic_growth_item_count * opportunistic_growth_item_extra_size; | ||||
|         VERIFY(pixels >= 0); | ||||
|         for (auto& item : items) { | ||||
|             if (item.preferred_size != SpecialDimension::OpportunisticGrow || item.final || !item.widget) | ||||
|                 continue; | ||||
| 
 | ||||
|             int pixel = (pixels > 0 ? 1 : 0); | ||||
|             pixels -= pixel; | ||||
|             int previous_size = item.size; | ||||
|             item.size += opportunistic_growth_item_extra_size + pixel; | ||||
|             if (item.max_size.is_int() && item.size >= item.max_size.as_int()) { | ||||
|                 item.size = item.max_size.as_int(); | ||||
|                 item.final = true; | ||||
|                 opportunistic_growth_item_count--; | ||||
|             } | ||||
|             uncommitted_size -= item.size - previous_size; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // Pass 6: Place the widgets.
 | ||||
|     int current_x = margins().left() + content_rect.x(); | ||||
|     int current_y = margins().top() + content_rect.y(); | ||||
| 
 | ||||
|  | @ -242,15 +360,18 @@ void BoxLayout::run(Widget& widget) | |||
| 
 | ||||
|         if (item.widget) { | ||||
|             int secondary = widget.content_size().secondary_size_for_orientation(orientation()); | ||||
| 
 | ||||
|             int min_secondary = item.widget->min_size().secondary_size_for_orientation(orientation()); | ||||
|             int max_secondary = item.widget->max_size().secondary_size_for_orientation(orientation()); | ||||
|             if (min_secondary >= 0) | ||||
|                 secondary = max(secondary, min_secondary); | ||||
|             if (max_secondary >= 0) | ||||
|                 secondary = min(secondary, max_secondary); | ||||
|             secondary -= margins().secondary_total_for_orientation(orientation()); | ||||
| 
 | ||||
|             UIDimension min_secondary = item.widget->effective_min_size().secondary_size_for_orientation(orientation()); | ||||
|             UIDimension max_secondary = item.widget->max_size().secondary_size_for_orientation(orientation()); | ||||
|             UIDimension preferred_secondary = item.widget->effective_preferred_size().secondary_size_for_orientation(orientation()); | ||||
|             if (preferred_secondary.is_int()) | ||||
|                 secondary = min(secondary, preferred_secondary.as_int()); | ||||
|             if (min_secondary.is_int()) | ||||
|                 secondary = max(secondary, min_secondary.as_int()); | ||||
|             if (max_secondary.is_int()) | ||||
|                 secondary = min(secondary, max_secondary.as_int()); | ||||
| 
 | ||||
|             rect.set_secondary_size_for_orientation(orientation(), secondary); | ||||
| 
 | ||||
|             if (orientation() == Gfx::Orientation::Horizontal) | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 FrHun
						FrHun