Serenity Operating System
at hosted 184 lines 7.5 kB view raw
1/* 2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright notice, this 9 * list of conditions and the following disclaimer. 10 * 11 * 2. Redistributions in binary form must reproduce the above copyright notice, 12 * this list of conditions and the following disclaimer in the documentation 13 * and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27#include <AK/JsonObject.h> 28#include <LibGUI/BoxLayout.h> 29#include <LibGUI/Widget.h> 30#include <LibGfx/Orientation.h> 31#include <stdio.h> 32 33//#define GBOXLAYOUT_DEBUG 34 35namespace GUI { 36 37BoxLayout::BoxLayout(Orientation orientation) 38 : m_orientation(orientation) 39{ 40} 41 42void BoxLayout::run(Widget& widget) 43{ 44 bool should_log = false; 45#ifdef GBOXLAYOUT_DEBUG 46 should_log = true; 47#endif 48 if (should_log) 49 dbgprintf("BoxLayout: running layout on %s{%p}, entry count: %zu\n", widget.class_name(), &widget, m_entries.size()); 50 51 if (m_entries.is_empty()) 52 return; 53 54 Gfx::Size available_size = widget.size(); 55 int number_of_entries_with_fixed_size = 0; 56 57 int number_of_visible_entries = 0; 58 59 if (should_log) 60 dbgprintf("BoxLayout: Starting with available size: %s\n", available_size.to_string().characters()); 61 62 Optional<size_t> last_entry_with_automatic_size; 63 64 for (size_t i = 0; i < m_entries.size(); ++i) { 65 auto& entry = m_entries[i]; 66 if (entry.type == Entry::Type::Spacer) { 67 ++number_of_visible_entries; 68 } 69 if (!entry.widget) 70 continue; 71 72 if (!entry.widget->is_visible()) 73 continue; 74 ++number_of_visible_entries; 75 if (entry.widget && entry.widget->size_policy(orientation()) == SizePolicy::Fixed) { 76 if (should_log) { 77 dbgprintf("BoxLayout: Subtracting for fixed %s{%p}, size: %s\n", entry.widget->class_name(), entry.widget.ptr(), entry.widget->preferred_size().to_string().characters()); 78 dbgprintf("BoxLayout: Available size before: %s\n", available_size.to_string().characters()); 79 } 80 available_size -= entry.widget->preferred_size(); 81 if (should_log) 82 dbgprintf("BoxLayout: Available size after: %s\n", available_size.to_string().characters()); 83 ++number_of_entries_with_fixed_size; 84 } else { 85 last_entry_with_automatic_size = i; 86 } 87 available_size -= { spacing(), spacing() }; 88 } 89 90 available_size += { spacing(), spacing() }; 91 92 available_size -= { margins().left() + margins().right(), margins().top() + margins().bottom() }; 93 94 if (should_log) 95 dbgprintf("BoxLayout: Number of visible: %d/%zu\n", number_of_visible_entries, m_entries.size()); 96 97 int number_of_entries_with_automatic_size = number_of_visible_entries - number_of_entries_with_fixed_size; 98 99 if (should_log) 100 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); 101 102 Gfx::Size automatic_size; 103 Gfx::Size automatic_size_for_last_entry; 104 105 if (number_of_entries_with_automatic_size) { 106 if (m_orientation == Orientation::Horizontal) { 107 automatic_size.set_width(available_size.width() / number_of_entries_with_automatic_size); 108 automatic_size.set_height(widget.height()); 109 110 automatic_size_for_last_entry.set_width(available_size.width() - (number_of_entries_with_automatic_size - 1) * automatic_size.width()); 111 automatic_size_for_last_entry.set_height(widget.height()); 112 } else { 113 automatic_size.set_width(widget.width()); 114 automatic_size.set_height(available_size.height() / number_of_entries_with_automatic_size); 115 116 automatic_size_for_last_entry.set_width(widget.width()); 117 automatic_size_for_last_entry.set_height(available_size.height() - (number_of_entries_with_automatic_size - 1) * automatic_size.height()); 118 } 119 } 120 121 if (should_log) 122 dbgprintf("BoxLayout: automatic_size=%s\n", automatic_size.to_string().characters()); 123 124 int current_x = margins().left(); 125 int current_y = margins().top(); 126 127 for (size_t i = 0; i < m_entries.size(); ++i) { 128 auto& entry = m_entries[i]; 129 if (entry.type == Entry::Type::Spacer) { 130 current_x += automatic_size.width(); 131 current_y += automatic_size.height(); 132 } 133 134 if (!entry.widget) 135 continue; 136 if (!entry.widget->is_visible()) 137 continue; 138 Gfx::Rect rect(current_x, current_y, 0, 0); 139 if (entry.layout) { 140 // FIXME: Implement recursive layout. 141 ASSERT_NOT_REACHED(); 142 } 143 ASSERT(entry.widget); 144 145 if (last_entry_with_automatic_size.has_value() && i == last_entry_with_automatic_size.value()) { 146 rect.set_size(automatic_size_for_last_entry); 147 } else { 148 rect.set_size(automatic_size); 149 } 150 151 if (entry.widget->size_policy(Orientation::Vertical) == SizePolicy::Fixed) 152 rect.set_height(entry.widget->preferred_size().height()); 153 154 if (entry.widget->size_policy(Orientation::Horizontal) == SizePolicy::Fixed) 155 rect.set_width(entry.widget->preferred_size().width()); 156 157 if (orientation() == Orientation::Horizontal) { 158 if (entry.widget->size_policy(Orientation::Vertical) == SizePolicy::Fill) 159 rect.set_height(widget.height() - margins().top() - margins().bottom()); 160 rect.center_vertically_within(widget.rect()); 161 } else { 162 if (entry.widget->size_policy(Orientation::Horizontal) == SizePolicy::Fill) 163 rect.set_width(widget.width() - margins().left() - margins().right()); 164 rect.center_horizontally_within(widget.rect()); 165 } 166 167 if (should_log) 168 dbgprintf("BoxLayout: apply, %s{%p} <- %s\n", entry.widget->class_name(), entry.widget.ptr(), rect.to_string().characters()); 169 entry.widget->set_relative_rect(rect); 170 171 if (orientation() == Orientation::Horizontal) 172 current_x += rect.width() + spacing(); 173 else 174 current_y += rect.height() + spacing(); 175 } 176} 177 178void BoxLayout::save_to(JsonObject& json) 179{ 180 Layout::save_to(json); 181 json.set("orientation", m_orientation == Gfx::Orientation::Vertical ? "Vertical" : "Horizontal"); 182} 183 184}