mirror of
https://github.com/RGBCube/serenity
synced 2025-05-19 00:15:08 +00:00
LibGUI: Rewrite layout system in terms of min and max sizes
This patch removes size policies and preferred sizes, and replaces them with min-size and max-size for each widget. Box layout now works in 3 passes: 1) Set all items (widgets/spacers) to their min-size 2) Distribute remaining space evenly, respecting max-size 3) Place widgets one after the other, adding spacing in between I've also added convenience helpers for setting a fixed size (which is the same as setting min-size and max-size to the same value.) This significantly reduces the verbosity of widget layout and makes GML a bit more pleasant to write, too. :^)
This commit is contained in:
parent
b2bba5ce5c
commit
7dc5a3ead8
83 changed files with 444 additions and 957 deletions
|
@ -43,169 +43,124 @@ BoxLayout::BoxLayout(Orientation orientation)
|
|||
|
||||
void BoxLayout::run(Widget& widget)
|
||||
{
|
||||
bool should_log = false;
|
||||
#ifdef GBOXLAYOUT_DEBUG
|
||||
should_log = true;
|
||||
#endif
|
||||
if (should_log)
|
||||
dbgprintf("BoxLayout: running layout on %s{%p}, entry count: %zu\n", widget.class_name(), &widget, m_entries.size());
|
||||
|
||||
if (m_entries.is_empty())
|
||||
return;
|
||||
|
||||
Gfx::IntSize available_size = widget.size();
|
||||
int number_of_entries_with_fixed_size = 0;
|
||||
struct Item {
|
||||
Widget* widget { nullptr };
|
||||
int min_size { -1 };
|
||||
int max_size { -1 };
|
||||
int size { 0 };
|
||||
bool final { false };
|
||||
};
|
||||
|
||||
int number_of_visible_entries = 0;
|
||||
|
||||
if (should_log)
|
||||
dbgprintf("BoxLayout: Starting with available size: %s\n", available_size.to_string().characters());
|
||||
Vector<Item, 32> items;
|
||||
|
||||
for (size_t i = 0; i < m_entries.size(); ++i) {
|
||||
auto& entry = m_entries[i];
|
||||
if (entry.type == Entry::Type::Spacer) {
|
||||
++number_of_visible_entries;
|
||||
items.append(Item { nullptr, -1, -1 });
|
||||
continue;
|
||||
}
|
||||
if (!entry.widget)
|
||||
continue;
|
||||
|
||||
if (!entry.widget->is_visible())
|
||||
continue;
|
||||
++number_of_visible_entries;
|
||||
if (entry.widget && entry.widget->size_policy(orientation()) == SizePolicy::Fixed) {
|
||||
if (should_log) {
|
||||
dbgprintf("BoxLayout: Subtracting for fixed %s{%p}, size: %s\n", entry.widget->class_name(), entry.widget.ptr(), entry.widget->preferred_size().to_string().characters());
|
||||
dbgprintf("BoxLayout: Available size before: %s\n", available_size.to_string().characters());
|
||||
auto min_size = entry.widget->min_size();
|
||||
auto max_size = entry.widget->max_size();
|
||||
items.append(Item { entry.widget.ptr(), min_size.primary_size_for_orientation(orientation()), max_size.primary_size_for_orientation(orientation()) });
|
||||
}
|
||||
|
||||
if (items.is_empty())
|
||||
return;
|
||||
|
||||
int available_size = widget.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();
|
||||
|
||||
// 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;
|
||||
|
||||
if (item.min_size >= 0 && item.max_size >= 0 && item.min_size == item.max_size) {
|
||||
// Fixed-size items finish immediately in the first pass.
|
||||
item.final = true;
|
||||
--unfinished_items;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
available_size = 0;
|
||||
|
||||
for (auto& item : items) {
|
||||
if (item.final)
|
||||
continue;
|
||||
|
||||
int item_size_with_full_slice = item.size + slice;
|
||||
item.size = item_size_with_full_slice;
|
||||
|
||||
if (item.max_size >= 0)
|
||||
item.size = min(item.max_size, item_size_with_full_slice);
|
||||
|
||||
// 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.
|
||||
item.final = true;
|
||||
--unfinished_items;
|
||||
}
|
||||
available_size -= entry.widget->preferred_size();
|
||||
if (should_log)
|
||||
dbgprintf("BoxLayout: Available size after: %s\n", available_size.to_string().characters());
|
||||
++number_of_entries_with_fixed_size;
|
||||
}
|
||||
available_size -= { spacing(), spacing() };
|
||||
}
|
||||
|
||||
available_size += { spacing(), spacing() };
|
||||
|
||||
available_size -= { margins().left() + margins().right(), margins().top() + margins().bottom() };
|
||||
|
||||
if (should_log)
|
||||
dbgprintf("BoxLayout: Number of visible: %d/%zu\n", number_of_visible_entries, m_entries.size());
|
||||
|
||||
int number_of_entries_with_automatic_size = number_of_visible_entries - number_of_entries_with_fixed_size;
|
||||
|
||||
if (should_log)
|
||||
dbgprintf("BoxLayout: available_size=%s, fixed=%d, fill=%d\n", available_size.to_string().characters(), number_of_entries_with_fixed_size, number_of_entries_with_automatic_size);
|
||||
|
||||
Gfx::IntSize automatic_size;
|
||||
|
||||
int remaining_size = 0;
|
||||
int number_of_entries_with_automatic_size_remaining = number_of_entries_with_automatic_size;
|
||||
|
||||
if (number_of_entries_with_automatic_size) {
|
||||
if (m_orientation == Orientation::Horizontal) {
|
||||
automatic_size.set_width(available_size.width() / number_of_entries_with_automatic_size);
|
||||
automatic_size.set_height(widget.height());
|
||||
|
||||
remaining_size = available_size.width();
|
||||
} else {
|
||||
automatic_size.set_width(widget.width());
|
||||
automatic_size.set_height(available_size.height() / number_of_entries_with_automatic_size);
|
||||
|
||||
remaining_size = available_size.height();
|
||||
}
|
||||
}
|
||||
|
||||
if (should_log)
|
||||
dbgprintf("BoxLayout: automatic_size=%s\n", automatic_size.to_string().characters());
|
||||
|
||||
// Pass 3: Place the widgets.
|
||||
int current_x = margins().left();
|
||||
int current_y = margins().top();
|
||||
|
||||
for (size_t i = 0; i < m_entries.size(); ++i) {
|
||||
auto& entry = m_entries[i];
|
||||
if (entry.type == Entry::Type::Spacer) {
|
||||
current_x += automatic_size.width();
|
||||
current_y += automatic_size.height();
|
||||
continue;
|
||||
}
|
||||
for (auto& item : items) {
|
||||
Gfx::IntRect rect { current_x, current_y, 0, 0 };
|
||||
|
||||
if (!entry.widget)
|
||||
continue;
|
||||
if (!entry.widget->is_visible())
|
||||
continue;
|
||||
Gfx::IntRect rect(current_x, current_y, 0, 0);
|
||||
if (entry.layout) {
|
||||
// FIXME: Implement recursive layout.
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
ASSERT(entry.widget);
|
||||
rect.set_primary_size_for_orientation(orientation(), item.size);
|
||||
|
||||
if (entry.widget->size_policy(Orientation::Vertical) == SizePolicy::Fixed) {
|
||||
rect.set_width(widget.width());
|
||||
rect.set_height(entry.widget->preferred_size().height());
|
||||
} else {
|
||||
if (orientation() == Orientation::Horizontal)
|
||||
rect.set_height(widget.height());
|
||||
if (item.widget) {
|
||||
int secondary = widget.size().secondary_size_for_orientation(orientation());
|
||||
if (orientation() == Gfx::Orientation::Horizontal)
|
||||
secondary -= margins().top() + margins().bottom();
|
||||
else
|
||||
rect.set_height(remaining_size / number_of_entries_with_automatic_size_remaining);
|
||||
}
|
||||
secondary -= margins().left() + margins().right();
|
||||
|
||||
if (entry.widget->size_policy(Orientation::Horizontal) == SizePolicy::Fixed) {
|
||||
rect.set_width(entry.widget->preferred_size().width());
|
||||
rect.set_height(widget.height());
|
||||
} else {
|
||||
if (orientation() == Orientation::Horizontal)
|
||||
rect.set_width(remaining_size / number_of_entries_with_automatic_size_remaining);
|
||||
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);
|
||||
|
||||
rect.set_secondary_size_for_orientation(orientation(), secondary);
|
||||
|
||||
if (orientation() == Gfx::Orientation::Horizontal)
|
||||
rect.center_vertically_within(widget.rect());
|
||||
else
|
||||
rect.set_width(widget.width());
|
||||
rect.center_horizontally_within(widget.rect());
|
||||
|
||||
item.widget->set_relative_rect(rect);
|
||||
}
|
||||
|
||||
if (orientation() == Orientation::Horizontal) {
|
||||
if (entry.widget->size_policy(Orientation::Vertical) == SizePolicy::Fill)
|
||||
rect.set_height(widget.height() - margins().top() - margins().bottom());
|
||||
} else {
|
||||
if (entry.widget->size_policy(Orientation::Horizontal) == SizePolicy::Fill)
|
||||
rect.set_width(widget.width() - margins().left() - margins().right());
|
||||
}
|
||||
|
||||
// Apply min/max constraints to filled widgets.
|
||||
if (entry.widget->size_policy(Orientation::Horizontal) == SizePolicy::Fill) {
|
||||
if (entry.widget->min_size().width() >= 0)
|
||||
rect.set_width(max(entry.widget->min_size().width(), rect.width()));
|
||||
if (entry.widget->max_size().width() >= 0)
|
||||
rect.set_width(min(entry.widget->max_size().width(), rect.width()));
|
||||
}
|
||||
if (entry.widget->size_policy(Orientation::Vertical) == SizePolicy::Fill) {
|
||||
if (entry.widget->min_size().height() >= 0)
|
||||
rect.set_height(max(entry.widget->min_size().height(), rect.height()));
|
||||
if (entry.widget->max_size().height() >= 0)
|
||||
rect.set_height(min(entry.widget->max_size().height(), rect.height()));
|
||||
}
|
||||
|
||||
if (orientation() == Orientation::Horizontal)
|
||||
rect.center_vertically_within(widget.rect());
|
||||
else
|
||||
rect.center_horizontally_within(widget.rect());
|
||||
|
||||
if (should_log)
|
||||
dbgprintf("BoxLayout: apply, %s{%p} <- %s\n", entry.widget->class_name(), entry.widget.ptr(), rect.to_string().characters());
|
||||
|
||||
entry.widget->set_relative_rect(rect);
|
||||
|
||||
if (orientation() == Orientation::Horizontal) {
|
||||
if (entry.widget->size_policy(Orientation::Horizontal) == SizePolicy::Fill) {
|
||||
remaining_size -= rect.width();
|
||||
--number_of_entries_with_automatic_size_remaining;
|
||||
}
|
||||
if (orientation() == Gfx::Orientation::Horizontal)
|
||||
current_x += rect.width() + spacing();
|
||||
} else {
|
||||
if (entry.widget->size_policy(Orientation::Vertical) == SizePolicy::Fill) {
|
||||
remaining_size -= rect.height();
|
||||
--number_of_entries_with_automatic_size_remaining;
|
||||
}
|
||||
else
|
||||
current_y += rect.height() + spacing();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue