ValueSlider.cpp 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. /*
  2. * Copyright (c) 2021, Marcus Nilsson <brainbomb@gmail.com>
  3. * Copyright (c) 2022, the SerenityOS developers.
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include <LibGUI/BoxLayout.h>
  8. #include <LibGUI/Painter.h>
  9. #include <LibGUI/TextBox.h>
  10. #include <LibGUI/ValueSlider.h>
  11. #include <LibGfx/Font/FontDatabase.h>
  12. #include <LibGfx/Palette.h>
  13. #include <LibGfx/StylePainter.h>
  14. REGISTER_WIDGET(GUI, ValueSlider)
  15. namespace GUI {
  16. ValueSlider::ValueSlider(Gfx::Orientation orientation, String suffix)
  17. : AbstractSlider(orientation)
  18. , m_suffix(move(suffix))
  19. {
  20. // FIXME: Implement vertical mode
  21. VERIFY(orientation == Orientation::Horizontal);
  22. set_preferred_size(SpecialDimension::Fit);
  23. m_textbox = add<GUI::TextBox>();
  24. m_textbox->set_relative_rect({ 0, 0, 34, 20 });
  25. m_textbox->set_font_fixed_width(true);
  26. m_textbox->set_font_size(8);
  27. m_textbox->on_change = [&]() {
  28. DeprecatedString value = m_textbox->text();
  29. if (value.ends_with(m_suffix, AK::CaseSensitivity::CaseInsensitive))
  30. value = value.substring_view(0, value.length() - m_suffix.bytes_as_string_view().length());
  31. auto integer_value = value.to_int();
  32. if (integer_value.has_value())
  33. AbstractSlider::set_value(integer_value.value());
  34. };
  35. m_textbox->on_return_pressed = [&]() {
  36. m_textbox->on_change();
  37. m_textbox->set_text(formatted_value());
  38. };
  39. m_textbox->on_up_pressed = [&]() {
  40. if (value() < max())
  41. AbstractSlider::increase_slider_by(1);
  42. m_textbox->set_text(formatted_value());
  43. };
  44. m_textbox->on_down_pressed = [&]() {
  45. if (value() > min())
  46. AbstractSlider::decrease_slider_by(1);
  47. m_textbox->set_text(formatted_value());
  48. };
  49. m_textbox->on_focusout = [&]() {
  50. m_textbox->on_return_pressed();
  51. };
  52. m_textbox->on_escape_pressed = [&]() {
  53. m_textbox->clear_selection();
  54. m_textbox->set_text(formatted_value());
  55. parent_widget()->set_focus(true);
  56. };
  57. }
  58. DeprecatedString ValueSlider::formatted_value() const
  59. {
  60. return DeprecatedString::formatted("{:2}{}", value(), m_suffix);
  61. }
  62. void ValueSlider::paint_event(PaintEvent& event)
  63. {
  64. GUI::Painter painter(*this);
  65. painter.add_clip_rect(event.rect());
  66. if (is_enabled())
  67. painter.fill_rect_with_gradient(m_orientation, bar_rect(), palette().active_window_border1(), palette().active_window_border2());
  68. else
  69. painter.fill_rect_with_gradient(m_orientation, bar_rect(), palette().inactive_window_border1(), palette().inactive_window_border2());
  70. auto unfilled_rect = bar_rect();
  71. unfilled_rect.set_left(knob_rect().right() - 1);
  72. painter.fill_rect(unfilled_rect, palette().base());
  73. Gfx::StylePainter::paint_frame(painter, bar_rect(), palette(), Gfx::FrameStyle::SunkenContainer);
  74. Gfx::StylePainter::paint_button(painter, knob_rect(), palette(), Gfx::ButtonStyle::Normal, false, m_hovered);
  75. auto paint_knurl = [&](int x, int y) {
  76. painter.set_pixel(x, y, palette().threed_shadow1());
  77. painter.set_pixel(x + 1, y, palette().threed_shadow1());
  78. painter.set_pixel(x, y + 1, palette().threed_shadow1());
  79. painter.set_pixel(x + 1, y + 1, palette().threed_highlight());
  80. };
  81. auto knurl_rect = knob_rect().shrunken(4, 8);
  82. if (m_knob_style == KnobStyle::Wide) {
  83. for (int i = 0; i < 4; ++i) {
  84. paint_knurl(knurl_rect.x(), knurl_rect.y() + (i * 3));
  85. paint_knurl(knurl_rect.x() + 3, knurl_rect.y() + (i * 3));
  86. paint_knurl(knurl_rect.x() + 6, knurl_rect.y() + (i * 3));
  87. }
  88. } else {
  89. for (int i = 0; i < 4; ++i)
  90. paint_knurl(knurl_rect.x(), knurl_rect.y() + (i * 3));
  91. }
  92. }
  93. Gfx::IntRect ValueSlider::bar_rect() const
  94. {
  95. auto bar_rect = rect();
  96. bar_rect.set_width(rect().width() - m_textbox->width());
  97. bar_rect.set_x(m_textbox->width());
  98. return bar_rect;
  99. }
  100. int ValueSlider::knob_length() const
  101. {
  102. return m_knob_style == KnobStyle::Wide ? 13 : 7;
  103. }
  104. Gfx::IntRect ValueSlider::knob_rect() const
  105. {
  106. int knob_thickness = knob_length();
  107. Gfx::IntRect knob_rect = bar_rect();
  108. knob_rect.set_width(knob_thickness);
  109. int knob_offset = (int)((float)bar_rect().left() + (float)(value() - min()) / (float)(max() - min()) * (float)(bar_rect().width() - knob_thickness));
  110. knob_rect.set_left(knob_offset);
  111. knob_rect.center_vertically_within(bar_rect());
  112. return knob_rect;
  113. }
  114. int ValueSlider::value_at(Gfx::IntPoint position) const
  115. {
  116. int knob_thickness = knob_length();
  117. float leftmost_knob_center = (float)bar_rect().left() + (float)knob_thickness / 2;
  118. if (position.x() < leftmost_knob_center)
  119. return min();
  120. float rightmost_knob_center = (float)(bar_rect().right() - 1) - (float)knob_thickness / 2;
  121. if (position.x() > rightmost_knob_center)
  122. return max();
  123. float relative_offset = (float)(position.x() - leftmost_knob_center) / (rightmost_knob_center - leftmost_knob_center);
  124. int range = max() - min();
  125. return min() + (int)roundf(relative_offset * (float)range);
  126. }
  127. void ValueSlider::set_value(int value, AllowCallback allow_callback, DoClamp do_clamp)
  128. {
  129. AbstractSlider::set_value(value, allow_callback, do_clamp);
  130. m_textbox->set_text(formatted_value());
  131. }
  132. void ValueSlider::leave_event(Core::Event&)
  133. {
  134. if (!m_hovered)
  135. return;
  136. m_hovered = false;
  137. update(knob_rect());
  138. }
  139. void ValueSlider::mousewheel_event(MouseEvent& event)
  140. {
  141. if (event.wheel_delta_y() < 0)
  142. increase_slider_by(1);
  143. else
  144. decrease_slider_by(1);
  145. }
  146. void ValueSlider::mousemove_event(MouseEvent& event)
  147. {
  148. bool is_hovered = knob_rect().contains(event.position());
  149. if (is_hovered != m_hovered) {
  150. m_hovered = is_hovered;
  151. update(knob_rect());
  152. }
  153. if (!m_dragging)
  154. return;
  155. set_value(value_at(event.position()));
  156. }
  157. void ValueSlider::mousedown_event(MouseEvent& event)
  158. {
  159. if (event.button() != MouseButton::Primary)
  160. return;
  161. m_textbox->set_focus(true);
  162. if (bar_rect().contains(event.position())) {
  163. m_dragging = true;
  164. set_value(value_at(event.position()));
  165. }
  166. }
  167. void ValueSlider::mouseup_event(MouseEvent& event)
  168. {
  169. if (event.button() != MouseButton::Primary)
  170. return;
  171. m_dragging = false;
  172. }
  173. Optional<UISize> ValueSlider::calculated_min_size() const
  174. {
  175. auto content_min_size = m_textbox->effective_min_size();
  176. if (orientation() == Gfx::Orientation::Vertical)
  177. return { { content_min_size.width(), content_min_size.height().as_int() + knob_length() } };
  178. return { { content_min_size.width().as_int() + knob_length(), content_min_size.height() } };
  179. }
  180. Optional<UISize> ValueSlider::calculated_preferred_size() const
  181. {
  182. if (orientation() == Gfx::Orientation::Vertical)
  183. return { { SpecialDimension::Shrink, SpecialDimension::OpportunisticGrow } };
  184. return { { SpecialDimension::OpportunisticGrow, SpecialDimension::Shrink } };
  185. }
  186. }