Serenity Operating System
at portability 382 lines 13 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 <LibCore/Timer.h> 28#include <LibGUI/Painter.h> 29#include <LibGUI/ScrollBar.h> 30#include <LibGfx/CharacterBitmap.h> 31#include <LibGfx/Palette.h> 32#include <LibGfx/StylePainter.h> 33 34namespace GUI { 35 36static const char* s_up_arrow_bitmap_data = { 37 " " 38 " # " 39 " ### " 40 " ##### " 41 " ####### " 42 " ### " 43 " ### " 44 " ### " 45 " " 46}; 47 48static const char* s_down_arrow_bitmap_data = { 49 " " 50 " ### " 51 " ### " 52 " ### " 53 " ####### " 54 " ##### " 55 " ### " 56 " # " 57 " " 58}; 59 60static const char* s_left_arrow_bitmap_data = { 61 " " 62 " # " 63 " ## " 64 " ###### " 65 " ####### " 66 " ###### " 67 " ## " 68 " # " 69 " " 70}; 71 72static const char* s_right_arrow_bitmap_data = { 73 " " 74 " # " 75 " ## " 76 " ###### " 77 " ####### " 78 " ###### " 79 " ## " 80 " # " 81 " " 82}; 83 84static Gfx::CharacterBitmap* s_up_arrow_bitmap; 85static Gfx::CharacterBitmap* s_down_arrow_bitmap; 86static Gfx::CharacterBitmap* s_left_arrow_bitmap; 87static Gfx::CharacterBitmap* s_right_arrow_bitmap; 88 89ScrollBar::ScrollBar(Orientation orientation) 90 : m_orientation(orientation) 91{ 92 m_automatic_scrolling_timer = add<Core::Timer>(); 93 if (!s_up_arrow_bitmap) 94 s_up_arrow_bitmap = &Gfx::CharacterBitmap::create_from_ascii(s_up_arrow_bitmap_data, 9, 9).leak_ref(); 95 if (!s_down_arrow_bitmap) 96 s_down_arrow_bitmap = &Gfx::CharacterBitmap::create_from_ascii(s_down_arrow_bitmap_data, 9, 9).leak_ref(); 97 if (!s_left_arrow_bitmap) 98 s_left_arrow_bitmap = &Gfx::CharacterBitmap::create_from_ascii(s_left_arrow_bitmap_data, 9, 9).leak_ref(); 99 if (!s_right_arrow_bitmap) 100 s_right_arrow_bitmap = &Gfx::CharacterBitmap::create_from_ascii(s_right_arrow_bitmap_data, 9, 9).leak_ref(); 101 102 if (m_orientation == Orientation::Vertical) { 103 set_preferred_size(15, 0); 104 } else { 105 set_preferred_size(0, 15); 106 } 107 108 m_automatic_scrolling_timer->set_interval(100); 109 m_automatic_scrolling_timer->on_timeout = [this] { 110 on_automatic_scrolling_timer_fired(); 111 }; 112} 113 114ScrollBar::~ScrollBar() 115{ 116} 117 118void ScrollBar::set_range(int min, int max) 119{ 120 ASSERT(min <= max); 121 if (m_min == min && m_max == max) 122 return; 123 124 m_min = min; 125 m_max = max; 126 127 int old_value = m_value; 128 m_value = clamp(m_value, m_min, m_max); 129 if (on_change && m_value != old_value) 130 on_change(m_value); 131 132 update(); 133} 134 135void ScrollBar::set_value(int value) 136{ 137 value = clamp(value, m_min, m_max); 138 if (value == m_value) 139 return; 140 m_value = value; 141 if (on_change) 142 on_change(value); 143 update(); 144} 145 146Gfx::Rect ScrollBar::decrement_button_rect() const 147{ 148 return { 0, 0, button_width(), button_height() }; 149} 150 151Gfx::Rect ScrollBar::increment_button_rect() const 152{ 153 if (orientation() == Orientation::Vertical) 154 return { 0, height() - button_height(), button_width(), button_height() }; 155 else 156 return { width() - button_width(), 0, button_width(), button_height() }; 157} 158 159Gfx::Rect ScrollBar::decrement_gutter_rect() const 160{ 161 if (orientation() == Orientation::Vertical) 162 return { 0, button_height(), button_width(), scrubber_rect().top() - button_height() }; 163 else 164 return { button_width(), 0, scrubber_rect().x() - button_width(), button_height() }; 165} 166 167Gfx::Rect ScrollBar::increment_gutter_rect() const 168{ 169 auto scrubber_rect = this->scrubber_rect(); 170 if (orientation() == Orientation::Vertical) 171 return { 0, scrubber_rect.bottom() + 1, button_width(), height() - button_height() - scrubber_rect.bottom() - 1 }; 172 else 173 return { scrubber_rect.right() + 1, 0, width() - button_width() - scrubber_rect.right() - 1, button_width() }; 174} 175 176int ScrollBar::scrubbable_range_in_pixels() const 177{ 178 if (orientation() == Orientation::Vertical) 179 return height() - button_height() * 2 - scrubber_size(); 180 else 181 return width() - button_width() * 2 - scrubber_size(); 182} 183 184bool ScrollBar::has_scrubber() const 185{ 186 return m_max != m_min; 187} 188 189int ScrollBar::scrubber_size() const 190{ 191 int pixel_range = length(orientation()) - button_size() * 2; 192 int value_range = m_max - m_min; 193 return ::max(pixel_range - value_range, button_size()); 194} 195 196Gfx::Rect ScrollBar::scrubber_rect() const 197{ 198 if (!has_scrubber() || length(orientation()) <= (button_size() * 2) + scrubber_size()) 199 return {}; 200 float x_or_y; 201 if (m_value == m_min) 202 x_or_y = button_size(); 203 else if (m_value == m_max) 204 x_or_y = (length(orientation()) - button_size() - scrubber_size()) + 1; 205 else { 206 float range_size = m_max - m_min; 207 float available = scrubbable_range_in_pixels(); 208 float step = available / range_size; 209 x_or_y = (button_size() + (step * m_value)); 210 } 211 212 if (orientation() == Orientation::Vertical) 213 return { 0, (int)x_or_y, button_width(), scrubber_size() }; 214 else 215 return { (int)x_or_y, 0, scrubber_size(), button_height() }; 216} 217 218void ScrollBar::paint_event(PaintEvent& event) 219{ 220 Painter painter(*this); 221 painter.add_clip_rect(event.rect()); 222 223 painter.fill_rect(rect(), palette().button().lightened()); 224 225 bool decrement_pressed = m_automatic_scrolling_direction == AutomaticScrollingDirection::Decrement; 226 bool increment_pressed = m_automatic_scrolling_direction == AutomaticScrollingDirection::Increment; 227 228 Gfx::StylePainter::paint_button(painter, decrement_button_rect(), palette(), Gfx::ButtonStyle::Normal, decrement_pressed, m_hovered_component == Component::DecrementButton); 229 Gfx::StylePainter::paint_button(painter, increment_button_rect(), palette(), Gfx::ButtonStyle::Normal, increment_pressed, m_hovered_component == Component::IncrementButton); 230 231 if (length(orientation()) > default_button_size()) { 232 auto decrement_location = decrement_button_rect().location().translated(3, 3); 233 if (decrement_pressed) 234 decrement_location.move_by(1, 1); 235 painter.draw_bitmap(decrement_location, orientation() == Orientation::Vertical ? *s_up_arrow_bitmap : *s_left_arrow_bitmap, has_scrubber() ? palette().button_text() : palette().threed_shadow1()); 236 237 auto increment_location = increment_button_rect().location().translated(3, 3); 238 if (increment_pressed) 239 increment_location.move_by(1, 1); 240 painter.draw_bitmap(increment_location, orientation() == Orientation::Vertical ? *s_down_arrow_bitmap : *s_right_arrow_bitmap, has_scrubber() ? palette().button_text() : palette().threed_shadow1()); 241 } 242 243 if (has_scrubber()) 244 Gfx::StylePainter::paint_button(painter, scrubber_rect(), palette(), Gfx::ButtonStyle::Normal, false, m_hovered_component == Component::Scrubber || m_scrubber_in_use); 245} 246 247void ScrollBar::on_automatic_scrolling_timer_fired() 248{ 249 if (m_automatic_scrolling_direction == AutomaticScrollingDirection::Decrement) { 250 set_value(value() - m_step); 251 return; 252 } 253 if (m_automatic_scrolling_direction == AutomaticScrollingDirection::Increment) { 254 set_value(value() + m_step); 255 return; 256 } 257} 258 259void ScrollBar::mousedown_event(MouseEvent& event) 260{ 261 if (event.button() != MouseButton::Left) 262 return; 263 if (!has_scrubber()) 264 return; 265 266 if (decrement_button_rect().contains(event.position())) { 267 m_automatic_scrolling_direction = AutomaticScrollingDirection::Decrement; 268 set_automatic_scrolling_active(true); 269 update(); 270 return; 271 } 272 if (increment_button_rect().contains(event.position())) { 273 m_automatic_scrolling_direction = AutomaticScrollingDirection::Increment; 274 set_automatic_scrolling_active(true); 275 update(); 276 return; 277 } 278 if (scrubber_rect().contains(event.position())) { 279 m_scrubber_in_use = true; 280 m_scrubbing = true; 281 m_scrub_start_value = value(); 282 m_scrub_origin = event.position(); 283 update(); 284 return; 285 } 286 287 float range_size = m_max - m_min; 288 float available = scrubbable_range_in_pixels(); 289 290 float x = ::max(0, event.position().x() - button_width() - button_width() / 2); 291 float y = ::max(0, event.position().y() - button_height() - button_height() / 2); 292 293 float rel_x = x / available; 294 float rel_y = y / available; 295 296 if (orientation() == Orientation::Vertical) 297 set_value(m_min + rel_y * range_size); 298 else 299 set_value(m_min + rel_x * range_size); 300 301 m_scrubbing = true; 302 m_scrub_start_value = value(); 303 m_scrub_origin = event.position(); 304} 305 306void ScrollBar::mouseup_event(MouseEvent& event) 307{ 308 if (event.button() != MouseButton::Left) 309 return; 310 m_scrubber_in_use = false; 311 m_automatic_scrolling_direction = AutomaticScrollingDirection::None; 312 set_automatic_scrolling_active(false); 313 m_scrubbing = false; 314 update(); 315} 316 317void ScrollBar::mousewheel_event(MouseEvent& event) 318{ 319 if (!is_scrollable()) 320 return; 321 set_value(value() + event.wheel_delta() * m_step); 322 Widget::mousewheel_event(event); 323} 324 325void ScrollBar::set_automatic_scrolling_active(bool active) 326{ 327 if (active) { 328 on_automatic_scrolling_timer_fired(); 329 m_automatic_scrolling_timer->start(); 330 } else { 331 m_automatic_scrolling_timer->stop(); 332 } 333} 334 335void ScrollBar::mousemove_event(MouseEvent& event) 336{ 337 auto old_hovered_component = m_hovered_component; 338 if (scrubber_rect().contains(event.position())) 339 m_hovered_component = Component::Scrubber; 340 else if (decrement_button_rect().contains(event.position())) 341 m_hovered_component = Component::DecrementButton; 342 else if (increment_button_rect().contains(event.position())) 343 m_hovered_component = Component::IncrementButton; 344 else if (rect().contains(event.position())) 345 m_hovered_component = Component::Gutter; 346 else 347 m_hovered_component = Component::Invalid; 348 if (old_hovered_component != m_hovered_component) { 349 update(); 350 351 if (m_automatic_scrolling_direction == AutomaticScrollingDirection::Decrement) 352 set_automatic_scrolling_active(m_hovered_component == Component::DecrementButton); 353 else if (m_automatic_scrolling_direction == AutomaticScrollingDirection::Increment) 354 set_automatic_scrolling_active(m_hovered_component == Component::IncrementButton); 355 } 356 if (!m_scrubbing) 357 return; 358 float delta = orientation() == Orientation::Vertical ? (event.y() - m_scrub_origin.y()) : (event.x() - m_scrub_origin.x()); 359 float scrubbable_range = scrubbable_range_in_pixels(); 360 float value_steps_per_scrubbed_pixel = (m_max - m_min) / scrubbable_range; 361 float new_value = m_scrub_start_value + (value_steps_per_scrubbed_pixel * delta); 362 set_value(new_value); 363} 364 365void ScrollBar::leave_event(Core::Event&) 366{ 367 if (m_hovered_component != Component::Invalid) { 368 m_hovered_component = Component::Invalid; 369 update(); 370 } 371} 372 373void ScrollBar::change_event(Event& event) 374{ 375 if (event.type() == Event::Type::EnabledChange) { 376 if (!is_enabled()) 377 m_scrubbing = false; 378 } 379 return Widget::change_event(event); 380} 381 382}