OpacitySlider.cpp 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. /*
  2. * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
  3. * Copyright (c) 2022, the SerenityOS developers.
  4. * Copyright (c) 2023, networkException <networkexception@serenityos.org>
  5. *
  6. * SPDX-License-Identifier: BSD-2-Clause
  7. */
  8. #include <LibGUI/OpacitySlider.h>
  9. #include <LibGUI/Painter.h>
  10. #include <LibGfx/Palette.h>
  11. #include <LibGfx/StylePainter.h>
  12. REGISTER_WIDGET(GUI, HorizontalOpacitySlider)
  13. REGISTER_WIDGET(GUI, VerticalOpacitySlider)
  14. namespace GUI {
  15. OpacitySlider::OpacitySlider(Gfx::Orientation orientation)
  16. : AbstractSlider(orientation)
  17. {
  18. set_min(0);
  19. set_max(100);
  20. set_value(100);
  21. set_preferred_size(SpecialDimension::Fit);
  22. }
  23. Gfx::IntRect OpacitySlider::frame_inner_rect() const
  24. {
  25. return rect().shrunken(4, 4);
  26. }
  27. void OpacitySlider::paint_event(PaintEvent& event)
  28. {
  29. GUI::Painter painter(*this);
  30. painter.add_clip_rect(event.rect());
  31. auto inner_rect = frame_inner_rect();
  32. // Grid pattern
  33. Gfx::StylePainter::paint_transparency_grid(painter, inner_rect, palette());
  34. // Alpha gradient
  35. painter.fill_rect_with_linear_gradient(inner_rect, Array { Gfx::ColorStop { Color::Transparent, 0 }, Gfx::ColorStop { m_base_color.with_alpha(255), 1 } },
  36. orientation() == Orientation::Horizontal ? 90.0f : 180.0f);
  37. constexpr int notch_size = 3;
  38. if (orientation() == Gfx::Orientation::Horizontal) {
  39. int notch_y_top = inner_rect.top() + notch_size;
  40. int notch_y_bottom = inner_rect.bottom() - 1 - notch_size;
  41. int notch_x = inner_rect.left() + ((float)value() / (float)max() * (float)inner_rect.width());
  42. // Top notch
  43. painter.set_pixel(notch_x, notch_y_top, palette().threed_shadow2());
  44. for (int i = notch_size; i >= 0; --i) {
  45. painter.set_pixel(notch_x - (i + 1), notch_y_top - i - 1, palette().threed_highlight());
  46. for (int j = 0; j < i * 2; ++j)
  47. painter.set_pixel(notch_x - (i + 1) + j + 1, notch_y_top - i - 1, palette().button());
  48. painter.set_pixel(notch_x + (i + 0), notch_y_top - i - 1, palette().threed_shadow1());
  49. painter.set_pixel(notch_x + (i + 1), notch_y_top - i - 1, palette().threed_shadow2());
  50. }
  51. // Bottom notch
  52. painter.set_pixel(notch_x, notch_y_bottom, palette().threed_shadow2());
  53. for (int i = 0; i < notch_size; ++i) {
  54. painter.set_pixel(notch_x - (i + 1), notch_y_bottom + i + 1, palette().threed_highlight());
  55. for (int j = 0; j < i * 2; ++j)
  56. painter.set_pixel(notch_x - (i + 1) + j + 1, notch_y_bottom + i + 1, palette().button());
  57. painter.set_pixel(notch_x + (i + 0), notch_y_bottom + i + 1, palette().threed_shadow1());
  58. painter.set_pixel(notch_x + (i + 1), notch_y_bottom + i + 1, palette().threed_shadow2());
  59. }
  60. // Hairline
  61. // NOTE: If we're in the whiter part of the gradient, the notch is painted as shadow between the notches.
  62. // If we're in the darker part, the notch is painted as highlight.
  63. // We adjust the hairline's x position so it lines up with the shadow/highlight of the notches.
  64. u8 h = ((float)value() / (float)max()) * 255.0f;
  65. if (h < 128)
  66. painter.draw_line({ notch_x, notch_y_top }, { notch_x, notch_y_bottom }, Color(h, h, h, 255));
  67. else
  68. painter.draw_line({ notch_x - 1, notch_y_top }, { notch_x - 1, notch_y_bottom }, Color(h, h, h, 255));
  69. } else {
  70. int notch_x_left = inner_rect.left() + notch_size;
  71. int notch_x_right = inner_rect.right() - 1 - notch_size;
  72. int notch_y = inner_rect.top() + ((float)value() / (float)max() * (float)inner_rect.height());
  73. // Left notch
  74. painter.set_pixel(notch_x_left, notch_y, palette().threed_shadow2());
  75. for (int i = notch_size; i >= 0; --i) {
  76. painter.set_pixel(notch_x_left - i - 1, notch_y - (i + 1), palette().threed_highlight());
  77. for (int j = 0; j < i * 2; ++j)
  78. painter.set_pixel(notch_x_left - i - 1, notch_y - (i + 1) + j + 1, palette().button());
  79. painter.set_pixel(notch_x_left - i - 1, notch_y + (i + 0), palette().threed_shadow1());
  80. painter.set_pixel(notch_x_left - i - 1, notch_y + (i + 1), palette().threed_shadow2());
  81. }
  82. // Bottom notch
  83. painter.set_pixel(notch_x_right, notch_y, palette().threed_shadow2());
  84. for (int i = 0; i < notch_size; ++i) {
  85. painter.set_pixel(notch_x_right + i + 1, notch_y - (i + 1), palette().threed_highlight());
  86. for (int j = 0; j < i * 2; ++j)
  87. painter.set_pixel(notch_x_right + i + 1, notch_y - (i + 1) + j + 1, palette().button());
  88. painter.set_pixel(notch_x_right + i + 1, notch_y + (i + 0), palette().threed_shadow1());
  89. painter.set_pixel(notch_x_right + i + 1, notch_y + (i + 1), palette().threed_shadow2());
  90. }
  91. // Hairline
  92. // NOTE: See above
  93. u8 h = ((float)value() / (float)max()) * 255.0f;
  94. if (h < 128)
  95. painter.draw_line({ notch_x_left, notch_y }, { notch_x_right, notch_y }, Color(h, h, h, 255));
  96. else
  97. painter.draw_line({ notch_x_left, notch_y - 1 }, { notch_x_right, notch_y - 1 }, Color(h, h, h, 255));
  98. }
  99. // Text label
  100. // FIXME: better support text in vertical orientation, either by having a vertical option for draw_text, or only showing when there is enough space
  101. auto percent_text = DeprecatedString::formatted("{}%", (int)((float)value() / (float)max() * 100.0f));
  102. painter.draw_text(inner_rect.translated(1, 1), percent_text, Gfx::TextAlignment::Center, Color::Black);
  103. painter.draw_text(inner_rect, percent_text, Gfx::TextAlignment::Center, Color::White);
  104. // Frame
  105. Gfx::StylePainter::paint_frame(painter, rect(), palette(), Gfx::FrameStyle::SunkenContainer);
  106. }
  107. int OpacitySlider::value_at(Gfx::IntPoint position) const
  108. {
  109. auto inner_rect = frame_inner_rect();
  110. auto relevant_position = position.primary_offset_for_orientation(orientation()),
  111. begin_position = inner_rect.first_edge_for_orientation(orientation()),
  112. end_position = inner_rect.last_edge_for_orientation(orientation());
  113. if (relevant_position < begin_position)
  114. return min();
  115. if (relevant_position > end_position)
  116. return max();
  117. float relative_offset = (float)(relevant_position - begin_position) / (float)inner_rect.primary_size_for_orientation(orientation());
  118. int range = max() - min();
  119. return min() + (int)(relative_offset * (float)range);
  120. }
  121. void OpacitySlider::set_base_color(Gfx::Color base_color)
  122. {
  123. m_base_color = base_color;
  124. update();
  125. }
  126. void OpacitySlider::mousedown_event(MouseEvent& event)
  127. {
  128. if (event.button() == MouseButton::Primary) {
  129. m_dragging = true;
  130. set_value(value_at(event.position()));
  131. return;
  132. }
  133. AbstractSlider::mousedown_event(event);
  134. }
  135. void OpacitySlider::mousemove_event(MouseEvent& event)
  136. {
  137. if (m_dragging) {
  138. set_value(value_at(event.position()));
  139. return;
  140. }
  141. AbstractSlider::mousemove_event(event);
  142. }
  143. void OpacitySlider::mouseup_event(MouseEvent& event)
  144. {
  145. if (event.button() == MouseButton::Primary) {
  146. m_dragging = false;
  147. return;
  148. }
  149. AbstractSlider::mouseup_event(event);
  150. }
  151. void OpacitySlider::mousewheel_event(MouseEvent& event)
  152. {
  153. decrease_slider_by(event.wheel_delta_y());
  154. }
  155. Optional<UISize> OpacitySlider::calculated_min_size() const
  156. {
  157. if (orientation() == Gfx::Orientation::Vertical)
  158. return { { 33, 40 } };
  159. return { { 40, 22 } };
  160. }
  161. Optional<UISize> OpacitySlider::calculated_preferred_size() const
  162. {
  163. if (orientation() == Gfx::Orientation::Vertical)
  164. return { { SpecialDimension::Shrink, SpecialDimension::OpportunisticGrow } };
  165. return { { SpecialDimension::OpportunisticGrow, SpecialDimension::Shrink } };
  166. }
  167. }