/* SPDX-License-Identifier: GPL-2.0-or-later */ /** \file * \ingroup edscr * \brief Functions for screen vertices and edges * * Screen geometry refers to the vertices (ScrVert) and edges (ScrEdge) through * which the flexible screen-layout system of Blender is established. */ #include "BLI_listbase.h" #include "BLI_math.h" #include "BLI_rect.h" #include "BKE_screen.h" #include "DNA_screen_types.h" #include "DNA_windowmanager_types.h" #include "ED_screen.h" #include "MEM_guardedalloc.h" #include "WM_api.h" #include "screen_intern.h" int screen_geom_area_height(const ScrArea *area) { return area->v2->vec.y - area->v1->vec.y + 1; } int screen_geom_area_width(const ScrArea *area) { return area->v4->vec.x - area->v1->vec.x + 1; } ScrVert *screen_geom_vertex_add_ex(ScrAreaMap *area_map, short x, short y) { ScrVert *sv = MEM_callocN(sizeof(ScrVert), "addscrvert"); sv->vec.x = x; sv->vec.y = y; BLI_addtail(&area_map->vertbase, sv); return sv; } ScrVert *screen_geom_vertex_add(bScreen *screen, short x, short y) { return screen_geom_vertex_add_ex(AREAMAP_FROM_SCREEN(screen), x, y); } ScrEdge *screen_geom_edge_add_ex(ScrAreaMap *area_map, ScrVert *v1, ScrVert *v2) { ScrEdge *se = MEM_callocN(sizeof(ScrEdge), "addscredge"); BKE_screen_sort_scrvert(&v1, &v2); se->v1 = v1; se->v2 = v2; BLI_addtail(&area_map->edgebase, se); return se; } ScrEdge *screen_geom_edge_add(bScreen *screen, ScrVert *v1, ScrVert *v2) { return screen_geom_edge_add_ex(AREAMAP_FROM_SCREEN(screen), v1, v2); } bool screen_geom_edge_is_horizontal(ScrEdge *se) { return (se->v1->vec.y == se->v2->vec.y); } ScrEdge *screen_geom_area_map_find_active_scredge(const ScrAreaMap *area_map, const rcti *bounds_rect, const int mx, const int my) { int safety = BORDERPADDING; CLAMP_MIN(safety, 2); LISTBASE_FOREACH (ScrEdge *, se, &area_map->edgebase) { if (screen_geom_edge_is_horizontal(se)) { if ((se->v1->vec.y > bounds_rect->ymin) && (se->v1->vec.y < (bounds_rect->ymax - 1))) { short min, max; min = MIN2(se->v1->vec.x, se->v2->vec.x); max = MAX2(se->v1->vec.x, se->v2->vec.x); if (abs(my - se->v1->vec.y) <= safety && mx >= min && mx <= max) { return se; } } } else { if ((se->v1->vec.x > bounds_rect->xmin) && (se->v1->vec.x < (bounds_rect->xmax - 1))) { short min, max; min = MIN2(se->v1->vec.y, se->v2->vec.y); max = MAX2(se->v1->vec.y, se->v2->vec.y); if (abs(mx - se->v1->vec.x) <= safety && my >= min && my <= max) { return se; } } } } return NULL; } ScrEdge *screen_geom_find_active_scredge(const wmWindow *win, const bScreen *screen, const int mx, const int my) { if (U.app_flag & USER_APP_LOCK_EDGE_RESIZE) { return NULL; } /* Use layout size (screen excluding global areas) for screen-layout area edges */ rcti screen_rect; WM_window_screen_rect_calc(win, &screen_rect); ScrEdge *se = screen_geom_area_map_find_active_scredge( AREAMAP_FROM_SCREEN(screen), &screen_rect, mx, my); if (!se) { /* Use entire window size (screen including global areas) for global area edges */ rcti win_rect; WM_window_rect_calc(win, &win_rect); se = screen_geom_area_map_find_active_scredge(&win->global_areas, &win_rect, mx, my); } return se; } /** * A single pass for moving all screen vertices to fit into \a screen_rect. * \return true if another pass should be run. */ static bool screen_geom_vertices_scale_pass(const wmWindow *win, const bScreen *screen, const rcti *screen_rect) { const int screen_size_x = BLI_rcti_size_x(screen_rect); const int screen_size_y = BLI_rcti_size_y(screen_rect); bool needs_another_pass = false; /* calculate size */ float min[2] = {20000.0f, 20000.0f}; float max[2] = {0.0f, 0.0f}; LISTBASE_FOREACH (ScrVert *, sv, &screen->vertbase) { const float fv[2] = {(float)sv->vec.x, (float)sv->vec.y}; minmax_v2v2_v2(min, max, fv); } int screen_size_x_prev = (max[0] - min[0]) + 1; int screen_size_y_prev = (max[1] - min[1]) + 1; if (screen_size_x_prev != screen_size_x || screen_size_y_prev != screen_size_y) { const float facx = ((float)screen_size_x - 1) / ((float)screen_size_x_prev - 1); const float facy = ((float)screen_size_y - 1) / ((float)screen_size_y_prev - 1); /* make sure it fits! */ LISTBASE_FOREACH (ScrVert *, sv, &screen->vertbase) { sv->vec.x = screen_rect->xmin + round_fl_to_short((sv->vec.x - min[0]) * facx); CLAMP(sv->vec.x, screen_rect->xmin, screen_rect->xmax - 1); sv->vec.y = screen_rect->ymin + round_fl_to_short((sv->vec.y - min[1]) * facy); CLAMP(sv->vec.y, screen_rect->ymin, screen_rect->ymax - 1); } /* test for collapsed areas. This could happen in some blender version... */ /* ton: removed option now, it needs Context... */ int headery = ED_area_headersize() + (U.pixelsize * 2); if (facy > 1) { /* Keep timeline small in video edit workspace. */ LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { if (area->spacetype == SPACE_ACTION && area->v1->vec.y == screen_rect->ymin && screen_geom_area_height(area) <= headery * facy + 1) { ScrEdge *se = BKE_screen_find_edge(screen, area->v2, area->v3); if (se) { const int yval = area->v1->vec.y + headery - 1; screen_geom_select_connected_edge(win, se); /* all selected vertices get the right offset */ LISTBASE_FOREACH (ScrVert *, sv, &screen->vertbase) { /* if is a collapsed area */ if (!ELEM(sv, area->v1, area->v4)) { if (sv->flag) { sv->vec.y = yval; /* Changed size of a area. Run another pass to ensure everything still fits. */ needs_another_pass = true; } } } } } } } if (facy < 1) { /* make each window at least ED_area_headersize() high */ LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { if (screen_geom_area_height(area) < headery) { /* lower edge */ ScrEdge *se = BKE_screen_find_edge(screen, area->v4, area->v1); if (se && area->v1 != area->v2) { const int yval = area->v2->vec.y - headery + 1; screen_geom_select_connected_edge(win, se); /* all selected vertices get the right offset */ LISTBASE_FOREACH (ScrVert *, sv, &screen->vertbase) { /* if is not a collapsed area */ if (!ELEM(sv, area->v2, area->v3)) { if (sv->flag) { sv->vec.y = yval; /* Changed size of a area. Run another pass to ensure everything still fits. */ needs_another_pass = true; } } } } } } } } return needs_another_pass; } void screen_geom_vertices_scale(const wmWindow *win, bScreen *screen) { rcti window_rect, screen_rect; WM_window_rect_calc(win, &window_rect); WM_window_screen_rect_calc(win, &screen_rect); bool needs_another_pass; int max_passes_left = 10; /* Avoids endless loop. Number is rather arbitrary. */ do { needs_another_pass = screen_geom_vertices_scale_pass(win, screen, &screen_rect); max_passes_left--; } while (needs_another_pass && (max_passes_left > 0)); /* Global areas have a fixed size that only changes with the DPI. * Here we ensure that exactly this size is set. */ LISTBASE_FOREACH (ScrArea *, area, &win->global_areas.areabase) { if (area->global->flag & GLOBAL_AREA_IS_HIDDEN) { continue; } int height = ED_area_global_size_y(area) - 1; if (area->v1->vec.y > window_rect.ymin) { height += U.pixelsize; } if (area->v2->vec.y < (window_rect.ymax - 1)) { height += U.pixelsize; } /* width */ area->v1->vec.x = area->v2->vec.x = window_rect.xmin; area->v3->vec.x = area->v4->vec.x = window_rect.xmax - 1; /* height */ area->v1->vec.y = area->v4->vec.y = window_rect.ymin; area->v2->vec.y = area->v3->vec.y = window_rect.ymax - 1; switch (area->global->align) { case GLOBAL_AREA_ALIGN_TOP: area->v1->vec.y = area->v4->vec.y = area->v2->vec.y - height; break; case GLOBAL_AREA_ALIGN_BOTTOM: area->v2->vec.y = area->v3->vec.y = area->v1->vec.y + height; break; } } } short screen_geom_find_area_split_point(const ScrArea *area, const rcti *window_rect, const eScreenAxis dir_axis, float fac) { const int cur_area_width = screen_geom_area_width(area); const int cur_area_height = screen_geom_area_height(area); const short area_min_x = AREAMINX; const short area_min_y = ED_area_headersize(); /* area big enough? */ if (dir_axis == SCREEN_AXIS_V) { if (cur_area_width <= 2 * area_min_x) { return 0; } } else if (dir_axis == SCREEN_AXIS_H) { if (cur_area_height <= 2 * area_min_y) { return 0; } } /* to be sure */ CLAMP(fac, 0.0f, 1.0f); if (dir_axis == SCREEN_AXIS_H) { short y = area->v1->vec.y + round_fl_to_short(fac * cur_area_height); int area_min = area_min_y; if (area->v1->vec.y > window_rect->ymin) { area_min += U.pixelsize; } if (area->v2->vec.y < (window_rect->ymax - 1)) { area_min += U.pixelsize; } if (y - area->v1->vec.y < area_min) { y = area->v1->vec.y + area_min; } else if (area->v2->vec.y - y < area_min) { y = area->v2->vec.y - area_min; } return y; } short x = area->v1->vec.x + round_fl_to_short(fac * cur_area_width); int area_min = area_min_x; if (area->v1->vec.x > window_rect->xmin) { area_min += U.pixelsize; } if (area->v4->vec.x < (window_rect->xmax - 1)) { area_min += U.pixelsize; } if (x - area->v1->vec.x < area_min) { x = area->v1->vec.x + area_min; } else if (area->v4->vec.x - x < area_min) { x = area->v4->vec.x - area_min; } return x; } void screen_geom_select_connected_edge(const wmWindow *win, ScrEdge *edge) { bScreen *screen = WM_window_get_active_screen(win); /* 'dir_axis' is the direction of EDGE */ eScreenAxis dir_axis; if (edge->v1->vec.x == edge->v2->vec.x) { dir_axis = SCREEN_AXIS_V; } else { dir_axis = SCREEN_AXIS_H; } ED_screen_verts_iter(win, screen, sv) { sv->flag = 0; } edge->v1->flag = 1; edge->v2->flag = 1; /* select connected, only in the right direction */ bool oneselected = true; while (oneselected) { oneselected = false; LISTBASE_FOREACH (ScrEdge *, se, &screen->edgebase) { if (se->v1->flag + se->v2->flag == 1) { if (dir_axis == SCREEN_AXIS_H) { if (se->v1->vec.y == se->v2->vec.y) { se->v1->flag = se->v2->flag = 1; oneselected = true; } } else if (dir_axis == SCREEN_AXIS_V) { if (se->v1->vec.x == se->v2->vec.x) { se->v1->flag = se->v2->flag = 1; oneselected = true; } } } } } }