Serenity Operating System
at hosted 231 lines 8.9 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 <LibGUI/ScrollBar.h> 28#include <LibGUI/ScrollableWidget.h> 29 30namespace GUI { 31 32ScrollableWidget::ScrollableWidget() 33{ 34 m_vertical_scrollbar = add<ScrollBar>(Orientation::Vertical); 35 m_vertical_scrollbar->set_step(4); 36 m_vertical_scrollbar->on_change = [this](int) { 37 did_scroll(); 38 update(); 39 }; 40 41 m_horizontal_scrollbar = add<ScrollBar>(Orientation::Horizontal); 42 m_horizontal_scrollbar->set_step(4); 43 m_horizontal_scrollbar->set_big_step(30); 44 m_horizontal_scrollbar->on_change = [this](int) { 45 did_scroll(); 46 update(); 47 }; 48 49 m_corner_widget = add<Widget>(); 50 m_corner_widget->set_fill_with_background_color(true); 51} 52 53ScrollableWidget::~ScrollableWidget() 54{ 55} 56 57void ScrollableWidget::mousewheel_event(MouseEvent& event) 58{ 59 if (!m_scrollbars_enabled) { 60 event.ignore(); 61 return; 62 } 63 // FIXME: The wheel delta multiplier should probably come from... somewhere? 64 vertical_scrollbar().set_value(vertical_scrollbar().value() + event.wheel_delta() * 20); 65} 66 67void ScrollableWidget::custom_layout() 68{ 69 auto inner_rect = frame_inner_rect_for_size(size()); 70 int height_wanted_by_horizontal_scrollbar = m_horizontal_scrollbar->is_visible() ? m_horizontal_scrollbar->preferred_size().height() : 0; 71 int width_wanted_by_vertical_scrollbar = m_vertical_scrollbar->is_visible() ? m_vertical_scrollbar->preferred_size().width() : 0; 72 73 m_vertical_scrollbar->set_relative_rect( 74 inner_rect.right() + 1 - m_vertical_scrollbar->preferred_size().width(), 75 inner_rect.top(), 76 m_vertical_scrollbar->preferred_size().width(), 77 inner_rect.height() - height_wanted_by_horizontal_scrollbar); 78 79 m_horizontal_scrollbar->set_relative_rect( 80 inner_rect.left(), 81 inner_rect.bottom() + 1 - m_horizontal_scrollbar->preferred_size().height(), 82 inner_rect.width() - width_wanted_by_vertical_scrollbar, 83 m_horizontal_scrollbar->preferred_size().height()); 84 85 m_corner_widget->set_visible(m_vertical_scrollbar->is_visible() && m_horizontal_scrollbar->is_visible()); 86 if (m_corner_widget->is_visible()) { 87 Gfx::Rect corner_rect { m_horizontal_scrollbar->relative_rect().right() + 1, m_vertical_scrollbar->relative_rect().bottom() + 1, width_occupied_by_vertical_scrollbar(), height_occupied_by_horizontal_scrollbar() }; 88 m_corner_widget->set_relative_rect(corner_rect); 89 } 90} 91 92void ScrollableWidget::resize_event(ResizeEvent& event) 93{ 94 Frame::resize_event(event); 95 update_scrollbar_ranges(); 96} 97 98Gfx::Size ScrollableWidget::available_size() const 99{ 100 int available_width = frame_inner_rect().width() - m_size_occupied_by_fixed_elements.width() - width_occupied_by_vertical_scrollbar(); 101 int available_height = frame_inner_rect().height() - m_size_occupied_by_fixed_elements.height() - height_occupied_by_horizontal_scrollbar(); 102 return { available_width, available_height }; 103} 104 105void ScrollableWidget::update_scrollbar_ranges() 106{ 107 auto available_size = this->available_size(); 108 109 int excess_height = max(0, m_content_size.height() - available_size.height()); 110 m_vertical_scrollbar->set_range(0, excess_height); 111 112 if (should_hide_unnecessary_scrollbars()) 113 m_vertical_scrollbar->set_visible(excess_height > 0); 114 115 int excess_width = max(0, m_content_size.width() - available_size.width()); 116 m_horizontal_scrollbar->set_range(0, excess_width); 117 118 if (should_hide_unnecessary_scrollbars()) 119 m_horizontal_scrollbar->set_visible(excess_width > 0); 120 121 m_vertical_scrollbar->set_big_step(visible_content_rect().height() - m_vertical_scrollbar->step()); 122} 123 124void ScrollableWidget::set_content_size(const Gfx::Size& size) 125{ 126 if (m_content_size == size) 127 return; 128 m_content_size = size; 129 update_scrollbar_ranges(); 130} 131 132void ScrollableWidget::set_size_occupied_by_fixed_elements(const Gfx::Size& size) 133{ 134 if (m_size_occupied_by_fixed_elements == size) 135 return; 136 m_size_occupied_by_fixed_elements = size; 137 update_scrollbar_ranges(); 138} 139 140int ScrollableWidget::height_occupied_by_horizontal_scrollbar() const 141{ 142 return m_horizontal_scrollbar->is_visible() ? m_horizontal_scrollbar->height() : 0; 143} 144 145int ScrollableWidget::width_occupied_by_vertical_scrollbar() const 146{ 147 return m_vertical_scrollbar->is_visible() ? m_vertical_scrollbar->width() : 0; 148} 149 150Gfx::Rect ScrollableWidget::visible_content_rect() const 151{ 152 return { 153 m_horizontal_scrollbar->value(), 154 m_vertical_scrollbar->value(), 155 min(m_content_size.width(), frame_inner_rect().width() - width_occupied_by_vertical_scrollbar() - m_size_occupied_by_fixed_elements.width()), 156 min(m_content_size.height(), frame_inner_rect().height() - height_occupied_by_horizontal_scrollbar() - m_size_occupied_by_fixed_elements.height()) 157 }; 158} 159 160void ScrollableWidget::scroll_into_view(const Gfx::Rect& rect, Orientation orientation) 161{ 162 if (orientation == Orientation::Vertical) 163 return scroll_into_view(rect, false, true); 164 return scroll_into_view(rect, true, false); 165} 166 167void ScrollableWidget::scroll_into_view(const Gfx::Rect& rect, bool scroll_horizontally, bool scroll_vertically) 168{ 169 auto visible_content_rect = this->visible_content_rect(); 170 if (visible_content_rect.contains(rect)) 171 return; 172 173 if (scroll_vertically) { 174 if (rect.top() < visible_content_rect.top()) 175 m_vertical_scrollbar->set_value(rect.top()); 176 else if (rect.bottom() > visible_content_rect.bottom()) 177 m_vertical_scrollbar->set_value(rect.bottom() - visible_content_rect.height()); 178 } 179 if (scroll_horizontally) { 180 if (rect.left() < visible_content_rect.left()) 181 m_horizontal_scrollbar->set_value(rect.left()); 182 else if (rect.right() > visible_content_rect.right()) 183 m_horizontal_scrollbar->set_value(rect.right() - visible_content_rect.width()); 184 } 185} 186 187void ScrollableWidget::set_scrollbars_enabled(bool scrollbars_enabled) 188{ 189 if (m_scrollbars_enabled == scrollbars_enabled) 190 return; 191 m_scrollbars_enabled = scrollbars_enabled; 192 m_vertical_scrollbar->set_visible(m_scrollbars_enabled); 193 m_horizontal_scrollbar->set_visible(m_scrollbars_enabled); 194 m_corner_widget->set_visible(m_scrollbars_enabled); 195} 196 197void ScrollableWidget::scroll_to_top() 198{ 199 scroll_into_view({ 0, 0, 1, 1 }, Orientation::Vertical); 200} 201 202void ScrollableWidget::scroll_to_bottom() 203{ 204 scroll_into_view({ 0, content_height(), 1, 1 }, Orientation::Vertical); 205} 206 207Gfx::Rect ScrollableWidget::widget_inner_rect() const 208{ 209 auto rect = frame_inner_rect(); 210 rect.set_width(rect.width() - width_occupied_by_vertical_scrollbar()); 211 rect.set_height(rect.height() - height_occupied_by_horizontal_scrollbar()); 212 return rect; 213} 214 215Gfx::Point ScrollableWidget::to_content_position(const Gfx::Point& widget_position) const 216{ 217 auto content_position = widget_position; 218 content_position.move_by(horizontal_scrollbar().value(), vertical_scrollbar().value()); 219 content_position.move_by(-frame_thickness(), -frame_thickness()); 220 return content_position; 221} 222 223Gfx::Point ScrollableWidget::to_widget_position(const Gfx::Point& content_position) const 224{ 225 auto widget_position = content_position; 226 widget_position.move_by(-horizontal_scrollbar().value(), -vertical_scrollbar().value()); 227 widget_position.move_by(frame_thickness(), frame_thickness()); 228 return widget_position; 229} 230 231}