#include "../../include/mgui/scrollview.h" #include "../../include/mgui/mgui.h" #include "../../include/alloc.h" #include #include #include #include #include #include #define SCROLL_ACCEL 20.0f #define SCROLL_DEACCEL 10.0f #define WIDGET_NATURAL_SIZE INT_MAX static float max_float(float a, float b) { return a >= b ? a : b; } static int max_int(int a, int b) { return a >= b ? a : b; } static int min_int(int a, int b) { return a <= b ? a : b; } static float abs_float(float value) { return value >= 0.0f ? value : -value; } mgui_scrollview* mgui_scrollview_create() { mgui_scrollview *scrollview = mgui_alloc(sizeof(mgui_scrollview)); mgui_widget_init(&scrollview->widget, MGUI_WIDGET_SCROLLVIEW); scrollview->child = NULL; scrollview->position = (mgl_vec2i){ 0, 0 }; scrollview->scroll = (mgl_vec2i){ 0, 0 }; scrollview->mouse_scroll = (mgl_vec2f){ 0.0f, 0.0f }; return scrollview; } void mgui_scrollview_destroy(mgui_scrollview *scrollview) { if(scrollview->child) mgui_widget_destroy(scrollview->child); mgui_free(scrollview); } mgui_widget* mgui_scrollview_to_widget(mgui_scrollview *list) { return &list->widget; } mgui_scrollview* mgui_widget_to_scrollview(mgui_widget *widget) { assert(widget->type == MGUI_WIDGET_SCROLLVIEW); return (mgui_scrollview*)widget; } void mgui_scrollview_set_child(mgui_scrollview *self, mgui_widget *child) { assert(child != mgui_scrollview_to_widget(self)); self->child = child; if(self->child) { mgui_widget_set_has_parent(self->child); mgui_widget_calculate_size(self->child, self->widget.size); } } void mgui_scrollview_set_position(mgui_scrollview *self, mgl_vec2i position) { self->position = position; } void mgui_scrollview_calculate_size(mgui_scrollview *self, mgl_vec2i max_size) { self->widget.size = max_size; if(self->widget.size.x >= WIDGET_NATURAL_SIZE/2 || self->widget.size.y >= WIDGET_NATURAL_SIZE/2) { self->widget.size.x = 500; self->widget.size.y = 600; } /* TODO: this assumes child list uses vertical scroll. Make this work for horizontal scroll as well */ if(self->child) mgui_widget_calculate_size(self->child, (mgl_vec2i){ max_size.x, WIDGET_NATURAL_SIZE }); } void mgui_scrollview_on_event(mgui_scrollview *self, mgl_window *window, mgl_event *event) { /* TODO: Allow keyboard navigation if scrollview has focus */ if(event->type == MGL_EVENT_MOUSE_WHEEL_SCROLLED) self->mouse_scroll.y += (event->mouse_wheel_scroll.delta * SCROLL_ACCEL); if(self->child) mgui_widget_on_event(self->child, window, event); } /* TODO: Check if visible in scissor */ void mgui_scrollview_draw(mgui_scrollview *self, mgl_window *window) { const double frame_time = mgui_get_frame_time_seconds(); self->mouse_scroll.x *= max_float(0.0f, (1.0f - frame_time * SCROLL_DEACCEL)); self->mouse_scroll.y *= max_float(0.0f, (1.0f - frame_time * SCROLL_DEACCEL)); if(abs_float(self->mouse_scroll.x) < 0.0001f) self->mouse_scroll.x = 0.0f; if(abs_float(self->mouse_scroll.y) < 0.0001f) self->mouse_scroll.y = 0.0f; self->scroll.x += (int)self->mouse_scroll.x; self->scroll.y += (int)self->mouse_scroll.y; mgl_vec2i child_size; if(self->child) child_size = self->child->size; else child_size = (mgl_vec2i){ 0, 0 }; if(child_size.x > self->widget.size.x) { self->scroll.x = min_int(self->scroll.x, 0); self->scroll.x = max_int(self->scroll.x, -child_size.x + self->widget.size.x); } else { self->scroll.x = 0; } if(child_size.y > self->widget.size.y) { self->scroll.y = min_int(self->scroll.y, 0); self->scroll.y = max_int(self->scroll.y, -child_size.y + self->widget.size.y); } else { self->scroll.y = 0; } if(self->child) { mgl_scissor prev_scissor; mgl_window_get_scissor(window, &prev_scissor); /* TODO: Fix all this margin crap, it should be invisible */ const int margin_width = self->widget.margin.top + self->widget.margin.bottom; const int margin_height = self->widget.margin.top + self->widget.margin.bottom; mgl_scissor new_scissor = { .position = self->position, .size = (mgl_vec2i){ self->widget.size.x - margin_width, self->widget.size.y - margin_height } }; mgl_window_set_scissor(window, &new_scissor); mgui_widget_set_position(self->child, (mgl_vec2i){ self->position.x + self->scroll.x + self->child->margin.left, self->position.y + self->scroll.y + self->child->margin.top }); mgui_widget_draw(self->child, window); mgl_window_set_scissor(window, &prev_scissor); { /* TODO: Fix all this margin crap, it should be invisible */ const int margin_height = self->widget.margin.top + self->widget.margin.bottom; const int height = self->widget.size.y - margin_height; const double scrollbar_height_ratio = child_size.y == 0 ? 0.0 : (double)height / (double)child_size.y; const int scrollbar_height = scrollbar_height_ratio * height; const double scroll_ratio = child_size.y == 0 ? 0.0 : (double)-self->scroll.y / (double)child_size.y; const int scrollbar_offset_y = height * scroll_ratio; const int scrollbar_width = 5; const int right = self->widget.size.x - self->widget.margin.left - self->widget.margin.right; mgl_rectangle rect = { .position = { self->position.x + right - scrollbar_width, self->position.y + scrollbar_offset_y }, .size = { scrollbar_width, scrollbar_height }, .color = { 55, 60, 68, 255 } }; mgl_rectangle_draw(mgl_get_context(), &rect); } } }