589 lines
19 KiB
C
589 lines
19 KiB
C
/*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
* The Original Code is Copyright (C) 2008 Blender Foundation.
|
|
* All rights reserved.
|
|
*/
|
|
|
|
/** \file
|
|
* \ingroup edinterface
|
|
*/
|
|
|
|
#include <float.h>
|
|
#include <limits.h>
|
|
#include <math.h>
|
|
#include <string.h>
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "DNA_scene_types.h"
|
|
#include "DNA_userdef_types.h"
|
|
|
|
#include "BLI_array.h"
|
|
#include "BLI_math.h"
|
|
#include "BLI_rect.h"
|
|
#include "BLI_string.h"
|
|
#include "BLI_timecode.h"
|
|
#include "BLI_utildefines.h"
|
|
|
|
#include "GPU_immediate.h"
|
|
#include "GPU_matrix.h"
|
|
#include "GPU_state.h"
|
|
|
|
#include "WM_api.h"
|
|
|
|
#include "BLF_api.h"
|
|
|
|
#include "UI_interface.h"
|
|
#include "UI_view2d.h"
|
|
|
|
#include "interface_intern.h"
|
|
|
|
/* Compute display grid resolution
|
|
********************************************************/
|
|
|
|
#define MIN_MAJOR_LINE_DISTANCE (U.v2d_min_gridsize * UI_DPI_FAC)
|
|
|
|
static float select_major_distance(const float *possible_distances,
|
|
uint amount,
|
|
float pixel_width,
|
|
float view_width)
|
|
{
|
|
BLI_assert(amount >= 1);
|
|
|
|
if (IS_EQF(view_width, 0.0f)) {
|
|
return possible_distances[0];
|
|
}
|
|
|
|
const float pixels_per_view_unit = pixel_width / view_width;
|
|
|
|
for (uint i = 0; i < amount; i++) {
|
|
const float distance = possible_distances[i];
|
|
if (pixels_per_view_unit * distance >= MIN_MAJOR_LINE_DISTANCE) {
|
|
return distance;
|
|
}
|
|
}
|
|
return possible_distances[amount - 1];
|
|
}
|
|
|
|
static const float discrete_value_scales[] = {
|
|
1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 50000, 100000};
|
|
|
|
static const float continuous_value_scales[] = {0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2,
|
|
5, 10, 20, 50, 100, 200, 500, 1000,
|
|
2000, 5000, 10000, 20000, 50000, 100000};
|
|
|
|
static uint view2d_major_step_x__discrete(const View2D *v2d)
|
|
{
|
|
return select_major_distance(discrete_value_scales,
|
|
ARRAY_SIZE(discrete_value_scales),
|
|
BLI_rcti_size_x(&v2d->mask),
|
|
BLI_rctf_size_x(&v2d->cur));
|
|
}
|
|
|
|
static float view2d_major_step_x__continuous(const View2D *v2d)
|
|
{
|
|
return select_major_distance(continuous_value_scales,
|
|
ARRAY_SIZE(continuous_value_scales),
|
|
BLI_rcti_size_x(&v2d->mask),
|
|
BLI_rctf_size_x(&v2d->cur));
|
|
}
|
|
|
|
static float view2d_major_step_y__continuous(const View2D *v2d)
|
|
{
|
|
return select_major_distance(continuous_value_scales,
|
|
ARRAY_SIZE(continuous_value_scales),
|
|
BLI_rcti_size_y(&v2d->mask),
|
|
BLI_rctf_size_y(&v2d->cur));
|
|
}
|
|
|
|
static float view2d_major_step_x__time(const View2D *v2d, const Scene *scene)
|
|
{
|
|
const double fps = FPS;
|
|
|
|
float *possible_distances = NULL;
|
|
BLI_array_staticdeclare(possible_distances, 32);
|
|
|
|
for (uint step = 1; step < fps; step *= 2) {
|
|
BLI_array_append(possible_distances, step);
|
|
}
|
|
BLI_array_append(possible_distances, fps);
|
|
BLI_array_append(possible_distances, 2 * fps);
|
|
BLI_array_append(possible_distances, 5 * fps);
|
|
BLI_array_append(possible_distances, 10 * fps);
|
|
BLI_array_append(possible_distances, 30 * fps);
|
|
BLI_array_append(possible_distances, 60 * fps);
|
|
BLI_array_append(possible_distances, 2 * 60 * fps);
|
|
BLI_array_append(possible_distances, 5 * 60 * fps);
|
|
BLI_array_append(possible_distances, 10 * 60 * fps);
|
|
BLI_array_append(possible_distances, 30 * 60 * fps);
|
|
BLI_array_append(possible_distances, 60 * 60 * fps);
|
|
|
|
float distance = select_major_distance(possible_distances,
|
|
BLI_array_len(possible_distances),
|
|
BLI_rcti_size_x(&v2d->mask),
|
|
BLI_rctf_size_x(&v2d->cur));
|
|
|
|
BLI_array_free(possible_distances);
|
|
return distance;
|
|
}
|
|
|
|
/* Draw parallel lines
|
|
************************************/
|
|
|
|
typedef struct ParallelLinesSet {
|
|
float offset;
|
|
float distance;
|
|
} ParallelLinesSet;
|
|
|
|
static void get_parallel_lines_draw_steps(const ParallelLinesSet *lines,
|
|
float region_start,
|
|
float region_end,
|
|
float *r_first,
|
|
uint *r_steps)
|
|
{
|
|
if (region_start >= region_end) {
|
|
*r_first = 0;
|
|
*r_steps = 0;
|
|
return;
|
|
}
|
|
|
|
BLI_assert(lines->distance > 0);
|
|
BLI_assert(region_start <= region_end);
|
|
|
|
*r_first = ceilf((region_start - lines->offset) / lines->distance) * lines->distance +
|
|
lines->offset;
|
|
|
|
if (region_start <= *r_first && region_end >= *r_first) {
|
|
*r_steps = MAX2(0, floorf((region_end - *r_first) / lines->distance)) + 1;
|
|
}
|
|
else {
|
|
*r_steps = 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* \param rect_mask: Region size in pixels.
|
|
*/
|
|
static void draw_parallel_lines(const ParallelLinesSet *lines,
|
|
const rctf *rect,
|
|
const rcti *rect_mask,
|
|
const uchar color[3],
|
|
char direction)
|
|
{
|
|
float first;
|
|
uint steps, steps_max;
|
|
|
|
if (direction == 'v') {
|
|
get_parallel_lines_draw_steps(lines, rect->xmin, rect->xmax, &first, &steps);
|
|
steps_max = BLI_rcti_size_x(rect_mask);
|
|
}
|
|
else {
|
|
BLI_assert(direction == 'h');
|
|
get_parallel_lines_draw_steps(lines, rect->ymin, rect->ymax, &first, &steps);
|
|
steps_max = BLI_rcti_size_y(rect_mask);
|
|
}
|
|
|
|
if (steps == 0) {
|
|
return;
|
|
}
|
|
|
|
if (UNLIKELY(steps >= steps_max)) {
|
|
/* Note that we could draw a solid color,
|
|
* however this flickers because of numeric instability when zoomed out. */
|
|
return;
|
|
}
|
|
|
|
GPUVertFormat *format = immVertexFormat();
|
|
const uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
|
|
|
|
if (U.pixelsize > 1.0f) {
|
|
float viewport[4];
|
|
GPU_viewport_size_get_f(viewport);
|
|
|
|
immBindBuiltinProgram(GPU_SHADER_3D_POLYLINE_UNIFORM_COLOR);
|
|
immUniform2fv("viewportSize", &viewport[2]);
|
|
/* -1.0f offset here is because the line is too fat due to the builtin anti-aliasing.
|
|
* TODO make a variant or a uniform to toggle it off. */
|
|
immUniform1f("lineWidth", U.pixelsize - 1.0f);
|
|
}
|
|
else {
|
|
immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
|
|
}
|
|
immUniformColor3ubv(color);
|
|
immBegin(GPU_PRIM_LINES, steps * 2);
|
|
|
|
if (direction == 'v') {
|
|
for (uint i = 0; i < steps; i++) {
|
|
const float xpos = first + i * lines->distance;
|
|
immVertex2f(pos, xpos, rect->ymin);
|
|
immVertex2f(pos, xpos, rect->ymax);
|
|
}
|
|
}
|
|
else {
|
|
for (uint i = 0; i < steps; i++) {
|
|
const float ypos = first + i * lines->distance;
|
|
immVertex2f(pos, rect->xmin, ypos);
|
|
immVertex2f(pos, rect->xmax, ypos);
|
|
}
|
|
}
|
|
|
|
immEnd();
|
|
immUnbindProgram();
|
|
}
|
|
|
|
static void view2d_draw_lines_internal(const View2D *v2d,
|
|
const ParallelLinesSet *lines,
|
|
const uchar color[3],
|
|
char direction)
|
|
{
|
|
GPU_matrix_push_projection();
|
|
UI_view2d_view_ortho(v2d);
|
|
draw_parallel_lines(lines, &v2d->cur, &v2d->mask, color, direction);
|
|
GPU_matrix_pop_projection();
|
|
}
|
|
|
|
static void view2d_draw_lines(const View2D *v2d,
|
|
float major_distance,
|
|
bool display_minor_lines,
|
|
char direction)
|
|
{
|
|
{
|
|
uchar major_color[3];
|
|
UI_GetThemeColor3ubv(TH_GRID, major_color);
|
|
ParallelLinesSet major_lines;
|
|
major_lines.distance = major_distance;
|
|
major_lines.offset = 0;
|
|
view2d_draw_lines_internal(v2d, &major_lines, major_color, direction);
|
|
}
|
|
|
|
if (display_minor_lines) {
|
|
uchar minor_color[3];
|
|
UI_GetThemeColorShade3ubv(TH_GRID, 16, minor_color);
|
|
ParallelLinesSet minor_lines;
|
|
minor_lines.distance = major_distance;
|
|
minor_lines.offset = major_distance / 2.0f;
|
|
view2d_draw_lines_internal(v2d, &minor_lines, minor_color, direction);
|
|
}
|
|
}
|
|
|
|
/* Scale indicator text drawing
|
|
**************************************************/
|
|
|
|
typedef void (*PositionToString)(
|
|
void *user_data, float v2d_pos, float v2d_step, uint max_len, char *r_str);
|
|
|
|
static void draw_horizontal_scale_indicators(const ARegion *region,
|
|
const View2D *v2d,
|
|
float distance,
|
|
const rcti *rect,
|
|
PositionToString to_string,
|
|
void *to_string_data,
|
|
int colorid)
|
|
{
|
|
if (UI_view2d_scale_get_x(v2d) <= 0.0f) {
|
|
return;
|
|
}
|
|
|
|
float start;
|
|
uint steps;
|
|
{
|
|
ParallelLinesSet lines;
|
|
lines.distance = distance;
|
|
lines.offset = 0;
|
|
get_parallel_lines_draw_steps(&lines,
|
|
UI_view2d_region_to_view_x(v2d, rect->xmin),
|
|
UI_view2d_region_to_view_x(v2d, rect->xmax),
|
|
&start,
|
|
&steps);
|
|
const uint steps_max = BLI_rcti_size_x(&v2d->mask);
|
|
if (UNLIKELY(steps >= steps_max)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
GPU_matrix_push_projection();
|
|
wmOrtho2_region_pixelspace(region);
|
|
|
|
const int font_id = BLF_default();
|
|
UI_FontThemeColor(font_id, colorid);
|
|
|
|
BLF_batch_draw_begin();
|
|
|
|
const float ypos = rect->ymin + 4 * UI_DPI_FAC;
|
|
const float xmin = rect->xmin;
|
|
const float xmax = rect->xmax;
|
|
|
|
for (uint i = 0; i < steps; i++) {
|
|
const float xpos_view = start + i * distance;
|
|
const float xpos_region = UI_view2d_view_to_region_x(v2d, xpos_view);
|
|
char text[32];
|
|
to_string(to_string_data, xpos_view, distance, sizeof(text), text);
|
|
const float text_width = BLF_width(font_id, text, strlen(text));
|
|
|
|
if (xpos_region - text_width / 2.0f >= xmin && xpos_region + text_width / 2.0f <= xmax) {
|
|
BLF_draw_default_ascii(xpos_region - text_width / 2.0f, ypos, 0.0f, text, sizeof(text));
|
|
}
|
|
}
|
|
|
|
BLF_batch_draw_end();
|
|
|
|
GPU_matrix_pop_projection();
|
|
}
|
|
|
|
static void draw_vertical_scale_indicators(const ARegion *region,
|
|
const View2D *v2d,
|
|
float distance,
|
|
float display_offset,
|
|
const rcti *rect,
|
|
PositionToString to_string,
|
|
void *to_string_data,
|
|
int colorid)
|
|
{
|
|
if (UI_view2d_scale_get_y(v2d) <= 0.0f) {
|
|
return;
|
|
}
|
|
|
|
float start;
|
|
uint steps;
|
|
{
|
|
ParallelLinesSet lines;
|
|
lines.distance = distance;
|
|
lines.offset = 0;
|
|
get_parallel_lines_draw_steps(&lines,
|
|
UI_view2d_region_to_view_y(v2d, rect->ymin),
|
|
UI_view2d_region_to_view_y(v2d, rect->ymax),
|
|
&start,
|
|
&steps);
|
|
const uint steps_max = BLI_rcti_size_y(&v2d->mask);
|
|
if (UNLIKELY(steps >= steps_max)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
GPU_matrix_push_projection();
|
|
wmOrtho2_region_pixelspace(region);
|
|
|
|
const int font_id = BLF_default();
|
|
UI_FontThemeColor(font_id, colorid);
|
|
|
|
BLF_enable(font_id, BLF_ROTATION);
|
|
BLF_rotation(font_id, M_PI_2);
|
|
|
|
BLF_batch_draw_begin();
|
|
|
|
const float xpos = rect->xmax - 2.0f * UI_DPI_FAC;
|
|
const float ymin = rect->ymin;
|
|
const float ymax = rect->ymax;
|
|
|
|
for (uint i = 0; i < steps; i++) {
|
|
const float ypos_view = start + i * distance;
|
|
const float ypos_region = UI_view2d_view_to_region_y(v2d, ypos_view + display_offset);
|
|
char text[32];
|
|
to_string(to_string_data, ypos_view, distance, sizeof(text), text);
|
|
const float text_width = BLF_width(font_id, text, strlen(text));
|
|
|
|
if (ypos_region - text_width / 2.0f >= ymin && ypos_region + text_width / 2.0f <= ymax) {
|
|
BLF_draw_default_ascii(xpos, ypos_region - text_width / 2.0f, 0.0f, text, sizeof(text));
|
|
}
|
|
}
|
|
|
|
BLF_batch_draw_end();
|
|
BLF_disable(font_id, BLF_ROTATION);
|
|
|
|
GPU_matrix_pop_projection();
|
|
}
|
|
|
|
static void view_to_string__frame_number(
|
|
void *UNUSED(user_data), float v2d_pos, float UNUSED(v2d_step), uint max_len, char *r_str)
|
|
{
|
|
BLI_snprintf(r_str, max_len, "%d", (int)v2d_pos);
|
|
}
|
|
|
|
static void view_to_string__time(
|
|
void *user_data, float v2d_pos, float UNUSED(v2d_step), uint max_len, char *r_str)
|
|
{
|
|
const Scene *scene = (const Scene *)user_data;
|
|
|
|
const int brevity_level = 0;
|
|
BLI_timecode_string_from_time(
|
|
r_str, max_len, brevity_level, v2d_pos / (float)FPS, FPS, U.timecode_style);
|
|
}
|
|
|
|
static void view_to_string__value(
|
|
void *UNUSED(user_data), float v2d_pos, float v2d_step, uint max_len, char *r_str)
|
|
{
|
|
if (v2d_step >= 1.0f) {
|
|
BLI_snprintf(r_str, max_len, "%d", (int)v2d_pos);
|
|
}
|
|
else if (v2d_step >= 0.1f) {
|
|
BLI_snprintf(r_str, max_len, "%.1f", v2d_pos);
|
|
}
|
|
else if (v2d_step >= 0.01f) {
|
|
BLI_snprintf(r_str, max_len, "%.2f", v2d_pos);
|
|
}
|
|
else {
|
|
BLI_snprintf(r_str, max_len, "%.3f", v2d_pos);
|
|
}
|
|
}
|
|
|
|
/* Grid Resolution API
|
|
**************************************************/
|
|
|
|
float UI_view2d_grid_resolution_x__frames_or_seconds(const struct View2D *v2d,
|
|
const struct Scene *scene,
|
|
bool display_seconds)
|
|
{
|
|
if (display_seconds) {
|
|
return view2d_major_step_x__time(v2d, scene);
|
|
}
|
|
return view2d_major_step_x__continuous(v2d);
|
|
}
|
|
|
|
float UI_view2d_grid_resolution_y__values(const struct View2D *v2d)
|
|
{
|
|
return view2d_major_step_y__continuous(v2d);
|
|
}
|
|
|
|
/* Line Drawing API
|
|
**************************************************/
|
|
|
|
void UI_view2d_draw_lines_x__discrete_values(const View2D *v2d)
|
|
{
|
|
const uint major_line_distance = view2d_major_step_x__discrete(v2d);
|
|
view2d_draw_lines(v2d, major_line_distance, major_line_distance > 1, 'v');
|
|
}
|
|
|
|
void UI_view2d_draw_lines_x__values(const View2D *v2d)
|
|
{
|
|
const float major_line_distance = view2d_major_step_x__continuous(v2d);
|
|
view2d_draw_lines(v2d, major_line_distance, true, 'v');
|
|
}
|
|
|
|
void UI_view2d_draw_lines_y__values(const View2D *v2d)
|
|
{
|
|
const float major_line_distance = view2d_major_step_y__continuous(v2d);
|
|
view2d_draw_lines(v2d, major_line_distance, true, 'h');
|
|
}
|
|
|
|
void UI_view2d_draw_lines_x__discrete_time(const View2D *v2d, const Scene *scene)
|
|
{
|
|
const float major_line_distance = view2d_major_step_x__time(v2d, scene);
|
|
view2d_draw_lines(v2d, major_line_distance, major_line_distance > 1, 'v');
|
|
}
|
|
|
|
void UI_view2d_draw_lines_x__discrete_frames_or_seconds(const View2D *v2d,
|
|
const Scene *scene,
|
|
bool display_seconds)
|
|
{
|
|
if (display_seconds) {
|
|
UI_view2d_draw_lines_x__discrete_time(v2d, scene);
|
|
}
|
|
else {
|
|
UI_view2d_draw_lines_x__discrete_values(v2d);
|
|
}
|
|
}
|
|
|
|
void UI_view2d_draw_lines_x__frames_or_seconds(const View2D *v2d,
|
|
const Scene *scene,
|
|
bool display_seconds)
|
|
{
|
|
if (display_seconds) {
|
|
UI_view2d_draw_lines_x__discrete_time(v2d, scene);
|
|
}
|
|
else {
|
|
UI_view2d_draw_lines_x__values(v2d);
|
|
}
|
|
}
|
|
|
|
/* Scale indicator text drawing API
|
|
**************************************************/
|
|
|
|
static void UI_view2d_draw_scale_x__discrete_values(const ARegion *region,
|
|
const View2D *v2d,
|
|
const rcti *rect,
|
|
int colorid)
|
|
{
|
|
const float number_step = view2d_major_step_x__discrete(v2d);
|
|
draw_horizontal_scale_indicators(
|
|
region, v2d, number_step, rect, view_to_string__frame_number, NULL, colorid);
|
|
}
|
|
|
|
static void UI_view2d_draw_scale_x__discrete_time(
|
|
const ARegion *region, const View2D *v2d, const rcti *rect, const Scene *scene, int colorid)
|
|
{
|
|
const float step = view2d_major_step_x__time(v2d, scene);
|
|
draw_horizontal_scale_indicators(
|
|
region, v2d, step, rect, view_to_string__time, (void *)scene, colorid);
|
|
}
|
|
|
|
static void UI_view2d_draw_scale_x__values(const ARegion *region,
|
|
const View2D *v2d,
|
|
const rcti *rect,
|
|
int colorid)
|
|
{
|
|
const float step = view2d_major_step_x__continuous(v2d);
|
|
draw_horizontal_scale_indicators(region, v2d, step, rect, view_to_string__value, NULL, colorid);
|
|
}
|
|
|
|
void UI_view2d_draw_scale_y__values(const ARegion *region,
|
|
const View2D *v2d,
|
|
const rcti *rect,
|
|
int colorid)
|
|
{
|
|
const float step = view2d_major_step_y__continuous(v2d);
|
|
draw_vertical_scale_indicators(
|
|
region, v2d, step, 0.0f, rect, view_to_string__value, NULL, colorid);
|
|
}
|
|
|
|
void UI_view2d_draw_scale_y__block(const ARegion *region,
|
|
const View2D *v2d,
|
|
const rcti *rect,
|
|
int colorid)
|
|
{
|
|
draw_vertical_scale_indicators(
|
|
region, v2d, 1.0f, 0.5f, rect, view_to_string__value, NULL, colorid);
|
|
}
|
|
|
|
void UI_view2d_draw_scale_x__discrete_frames_or_seconds(const struct ARegion *region,
|
|
const struct View2D *v2d,
|
|
const struct rcti *rect,
|
|
const struct Scene *scene,
|
|
bool display_seconds,
|
|
int colorid)
|
|
{
|
|
if (display_seconds) {
|
|
UI_view2d_draw_scale_x__discrete_time(region, v2d, rect, scene, colorid);
|
|
}
|
|
else {
|
|
UI_view2d_draw_scale_x__discrete_values(region, v2d, rect, colorid);
|
|
}
|
|
}
|
|
|
|
void UI_view2d_draw_scale_x__frames_or_seconds(const struct ARegion *region,
|
|
const struct View2D *v2d,
|
|
const struct rcti *rect,
|
|
const struct Scene *scene,
|
|
bool display_seconds,
|
|
int colorid)
|
|
{
|
|
if (display_seconds) {
|
|
UI_view2d_draw_scale_x__discrete_time(region, v2d, rect, scene, colorid);
|
|
}
|
|
else {
|
|
UI_view2d_draw_scale_x__values(region, v2d, rect, colorid);
|
|
}
|
|
}
|