1/*
2 * Copyright (C) 2020-2022 The opuntiaOS Project Authors.
3 * + Contributed by Nikita Melekhin <nimelehin@gmail.com>
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
9#include <libg/Color.h>
10#include <libui/Context.h>
11#include <libui/ScrollView.h>
12#include <utility>
13
14namespace UI {
15
16ScrollView::ScrollView(View* superview, const LG::Rect& frame)
17 : View(superview, frame)
18{
19}
20
21ScrollView::ScrollView(View* superview, Window* window, const LG::Rect& frame)
22 : View(superview, window, frame)
23{
24}
25
26void ScrollView::display(const LG::Rect& rect)
27{
28 LG::Context ctx = graphics_current_context();
29 ctx.add_clip(rect);
30
31 display_scroll_indicators(ctx);
32}
33
34void ScrollView::did_scroll(int n_x, int n_y)
35{
36 int x = content_offset().x();
37 int y = content_offset().y();
38 int max_x = std::max(0, (int)content_size().width() - (int)bounds().width());
39 int max_y = std::max(0, (int)content_size().height() - (int)bounds().height());
40 content_offset().set_x(std::max(0, std::min(x + n_x, max_x)));
41 content_offset().set_y(std::max(0, std::min(y + n_y, max_y)));
42 set_needs_display();
43}
44
45void ScrollView::mouse_wheel_event(int wheel_data)
46{
47 did_scroll(0, wheel_data * 10);
48}
49
50std::optional<LG::Point<int>> ScrollView::subview_location(const View& subview) const
51{
52 auto frame_origin = subview.frame().origin();
53 frame_origin.offset_by(-m_content_offset);
54 return frame_origin;
55}
56
57std::optional<View*> ScrollView::subview_at(const LG::Point<int>& point) const
58{
59 for (int i = subviews().size() - 1; i >= 0; --i) {
60 auto frame = subviews()[i]->frame();
61 frame.offset_by(-m_content_offset);
62 if (frame.contains(point)) {
63 return subviews()[i];
64 }
65 }
66 return {};
67}
68
69void ScrollView::receive_mouse_move_event(MouseEvent& event)
70{
71 auto location = LG::Point<int>(event.x(), event.y());
72 if (!is_hovered()) {
73 mouse_entered(location);
74 }
75
76 foreach_subview([&](View& subview) -> bool {
77 auto frame = subview.frame();
78 frame.offset_by(-m_content_offset);
79 bool event_hits_subview = frame.contains(event.x(), event.y());
80 if (subview.is_hovered() && !event_hits_subview) {
81 LG::Point<int> point(event.x(), event.y());
82 point.offset_by(-frame.origin());
83 MouseLeaveEvent mle(point.x(), point.y());
84 subview.receive_mouse_leave_event(mle);
85 } else if (event_hits_subview) {
86 LG::Point<int> point(event.x(), event.y());
87 point.offset_by(-frame.origin());
88 MouseEvent me(point.x(), point.y());
89 subview.receive_mouse_move_event(me);
90 }
91 return true;
92 });
93
94 mouse_moved(location);
95 Responder::receive_mouse_move_event(event);
96}
97
98void ScrollView::receive_display_event(DisplayEvent& event)
99{
100 event.bounds().intersect(bounds());
101 display(event.bounds());
102 foreach_subview([&](View& subview) -> bool {
103 auto bounds = event.bounds();
104 auto frame = subview.frame();
105 frame.offset_by(-m_content_offset);
106 bounds.intersect(frame);
107 if (!bounds.empty()) {
108 graphics_push_context(Context(subview, frame, Context::RelativeToCurrentContext::Yes));
109 bounds.origin().offset_by(-frame.origin());
110 DisplayEvent own_event(bounds);
111 subview.receive_display_event(own_event);
112 graphics_pop_context();
113 }
114 return true;
115 });
116 did_display(event.bounds());
117
118 if (!has_superview()) {
119 // Only superview sends invalidate_message to server.
120 bool success = send_invalidate_message_to_server(event.bounds());
121 }
122
123 Responder::receive_display_event(event);
124}
125
126void ScrollView::recalc_content_props()
127{
128 int max_width = 0;
129 int max_height = 0;
130
131 for (auto* view : subviews()) {
132 max_width = std::max(max_width, view->frame().max_x());
133 max_height = std::max(max_height, view->frame().max_y());
134 }
135
136 m_content_size.set_width(max_width);
137 m_content_size.set_height(max_height);
138}
139
140void ScrollView::display_scroll_indicators(LG::Context& ctx)
141{
142 float ratio = (float)bounds().height() / (float)content_size().height();
143 int line_height = bounds().height() * ratio;
144 int start_y = content_offset().y() * ratio;
145 int start_x = bounds().max_x() - 6;
146 ctx.set_fill_color(LG::Color(30, 30, 30, 100));
147 ctx.fill(LG::Rect(start_x, start_y, 4, line_height));
148}
149
150} // namespace UI