Add Chromium-only Blender WebEngine parity work

This commit is contained in:
mes123456
2026-08-12 04:47:48 -04:00
commit 9fd26010f6
18225 changed files with 11622124 additions and 0 deletions

View File

@@ -0,0 +1,64 @@
# SPDX-FileCopyrightText: 2023 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later
set(INC
../asset
../include
../space_graph
../../makesrna
../../nodes
# RNA_prototypes.hh
${CMAKE_BINARY_DIR}/source/blender/makesrna
)
set(INC_SYS
)
set(SRC
area.cc
area_query.cc
area_utils.cc
glutil.cc
screen_context.cc
screen_draw.cc
screen_edit.cc
screen_geometry.cc
screen_ops.cc
screen_user_menu.cc
screendump.cc
workspace_edit.cc
workspace_layout_edit.cc
workspace_listen.cc
screen_intern.hh
)
set(LIB
PRIVATE bf::animrig
PRIVATE bf::asset_system
PRIVATE bf::blenfont
PRIVATE bf::blenkernel
PRIVATE bf::blenlib
PRIVATE bf::blenloader
PRIVATE bf::blentranslation
PRIVATE bf::bmesh
PRIVATE bf::depsgraph
PRIVATE bf::dna
bf_editor_datafiles
bf_editor_space_sequencer
PRIVATE bf::gpu
PRIVATE bf::imbuf
PRIVATE bf::intern::guardedalloc
PRIVATE bf::sequencer
PRIVATE bf::windowmanager
)
if(WITH_HEADLESS)
add_definitions(-DWITH_HEADLESS)
endif()
blender_add_lib(bf_editor_screen "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
# RNA_prototypes.hh
add_dependencies(bf_editor_screen bf_rna)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,229 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edscr
*
* Query functions for area/region.
*/
#include "BKE_screen.hh"
#include "BLI_listbase.h"
#include "BLI_math_base.h"
#include "BLI_utildefines.h"
#include "ED_screen.hh"
#include "UI_interface.hh"
#include "UI_view2d.hh"
namespace blender {
bool ED_region_overlap_isect_x(const ARegion *region, const int event_x)
{
BLI_assert(region->overlap);
/* No contents, skip it. */
if (region->v2d.mask.xmin == region->v2d.mask.xmax) {
return false;
}
if ((event_x < region->winrct.xmin) || (event_x > region->winrct.xmax)) {
return false;
}
return BLI_rctf_isect_x(
&region->v2d.tot, ui::view2d_region_to_view_x(&region->v2d, event_x - region->winrct.xmin));
}
bool ED_region_overlap_isect_y(const ARegion *region, const int event_y)
{
BLI_assert(region->overlap);
/* No contents, skip it. */
if (region->v2d.mask.ymin == region->v2d.mask.ymax) {
return false;
}
if ((event_y < region->winrct.ymin) || (event_y > region->winrct.ymax)) {
return false;
}
return BLI_rctf_isect_y(
&region->v2d.tot, ui::view2d_region_to_view_y(&region->v2d, event_y - region->winrct.ymin));
}
bool ED_region_overlap_isect_xy(const ARegion *region, const int event_xy[2])
{
return (ED_region_overlap_isect_x(region, event_xy[0]) &&
ED_region_overlap_isect_y(region, event_xy[1]));
}
bool ED_region_overlap_isect_any_xy(const ScrArea *area, const int event_xy[2])
{
for (ARegion &region : area->regionbase) {
if (!region.runtime->visible) {
continue;
}
if (ED_region_is_overlap(area->spacetype, region.regiontype)) {
if (ED_region_overlap_isect_xy(&region, event_xy)) {
return true;
}
}
}
return false;
}
bool ED_region_panel_category_gutter_calc_rect(const ARegion *region, rcti *r_region_gutter)
{
*r_region_gutter = region->winrct;
if (ui::panel_category_tabs_is_visible(region)) {
const int category_tabs_width = round_fl_to_int(ui::view2d_scale_get_x(&region->v2d) *
UI_PANEL_CATEGORY_MARGIN_WIDTH);
const int alignment = RGN_ALIGN_ENUM_FROM_MASK(region->alignment);
if (alignment == RGN_ALIGN_LEFT) {
r_region_gutter->xmax = r_region_gutter->xmin + category_tabs_width;
}
else if (alignment == RGN_ALIGN_RIGHT) {
r_region_gutter->xmin = r_region_gutter->xmax - category_tabs_width;
}
else {
BLI_assert_msg(0, "Unsupported alignment");
}
return true;
}
return false;
}
bool ED_region_panel_category_gutter_isect_xy(const ARegion *region, const int event_xy[2])
{
rcti region_gutter;
if (ED_region_panel_category_gutter_calc_rect(region, &region_gutter)) {
return BLI_rcti_isect_pt_v(&region_gutter, event_xy);
}
return false;
}
bool ED_region_overlap_isect_x_with_margin(const ARegion *region,
const int event_x,
const int margin)
{
BLI_assert(region->overlap);
/* No contents, skip it. */
if (region->v2d.mask.xmin == region->v2d.mask.xmax) {
return false;
}
if ((event_x < region->winrct.xmin) || (event_x > region->winrct.xmax)) {
return false;
}
const int region_x = event_x - region->winrct.xmin;
return ((region->v2d.tot.xmin <= ui::view2d_region_to_view_x(&region->v2d, region_x + margin)) &&
(region->v2d.tot.xmax >= ui::view2d_region_to_view_x(&region->v2d, region_x - margin)));
}
bool ED_region_overlap_isect_y_with_margin(const ARegion *region,
const int event_y,
const int margin)
{
BLI_assert(region->overlap);
/* No contents, skip it. */
if (region->v2d.mask.ymin == region->v2d.mask.ymax) {
return false;
}
if ((event_y < region->winrct.ymin) || (event_y > region->winrct.ymax)) {
return false;
}
const int region_y = event_y - region->winrct.ymin;
return (region->v2d.tot.ymin <= ui::view2d_region_to_view_y(&region->v2d, region_y + margin)) &&
(region->v2d.tot.ymax >= ui::view2d_region_to_view_y(&region->v2d, region_y - margin));
}
bool ED_region_overlap_isect_xy_with_margin(const ARegion *region,
const int event_xy[2],
const int margin)
{
return (ED_region_overlap_isect_x_with_margin(region, event_xy[0], margin) &&
ED_region_overlap_isect_y_with_margin(region, event_xy[1], margin));
}
bool ED_region_contains_xy(const ARegion *region, const int event_xy[2])
{
/* Only use the margin when inside the region. */
if (BLI_rcti_isect_pt_v(&region->winrct, event_xy)) {
if (region->overlap) {
const int overlap_margin = UI_REGION_OVERLAP_MARGIN;
/* Note the View2D.tot isn't reliable for headers with spacers otherwise
* we'd check #ED_region_overlap_isect_xy_with_margin for both bases. */
if (region->v2d.keeptot == V2D_KEEPTOT_STRICT) {
/* Header. */
rcti rect;
BLI_rcti_init_pt_radius(&rect, event_xy, overlap_margin);
if (ui::region_but_find_rect_over(region, &rect) == nullptr) {
return false;
}
}
else {
/* Side-bar & any other kind of overlapping region. */
const int alignment = RGN_ALIGN_ENUM_FROM_MASK(region->alignment);
/* Check alignment to avoid region tabs being clipped out
* by only clipping a single axis for aligned regions. */
if (ELEM(alignment, RGN_ALIGN_TOP, RGN_ALIGN_BOTTOM)) {
if (!ED_region_overlap_isect_x_with_margin(region, event_xy[0], overlap_margin)) {
return false;
}
}
else if (ELEM(alignment, RGN_ALIGN_LEFT, RGN_ALIGN_RIGHT)) {
if (ED_region_panel_category_gutter_isect_xy(region, event_xy)) {
/* pass */
}
else if (!ED_region_overlap_isect_y_with_margin(region, event_xy[1], overlap_margin)) {
return false;
}
}
else {
/* No panel categories for horizontal regions currently. */
if (!ED_region_overlap_isect_xy_with_margin(region, event_xy, overlap_margin)) {
return false;
}
}
}
}
return true;
}
return false;
}
ARegion *ED_area_find_region_xy_visual(const ScrArea *area,
const int regiontype,
const int event_xy[2])
{
if (!area) {
return nullptr;
}
/* Check overlapped regions first. */
for (ARegion &region : area->regionbase) {
if (!region.overlap) {
continue;
}
if (ELEM(regiontype, RGN_TYPE_ANY, region.regiontype)) {
if (ED_region_contains_xy(&region, event_xy)) {
return &region;
}
}
}
/* Now non-overlapping ones. */
for (ARegion &region : area->regionbase) {
if (region.overlap) {
continue;
}
if (ELEM(regiontype, RGN_TYPE_ANY, region.regiontype)) {
if (ED_region_contains_xy(&region, event_xy)) {
return &region;
}
}
}
return nullptr;
}
} // namespace blender

View File

@@ -0,0 +1,90 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edscr
*
* Helper functions for area/region API.
*/
#include <limits>
#include "BKE_screen.hh"
#include "BLI_rect.h"
#include "BLI_utildefines.h"
#include "WM_message.hh"
#include "ED_screen.hh"
#include "UI_interface.hh"
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Generic Tool System Region Callbacks
* \{ */
void ED_region_generic_tools_region_message_subscribe(const wmRegionMessageSubscribeParams *params)
{
wmMsgBus *mbus = params->message_bus;
ARegion *region = params->region;
wmMsgSubscribeValue msg_sub_value_region_tag_redraw{};
msg_sub_value_region_tag_redraw.owner = region;
msg_sub_value_region_tag_redraw.user_data = region;
msg_sub_value_region_tag_redraw.notify = ED_region_do_msg_notify_tag_redraw;
WM_msg_subscribe_rna_anon_prop(mbus, WorkSpace, tools, &msg_sub_value_region_tag_redraw);
}
int ED_region_generic_tools_region_snap_size(const ARegion *region, int size, int axis)
{
if (axis == 0) {
/* Using Y axis avoids slight feedback loop when adjusting X. */
const float aspect = BLI_rctf_size_y(&region->v2d.cur) /
(BLI_rcti_size_y(&region->v2d.mask) + 1);
const float column = UI_TOOLBAR_COLUMN / aspect;
const float margin = UI_TOOLBAR_MARGIN / aspect;
const float snap_units[] = {
column + margin,
(2.0f * column) + margin,
(2.7f * column) + margin,
};
int best_diff = std::numeric_limits<int>::max();
int best_size = size;
/* Only snap if less than last snap unit. */
if (size <= snap_units[ARRAY_SIZE(snap_units) - 1]) {
for (uint i = 0; i < ARRAY_SIZE(snap_units); i += 1) {
const int test_size = snap_units[i];
const int test_diff = abs(test_size - size);
if (test_diff < best_diff) {
best_size = test_size;
best_diff = test_diff;
}
}
}
return best_size;
}
return size;
}
int ED_region_generic_panel_region_snap_size(const ARegion *region, int size, int axis)
{
if (axis == 0) {
if (!ui::panel_category_tabs_is_visible(region)) {
return size;
}
/* Using Y axis avoids slight feedback loop when adjusting X. */
const float aspect = BLI_rctf_size_y(&region->v2d.cur) /
(BLI_rcti_size_y(&region->v2d.mask) + 1);
return int(UI_PANEL_CATEGORY_MIN_WIDTH / aspect);
}
return size;
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,228 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edscr
*/
#include "DNA_userdef_types.h"
#include "DNA_vec_types.h"
#include "BLI_utildefines.h"
#include "BIF_glutil.hh"
#include "IMB_colormanagement.hh"
#include "IMB_imbuf_types.hh"
#include "GPU_immediate.hh"
#include "GPU_texture.hh"
namespace blender {
void PixelBitmapDrawer::init_vertex_attributes()
{
GPUVertFormat *vert_format = immVertexFormat();
this->pos = GPU_vertformat_attr_add(vert_format, "pos", gpu::VertAttrType::SFLOAT_32_32);
this->texco = GPU_vertformat_attr_add(vert_format, "texCoord", gpu::VertAttrType::SFLOAT_32_32);
}
PixelBitmapDrawer::PixelBitmapDrawer() : shader(nullptr)
{
init_vertex_attributes();
}
PixelBitmapDrawer::PixelBitmapDrawer(GPUBuiltinShader builtin_shader)
{
init_vertex_attributes();
this->shader = GPU_shader_get_builtin_shader(builtin_shader);
/* Shader will be unbound in draw(). */
immBindBuiltinProgram(builtin_shader);
}
void PixelBitmapDrawer::draw(const float x,
const float y,
const int img_w,
const int img_h,
const gpu::TextureFormat gpu_format,
const bool use_filter,
const void *rect,
const float scale_x,
const float scale_y,
const float color[4])
{
const float draw_width = img_w * scale_x;
const float draw_height = img_h * scale_y;
/* When scaling down by more than 2x, create mipmaps for the texture and
* use trilinear filtering. */
const bool use_mipmap = use_filter && (scale_x < 0.5f || scale_y < 0.5f);
const int mip_len = use_mipmap ? 9999 : 1;
eGPUTextureUsage usage = GPU_TEXTURE_USAGE_SHADER_READ;
if (use_mipmap) {
usage |= GPU_TEXTURE_USAGE_SHADER_WRITE;
}
gpu::Texture *tex = GPU_texture_create_2d(
"immDrawPixels", img_w, img_h, mip_len, gpu_format, usage, nullptr);
if (tex == nullptr) {
return;
}
const bool use_float_data = ELEM(gpu_format,
gpu::TextureFormat::SFLOAT_16_16_16_16,
gpu::TextureFormat::SFLOAT_16_16_16,
gpu::TextureFormat::SFLOAT_16);
eGPUDataFormat gpu_data_format = use_float_data ? GPU_DATA_FLOAT : GPU_DATA_UBYTE;
GPU_texture_update(tex, gpu_data_format, rect);
GPU_texture_filter_mode(tex, use_filter);
if (use_mipmap) {
GPU_texture_update_mipmap_chain(tex);
GPU_texture_mipmap_mode(tex, true, true);
}
GPU_texture_extend_mode(tex, GPU_SAMPLER_EXTEND_MODE_EXTEND);
GPU_texture_bind(tex, 0);
/* NOTE: Shader could be null for GLSL OCIO drawing, it is fine, since
* it does not need color. */
static const float white[4] = {1.0f, 1.0f, 1.0f, 1.0f};
if (this->shader != nullptr && GPU_shader_get_uniform(this->shader, "color") != -1) {
immUniformColor4fv((color) ? color : white);
}
const uint pos = this->pos, texco = this->texco;
immBegin(GPU_PRIM_TRI_FAN, 4);
immAttr2f(texco, 0.0f, 0.0f);
immVertex2f(pos, x, y);
immAttr2f(texco, 1.0f, 0.0f);
immVertex2f(pos, x + draw_width, y);
immAttr2f(texco, 1.0f, 1.0f);
immVertex2f(pos, x + draw_width, y + draw_height);
immAttr2f(texco, 0.0f, 1.0f);
immVertex2f(pos, x, y + draw_height);
immEnd();
if (this->shader) {
immUnbindProgram();
}
GPU_texture_unbind(tex);
GPU_texture_free(tex);
}
void ED_draw_imbuf(const ImBuf *ibuf,
float x,
float y,
bool use_filter,
const ColorManagedViewSettings *view_settings,
const ColorManagedDisplaySettings *display_settings,
float zoom_x,
float zoom_y)
{
using namespace blender::gpu;
/* Early out */
if (ibuf->byte_data() == nullptr && ibuf->float_data() == nullptr) {
return;
}
PixelBitmapDrawer drawer;
const ColorSpace *colorspace = ibuf->float_data() ? ibuf->float_buffer.colorspace :
ibuf->byte_buffer.colorspace;
const bool predivide = ibuf->float_data() != nullptr;
if (!IMB_colormanagement_setup_glsl_draw_from_space(
view_settings, display_settings, colorspace, ibuf->dither, predivide, false))
{
return;
}
const void *texture_data = nullptr;
TextureFormat format = TextureFormat::Invalid;
if (ibuf->float_data()) {
texture_data = ibuf->float_data();
if (ibuf->channels == 1) {
format = TextureFormat::SFLOAT_16;
}
else if (ibuf->channels == 3) {
format = TextureFormat::SFLOAT_16_16_16;
}
else if (ibuf->channels == 4) {
format = TextureFormat::SFLOAT_16_16_16_16;
}
else {
BLI_assert_msg(0, "Incompatible number of channels for GLSL display");
}
}
else {
texture_data = ibuf->byte_data();
format = TextureFormat::UNORM_8_8_8_8;
}
if (format != TextureFormat::Invalid) {
drawer.draw(x, y, ibuf->x, ibuf->y, format, use_filter, texture_data, zoom_x, zoom_y, nullptr);
}
IMB_colormanagement_finish_glsl_draw();
}
void ED_draw_imbuf_ctx(const bContext *C,
const ImBuf *ibuf,
float x,
float y,
bool use_filter,
float zoom_x,
float zoom_y)
{
ColorManagedViewSettings *view_settings;
ColorManagedDisplaySettings *display_settings;
IMB_colormanagement_display_settings_from_ctx(C, &view_settings, &display_settings);
ED_draw_imbuf(ibuf, x, y, use_filter, view_settings, display_settings, zoom_x, zoom_y);
}
void immDrawBorderCorners(uint pos, const rcti *border, float zoomx, float zoomy)
{
float delta_x = 4.0f * UI_SCALE_FAC / zoomx;
float delta_y = 4.0f * UI_SCALE_FAC / zoomy;
delta_x = min_ff(delta_x, border->xmax - border->xmin);
delta_y = min_ff(delta_y, border->ymax - border->ymin);
/* left bottom corner */
immBegin(GPU_PRIM_LINE_STRIP, 3);
immVertex2f(pos, border->xmin, border->ymin + delta_y);
immVertex2f(pos, border->xmin, border->ymin);
immVertex2f(pos, border->xmin + delta_x, border->ymin);
immEnd();
/* left top corner */
immBegin(GPU_PRIM_LINE_STRIP, 3);
immVertex2f(pos, border->xmin, border->ymax - delta_y);
immVertex2f(pos, border->xmin, border->ymax);
immVertex2f(pos, border->xmin + delta_x, border->ymax);
immEnd();
/* right bottom corner */
immBegin(GPU_PRIM_LINE_STRIP, 3);
immVertex2f(pos, border->xmax - delta_x, border->ymin);
immVertex2f(pos, border->xmax, border->ymin);
immVertex2f(pos, border->xmax, border->ymin + delta_y);
immEnd();
/* right top corner */
immBegin(GPU_PRIM_LINE_STRIP, 3);
immVertex2f(pos, border->xmax - delta_x, border->ymax);
immVertex2f(pos, border->xmax, border->ymax);
immVertex2f(pos, border->xmax, border->ymax - delta_y);
immEnd();
}
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,753 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edscr
*/
#include "ED_screen.hh"
#include "ED_screen_types.hh"
#include "GPU_batch_presets.hh"
#include "GPU_immediate.hh"
#include "GPU_platform.hh"
#include "GPU_state.hh"
#include "BKE_global.hh"
#include "BKE_screen.hh"
#include "BLF_api.hh"
#include "BLI_listbase.h"
#include "BLI_math_color.h"
#include "BLI_math_vector.h"
#include "BLI_rect.h"
#include "BLI_time.h"
#include "BLT_translation.hh"
#include "WM_api.hh"
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "screen_intern.hh"
namespace blender {
#define CORNER_RESOLUTION 3
static void do_vert_pair(gpu::VertBuf *vbo, uint pos, uint *vidx, int corner, int i)
{
float inter[2];
inter[0] = cosf(corner * M_PI_2 + (i * M_PI_2 / (CORNER_RESOLUTION - 1.0f)));
inter[1] = sinf(corner * M_PI_2 + (i * M_PI_2 / (CORNER_RESOLUTION - 1.0f)));
/* Snap point to edge */
float div = 1.0f / max_ff(fabsf(inter[0]), fabsf(inter[1]));
float exter[2];
mul_v2_v2fl(exter, inter, div);
exter[0] = roundf(exter[0]);
exter[1] = roundf(exter[1]);
if (i == 0 || i == (CORNER_RESOLUTION - 1)) {
copy_v2_v2(inter, exter);
}
/* Small offset to be able to tell inner and outer vertex apart inside the shader.
* Edge width is specified in the shader. */
mul_v2_fl(inter, 1.0f - 0.0001f);
mul_v2_fl(exter, 1.0f);
GPU_vertbuf_attr_set(vbo, pos, (*vidx)++, inter);
GPU_vertbuf_attr_set(vbo, pos, (*vidx)++, exter);
}
static gpu::Batch *batch_screen_edges_get(int *corner_len)
{
static gpu::Batch *screen_edges_batch = nullptr;
if (screen_edges_batch == nullptr) {
GPUVertFormat format = {0};
uint pos = GPU_vertformat_attr_add(&format, "pos", gpu::VertAttrType::SFLOAT_32_32);
gpu::VertBuf *vbo = GPU_vertbuf_create_with_format(format);
GPU_vertbuf_data_alloc(*vbo, CORNER_RESOLUTION * 2 * 4 + 2);
uint vidx = 0;
for (int corner = 0; corner < 4; corner++) {
for (int c = 0; c < CORNER_RESOLUTION; c++) {
do_vert_pair(vbo, pos, &vidx, corner, c);
}
}
/* close the loop */
do_vert_pair(vbo, pos, &vidx, 0, 0);
screen_edges_batch = GPU_batch_create_ex(GPU_PRIM_TRI_STRIP, vbo, nullptr, GPU_BATCH_OWNS_VBO);
gpu_batch_presets_register(screen_edges_batch);
}
if (corner_len) {
*corner_len = CORNER_RESOLUTION * 2;
}
return screen_edges_batch;
}
#undef CORNER_RESOLUTION
/**
* \brief Screen edges drawing.
*/
static void drawscredge_area(const ScrArea &area, float edge_thickness)
{
rctf rect;
BLI_rctf_rcti_copy(&rect, &area.totrct);
BLI_rctf_pad(&rect, edge_thickness, edge_thickness);
gpu::Batch *batch = batch_screen_edges_get(nullptr);
GPU_batch_program_set_builtin(batch, GPU_SHADER_2D_AREA_BORDERS);
GPU_batch_uniform_4fv(batch, "rect", (float *)&rect);
GPU_batch_draw(batch);
}
void ED_screen_draw_edges(wmWindow *win)
{
bScreen *screen = WM_window_get_active_screen(win);
screen->do_draw = false;
if (screen->state != SCREENNORMAL) {
return;
}
if (screen->areabase.is_single() && win->global_areas.areabase.first == nullptr) {
/* Do not show edges on windows without global areas and with only one editor. */
return;
}
ARegion *region = screen->active_region;
ScrArea *active_area = nullptr;
if (region) {
/* Find active area from active region. */
const int pos[2] = {BLI_rcti_cent_x(&region->winrct), BLI_rcti_cent_y(&region->winrct)};
active_area = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, pos);
}
if (!active_area) {
for (ScrArea &area : screen->areabase) {
AZone *zone = ED_area_actionzone_find_xy(&area, win->runtime->eventstate->xy);
/* Get area from action zone, if not scroll-bar. */
if (zone && zone->type != AZONE_REGION_SCROLL) {
active_area = &area;
break;
}
}
}
if (G.moving & G_TRANSFORM_WM) {
active_area = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, win->runtime->eventstate->xy);
/* We don't want an active area when resizing, otherwise outline for active area flickers, see:
* #136314. */
if (active_area && !win->runtime->drawcalls.is_empty()) {
active_area = nullptr;
}
}
rcti scissor_rect;
BLI_rcti_init_minmax(&scissor_rect);
for (ScrArea &area : screen->areabase) {
BLI_rcti_do_minmax_v(&scissor_rect, int2{area.v1->vec.x, area.v1->vec.y});
BLI_rcti_do_minmax_v(&scissor_rect, int2{area.v3->vec.x, area.v3->vec.y});
}
if (GPU_type_matches_ex(GPU_DEVICE_INTEL_UHD, GPU_OS_UNIX, GPU_DRIVER_ANY, GPU_BACKEND_OPENGL)) {
/* For some reason, on linux + Intel UHD Graphics 620 the driver
* hangs if we don't flush before this. (See #57455) */
GPU_flush();
}
GPU_scissor(scissor_rect.xmin,
scissor_rect.ymin,
BLI_rcti_size_x(&scissor_rect) + 1,
BLI_rcti_size_y(&scissor_rect) + 1);
GPU_scissor_test(true);
float col[4];
ui::theme::get_color_4fv(TH_EDITOR_BORDER, col);
const float edge_thickness = float(U.border_width) * UI_SCALE_FAC;
/* Entire width of the evaluated outline as far as the shader is concerned. */
const float shader_scale = edge_thickness + EDITORRADIUS;
const float corner_coverage[10] = {
0.144f, 0.25f, 0.334f, 0.40f, 0.455, 0.5, 0.538, 0.571, 0.6, 0.625f};
const float shader_width = corner_coverage[U.border_width - 1];
GPU_blend(GPU_BLEND_ALPHA);
int verts_per_corner = 0;
gpu::Batch *batch = batch_screen_edges_get(&verts_per_corner);
GPU_batch_program_set_builtin(batch, GPU_SHADER_2D_AREA_BORDERS);
GPU_batch_uniform_1i(batch, "cornerLen", verts_per_corner);
GPU_batch_uniform_1f(batch, "scale", shader_scale);
GPU_batch_uniform_1f(batch, "width", shader_width);
GPU_batch_uniform_4fv(batch, "color", col);
for (ScrArea &area : screen->areabase) {
drawscredge_area(area, edge_thickness);
}
float outline1[4];
float outline2[4];
rctf bounds;
/* Outset by 1/2 pixel, regardless of UI scale or pixel size. #141550. */
const float padding = 0.5f;
ui::theme::get_color_4fv(TH_EDITOR_OUTLINE, outline1);
ui::theme::get_color_4fv(TH_EDITOR_OUTLINE_ACTIVE, outline2);
ui::draw_roundbox_corner_set(ui::CNR_ALL);
for (ScrArea &area : screen->areabase) {
BLI_rctf_rcti_copy(&bounds, &area.totrct);
BLI_rctf_pad(&bounds, padding, padding);
ui::draw_roundbox_4fv_ex(&bounds,
nullptr,
nullptr,
1.0f,
(&area == active_area) ? outline2 : outline1,
U.pixelsize,
EDITORRADIUS);
}
GPU_blend(GPU_BLEND_NONE);
GPU_scissor_test(false);
}
void screen_draw_move_highlight(const wmWindow *win,
bScreen *screen,
eScreenAxis dir_axis,
float anim_factor)
{
rctf rect = {SHRT_MAX, SHRT_MIN, SHRT_MAX, SHRT_MIN};
for (const ScrEdge &edge : screen->edgebase) {
if (edge.v1->editflag && edge.v2->editflag) {
if (dir_axis == SCREEN_AXIS_H) {
rect.xmin = std::min({rect.xmin, float(edge.v1->vec.x), float(edge.v2->vec.x)});
rect.xmax = std::max({rect.xmax, float(edge.v1->vec.x), float(edge.v2->vec.x)});
rect.ymin = rect.ymax = float(edge.v1->vec.y);
}
else {
rect.ymin = std::min({rect.ymin, float(edge.v1->vec.y), float(edge.v2->vec.y)});
rect.ymax = std::max({rect.ymax, float(edge.v1->vec.y), float(edge.v2->vec.y)});
rect.xmin = rect.xmax = float(edge.v1->vec.x);
}
};
}
rcti window_rect;
WM_window_screen_rect_calc(win, &window_rect);
const float offset = U.border_width * UI_SCALE_FAC;
const float width = std::min(2.0f * offset, 5.0f * UI_SCALE_FAC);
if (dir_axis == SCREEN_AXIS_H) {
BLI_rctf_pad(&rect, -offset, width);
}
else {
BLI_rctf_pad(&rect, width, -offset);
}
float inner[4] = {1.0f, 1.0f, 1.0f, 0.4f * anim_factor};
float outline[4];
ui::theme::get_color_4fv(TH_EDITOR_BORDER, outline);
outline[3] *= anim_factor;
ui::draw_roundbox_corner_set(ui::CNR_ALL);
ui::draw_roundbox_4fv_ex(
&rect, inner, nullptr, 1.0f, outline, width - U.pixelsize, 2.5f * UI_SCALE_FAC);
}
void screen_draw_region_scale_highlight(ARegion *region)
{
rctf rect;
BLI_rctf_rcti_copy(&rect, &region->winrct);
ui::draw_roundbox_corner_set(ui::CNR_ALL);
switch (region->alignment) {
case RGN_ALIGN_RIGHT:
rect.xmax = rect.xmin - U.pixelsize;
rect.xmin = rect.xmax - (4.0f * U.pixelsize);
rect.ymax -= EDITORRADIUS;
rect.ymin += EDITORRADIUS;
break;
case RGN_ALIGN_LEFT:
rect.xmin = rect.xmax + U.pixelsize;
rect.xmax = rect.xmin + (4.0f * U.pixelsize);
rect.ymax -= EDITORRADIUS;
rect.ymin += EDITORRADIUS;
break;
case RGN_ALIGN_TOP:
rect.ymax = rect.ymin - U.pixelsize;
rect.ymin = rect.ymax - (4.0f * U.pixelsize);
rect.xmax -= EDITORRADIUS;
rect.xmin += EDITORRADIUS;
break;
case RGN_ALIGN_BOTTOM:
rect.ymin = rect.ymax + U.pixelsize;
rect.ymax = rect.ymin + (4.0f * U.pixelsize);
rect.xmax -= EDITORRADIUS;
rect.xmin += EDITORRADIUS;
break;
default:
return;
}
float inner[4] = {1.0f, 1.0f, 1.0f, 0.4f};
float outline[4] = {0.0f, 0.0f, 0.0f, 0.3f};
ui::draw_roundbox_4fv_ex(
&rect, inner, nullptr, 1.0f, outline, 1.0f * U.pixelsize, 2.5f * UI_SCALE_FAC);
}
static void screen_draw_area_drag_tip(
const wmWindow *win, int x, int y, const ScrArea *source, const std::string &hint)
{
const char *area_name = IFACE_(ED_area_name(source).c_str());
const uiFontStyle *fstyle = UI_FSTYLE_TOOLTIP;
const bTheme *btheme = ui::theme::theme_get();
const uiWidgetColors *wcol = &btheme->tui.wcol_tooltip;
float col_fg[4], col_bg[4];
rgba_uchar_to_float(col_fg, wcol->text);
rgba_uchar_to_float(col_bg, wcol->inner);
float scale = fstyle->points * UI_SCALE_FAC / UI_DEFAULT_TOOLTIP_POINTS;
BLF_size(fstyle->uifont_id, UI_DEFAULT_TOOLTIP_POINTS * scale);
const float margin = scale * 4.0f;
const float icon_width = (scale * ICON_DEFAULT_WIDTH / 1.4f);
const float icon_gap = scale * 3.0f;
const float line_gap = scale * 5.0f;
const int lheight = BLF_height_max(fstyle->uifont_id);
const int descent = BLF_descender(fstyle->uifont_id);
const float line1_len = BLF_width(fstyle->uifont_id, hint.c_str(), hint.size());
const float line2_len = BLF_width(fstyle->uifont_id, area_name, BLF_DRAW_STR_DUMMY_MAX);
const float width = margin + std::max(line1_len, line2_len + icon_width + icon_gap) + margin;
const float height = margin + lheight + line_gap + lheight + margin;
/* Position of this hint relative to the mouse position. */
const int left = std::min(x + int(5.0f * UI_SCALE_FAC),
WM_window_native_pixel_x(win) - int(width));
const int top = std::max(y - int(7.0f * UI_SCALE_FAC), int(height));
rctf rect;
rect.xmin = left;
rect.xmax = left + width;
rect.ymax = top;
rect.ymin = top - height;
ui::draw_roundbox_corner_set(ui::CNR_ALL);
ui::draw_roundbox_4fv(&rect, true, wcol->roundness * U.widget_unit, col_bg);
ui::icon_draw_ex(left + margin,
top - height + margin + (1.0f * scale),
ED_area_icon(source),
1.4f / scale,
1.0f,
0.0f,
wcol->text,
true,
UI_NO_ICON_OVERLAY_TEXT);
BLF_size(fstyle->uifont_id, UI_DEFAULT_TOOLTIP_POINTS * scale);
BLF_color4fv(fstyle->uifont_id, col_fg);
BLF_position(fstyle->uifont_id, left + margin, top - margin - lheight + (2.0f * scale), 0.0f);
BLF_draw(fstyle->uifont_id, hint.c_str(), hint.size());
BLF_position(fstyle->uifont_id,
left + margin + icon_width + icon_gap,
top - height + margin - descent,
0.0f);
BLF_draw(fstyle->uifont_id, area_name, BLF_DRAW_STR_DUMMY_MAX);
}
static void screen_draw_area_closed(int xmin, int xmax, int ymin, int ymax, float anim_factor)
{
/* Darken the area. */
rctf rect = {float(xmin), float(xmax), float(ymin), float(ymax)};
float darken[4] = {0.0f, 0.0f, 0.0f, 0.7f * anim_factor};
ui::draw_roundbox_corner_set(ui::CNR_ALL);
ui::draw_roundbox_4fv_ex(&rect, darken, nullptr, 1.0f, nullptr, U.pixelsize, EDITORRADIUS);
}
void screen_draw_join_highlight(
const wmWindow *win, ScrArea *sa1, ScrArea *sa2, eScreenDir dir, float anim_factor)
{
if (dir == SCREEN_DIR_NONE || !sa2) {
/* Darken source if docking. Done here because it might be a different window.
* Do not animate this as we don't want to reset every time we change areas. */
screen_draw_area_closed(
sa1->totrct.xmin, sa1->totrct.xmax, sa1->totrct.ymin, sa1->totrct.ymax, 1.0f);
return;
}
/* Rect of the combined areas. */
const bool vertical = SCREEN_DIR_IS_VERTICAL(dir);
rctf combined{};
combined.xmin = vertical ? std::max(sa1->totrct.xmin, sa2->totrct.xmin) :
std::min(sa1->totrct.xmin, sa2->totrct.xmin);
combined.xmax = vertical ? std::min(sa1->totrct.xmax, sa2->totrct.xmax) :
std::max(sa1->totrct.xmax, sa2->totrct.xmax);
combined.ymin = vertical ? std::min(sa1->totrct.ymin, sa2->totrct.ymin) :
std::max(sa1->totrct.ymin, sa2->totrct.ymin);
combined.ymax = vertical ? std::max(sa1->totrct.ymax, sa2->totrct.ymax) :
std::min(sa1->totrct.ymax, sa2->totrct.ymax);
int offset1;
int offset2;
area_getoffsets(sa1, sa2, dir, &offset1, &offset2);
if (offset1 < 0 || offset2 > 0) {
/* Show partial areas that will be closed. */
if (vertical) {
if (sa1->totrct.xmin < combined.xmin) {
screen_draw_area_closed(
sa1->totrct.xmin, combined.xmin, sa1->totrct.ymin, sa1->totrct.ymax, anim_factor);
}
if (sa2->totrct.xmin < combined.xmin) {
screen_draw_area_closed(
sa2->totrct.xmin, combined.xmin, sa2->totrct.ymin, sa2->totrct.ymax, anim_factor);
}
if (sa1->totrct.xmax > combined.xmax) {
screen_draw_area_closed(
combined.xmax, sa1->totrct.xmax, sa1->totrct.ymin, sa1->totrct.ymax, anim_factor);
}
if (sa2->totrct.xmax > combined.xmax) {
screen_draw_area_closed(
combined.xmax, sa2->totrct.xmax, sa2->totrct.ymin, sa2->totrct.ymax, anim_factor);
}
}
else {
if (sa1->totrct.ymin < combined.ymin) {
screen_draw_area_closed(
sa1->totrct.xmin, sa1->totrct.xmax, sa1->totrct.ymin, combined.ymin, anim_factor);
}
if (sa2->totrct.ymin < combined.ymin) {
screen_draw_area_closed(
sa2->totrct.xmin, sa2->totrct.xmax, sa2->totrct.ymin, combined.ymin, anim_factor);
}
if (sa1->totrct.ymax > combined.ymax) {
screen_draw_area_closed(
sa1->totrct.xmin, sa1->totrct.xmax, combined.ymax, sa1->totrct.ymax, anim_factor);
}
if (sa2->totrct.ymax > combined.ymax) {
screen_draw_area_closed(
sa2->totrct.xmin, sa2->totrct.xmax, combined.ymax, sa2->totrct.ymax, anim_factor);
}
}
}
/* Outline the combined area. */
draw_roundbox_corner_set(ui::CNR_ALL);
float outline[4] = {1.0f, 1.0f, 1.0f, 0.4f * anim_factor};
float inner[4] = {1.0f, 1.0f, 1.0f, 0.10f * anim_factor};
ui::draw_roundbox_4fv_ex(&combined, inner, nullptr, 1.0f, outline, U.pixelsize, EDITORRADIUS);
screen_draw_area_drag_tip(win,
win->runtime->eventstate->xy[0],
win->runtime->eventstate->xy[1],
sa1,
IFACE_("Join Areas"));
}
static void rounded_corners(rctf rect, float color[4], int corners)
{
GPUVertFormat *format = immVertexFormat();
const uint pos = GPU_vertformat_attr_add(format, "pos", gpu::VertAttrType::SFLOAT_32_32);
const float rad = EDITORRADIUS;
float vec[4][2] = {
{0.195, 0.02},
{0.55, 0.169},
{0.831, 0.45},
{0.98, 0.805},
};
for (int a = 0; a < 4; a++) {
mul_v2_fl(vec[a], rad);
}
immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR);
immUniformColor4fv(color);
if (corners & ui::CNR_TOP_LEFT) {
immBegin(GPU_PRIM_TRI_FAN, 7);
immVertex2f(pos, rect.xmin - 1, rect.ymax);
immVertex2f(pos, rect.xmin, rect.ymax - rad);
for (int a = 0; a < 4; a++) {
immVertex2f(pos, rect.xmin + vec[a][1], rect.ymax - rad + vec[a][0]);
}
immVertex2f(pos, rect.xmin + rad, rect.ymax);
immEnd();
}
if (corners & ui::CNR_TOP_RIGHT) {
immBegin(GPU_PRIM_TRI_FAN, 7);
immVertex2f(pos, rect.xmax + 1, rect.ymax);
immVertex2f(pos, rect.xmax - rad, rect.ymax);
for (int a = 0; a < 4; a++) {
immVertex2f(pos, rect.xmax - rad + vec[a][0], rect.ymax - vec[a][1]);
}
immVertex2f(pos, rect.xmax, rect.ymax - rad);
immEnd();
}
if (corners & ui::CNR_BOTTOM_RIGHT) {
immBegin(GPU_PRIM_TRI_FAN, 7);
immVertex2f(pos, rect.xmax + 1, rect.ymin);
immVertex2f(pos, rect.xmax, rect.ymin + rad);
for (int a = 0; a < 4; a++) {
immVertex2f(pos, rect.xmax - vec[a][1], rect.ymin + rad - vec[a][0]);
}
immVertex2f(pos, rect.xmax - rad, rect.ymin);
immEnd();
}
if (corners & ui::CNR_BOTTOM_LEFT) {
immBegin(GPU_PRIM_TRI_FAN, 7);
immVertex2f(pos, rect.xmin - 1, rect.ymin);
immVertex2f(pos, rect.xmin + rad, rect.ymin);
for (int a = 0; a < 4; a++) {
immVertex2f(pos, rect.xmin + rad - vec[a][0], rect.ymin + vec[a][1]);
}
immVertex2f(pos, rect.xmin, rect.ymin + rad);
immEnd();
}
immUnbindProgram();
}
void screen_draw_dock_preview(const wmWindow *win,
ScrArea *source,
ScrArea *target,
AreaDockTarget dock_target,
float factor,
int x,
int y,
float anim_factor)
{
if (dock_target == AreaDockTarget::None) {
return;
}
float outline[4] = {1.0f, 1.0f, 1.0f, 0.4f * anim_factor};
float inner[4] = {1.0f, 1.0f, 1.0f, 0.1f * anim_factor};
float border[4];
ui::theme::get_color_4fv(TH_EDITOR_BORDER, border);
border[3] *= anim_factor;
ui::draw_roundbox_corner_set(ui::CNR_ALL);
float half_line_width = float(U.border_width) * UI_SCALE_FAC;
rctf dest;
rctf remainder;
BLI_rctf_rcti_copy(&dest, &target->totrct);
BLI_rctf_rcti_copy(&remainder, &target->totrct);
float split;
int corners = ui::CNR_NONE;
if (dock_target == AreaDockTarget::Right) {
split = std::min(dest.xmin + target->winx * (1.0f - factor),
dest.xmax - AREAMINX * UI_SCALE_FAC);
dest.xmin = split + half_line_width;
remainder.xmax = split - half_line_width;
corners = ui::CNR_TOP_LEFT | ui::CNR_BOTTOM_LEFT;
}
else if (dock_target == AreaDockTarget::Left) {
split = std::max(dest.xmax - target->winx * (1.0f - factor),
dest.xmin + AREAMINX * UI_SCALE_FAC);
dest.xmax = split - half_line_width;
remainder.xmin = split + half_line_width;
corners = ui::CNR_TOP_RIGHT | ui::CNR_BOTTOM_RIGHT;
}
else if (dock_target == AreaDockTarget::Top) {
split = std::min(dest.ymin + target->winy * (1.0f - factor),
dest.ymax - HEADERY * UI_SCALE_FAC);
dest.ymin = split + half_line_width;
remainder.ymax = split - half_line_width;
corners = ui::CNR_BOTTOM_RIGHT | ui::CNR_BOTTOM_LEFT;
}
else if (dock_target == AreaDockTarget::Bottom) {
split = std::max(dest.ymax - target->winy * (1.0f - factor),
dest.ymin + HEADERY * UI_SCALE_FAC);
dest.ymax = split - half_line_width;
remainder.ymin = split + half_line_width;
corners = ui::CNR_TOP_RIGHT | ui::CNR_TOP_LEFT;
}
rounded_corners(dest, border, corners);
ui::draw_roundbox_4fv_ex(&dest, inner, nullptr, 1.0f, outline, U.pixelsize, EDITORRADIUS);
if (dock_target != AreaDockTarget::Center) {
/* Darken the split position itself. */
if (ELEM(dock_target, AreaDockTarget::Right, AreaDockTarget::Left)) {
dest.xmin = split - half_line_width;
dest.xmax = split + half_line_width;
}
else {
dest.ymin = split - half_line_width;
dest.ymax = split + half_line_width;
}
ui::draw_roundbox_4fv(&dest, true, 0.0f, border);
}
screen_draw_area_drag_tip(win,
x,
y,
source,
dock_target == AreaDockTarget::Center ? IFACE_("Replace this area") :
IFACE_("Move area here"));
}
void screen_draw_split_preview(ScrArea *area, const eScreenAxis dir_axis, const float factor)
{
float outline[4] = {1.0f, 1.0f, 1.0f, 0.4f};
float inner[4] = {1.0f, 1.0f, 1.0f, 0.10f};
float border[4];
ui::theme::get_color_4fv(TH_EDITOR_BORDER, border);
draw_roundbox_corner_set(ui::CNR_ALL);
rctf rect;
BLI_rctf_rcti_copy(&rect, &area->totrct);
if (factor < 0.0001 || factor > 0.9999) {
/* Highlight the entire area. */
ui::draw_roundbox_4fv_ex(&rect, inner, nullptr, 1.0f, outline, U.pixelsize, EDITORRADIUS);
return;
}
float x = (1 - factor) * rect.xmin + factor * rect.xmax;
float y = (1 - factor) * rect.ymin + factor * rect.ymax;
x = std::clamp(x, rect.xmin, rect.xmax);
y = std::clamp(y, rect.ymin, rect.ymax);
float half_line_width = float(U.border_width) * UI_SCALE_FAC;
/* Outlined rectangle to left/above split position. */
rect.xmax = (dir_axis == SCREEN_AXIS_V) ? x - half_line_width : rect.xmax;
rect.ymax = (dir_axis == SCREEN_AXIS_H) ? y - half_line_width : rect.ymax;
rounded_corners(rect,
border,
(dir_axis == SCREEN_AXIS_H) ? ui::CNR_TOP_RIGHT | ui::CNR_TOP_LEFT :
ui::CNR_BOTTOM_RIGHT | ui::CNR_TOP_RIGHT);
ui::draw_roundbox_4fv_ex(&rect, inner, nullptr, 1.0f, outline, U.pixelsize, EDITORRADIUS);
/* Outlined rectangle to right/below split position. */
if (dir_axis == SCREEN_AXIS_H) {
rect.ymin = y + half_line_width;
rect.ymax = area->totrct.ymax;
}
else {
rect.xmin = x + half_line_width;
rect.xmax = area->totrct.xmax;
}
rounded_corners(rect,
border,
(dir_axis == SCREEN_AXIS_H) ? ui::CNR_BOTTOM_RIGHT | ui::CNR_BOTTOM_LEFT :
ui::CNR_BOTTOM_LEFT | ui::CNR_TOP_LEFT);
ui::draw_roundbox_4fv_ex(&rect, inner, nullptr, 1.0f, outline, U.pixelsize, EDITORRADIUS);
/* Darken the split position itself. */
if (dir_axis == SCREEN_AXIS_H) {
rect.ymin = y - half_line_width;
rect.ymax = y + half_line_width;
}
else {
rect.xmin = x - half_line_width;
rect.xmax = x + half_line_width;
}
ui::draw_roundbox_4fv(&rect, true, 0.0f, border);
}
struct AreaAnimateHighlightData {
wmWindow *win;
bScreen *screen;
rctf rect;
float inner[4];
float outline[4];
double start_time;
double end_time;
void *draw_callback;
};
static void area_animate_highlight_cb(const wmWindow * /*win*/, void *userdata)
{
const AreaAnimateHighlightData *data = static_cast<const AreaAnimateHighlightData *>(userdata);
double now = BLI_time_now_seconds();
if (now > data->end_time) {
WM_draw_cb_exit(data->win, data->draw_callback);
MEM_delete(const_cast<AreaAnimateHighlightData *>(data));
data = nullptr;
return;
}
const float factor = pow((now - data->start_time) / (data->end_time - data->start_time), 2);
const bool do_inner = data->inner[3] > 0.0f;
const bool do_outline = data->outline[3] > 0.0f;
float inner_color[4];
if (do_inner) {
inner_color[0] = data->inner[0];
inner_color[1] = data->inner[1];
inner_color[2] = data->inner[2];
inner_color[3] = (1.0f - factor) * data->inner[3];
}
float outline_color[4];
if (do_outline) {
outline_color[0] = data->outline[0];
outline_color[1] = data->outline[1];
outline_color[2] = data->outline[2];
outline_color[3] = (1.0f - factor) * data->outline[3];
}
ui::draw_roundbox_corner_set(ui::CNR_ALL);
ui::draw_roundbox_4fv_ex(&data->rect,
do_inner ? inner_color : nullptr,
nullptr,
1.0f,
do_outline ? outline_color : nullptr,
U.pixelsize,
EDITORRADIUS);
data->screen->do_refresh = true;
}
void screen_animate_area_highlight(wmWindow *win,
bScreen *screen,
const rcti *rect,
float inner[4],
float outline[4],
float seconds)
{
/* Disabling for now, see #147487. This can cause memory leaks since the
* data is only freed when the animation completes, which might not happen
* during automated tests. Freeing wmWindow->runtime->drawcalls on window close might
* be enough, but will have to be investigated. */
return;
AreaAnimateHighlightData *data = MEM_new_zeroed<AreaAnimateHighlightData>(
"screen_animate_area_highlight");
data->win = win;
data->screen = screen;
BLI_rctf_rcti_copy(&data->rect, rect);
if (inner) {
copy_v4_v4(data->inner, inner);
}
if (outline) {
copy_v4_v4(data->outline, outline);
}
data->start_time = BLI_time_now_seconds();
data->end_time = data->start_time + seconds;
data->draw_callback = WM_draw_cb_activate(win, area_animate_highlight_cb, data);
}
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,552 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* 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_vector.h"
#include "BLI_rect.h"
#include "BKE_screen.hh"
#include "DNA_screen_types.h"
#include "DNA_windowmanager_types.h"
#include "ED_screen.hh"
#include "MEM_guardedalloc.h"
#include "WM_api.hh"
#include "screen_intern.hh"
namespace blender {
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_new<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_new<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)
{
CLAMP_MIN(safety, 2);
for (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 = std::min(se.v1->vec.x, se.v2->vec.x);
max = std::max(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 = std::min(se.v1->vec.y, se.v2->vec.y);
max = std::max(se.v1->vec.y, se.v2->vec.y);
if (abs(mx - se.v1->vec.x) <= safety && my >= min && my <= max) {
return &se;
}
}
}
}
return nullptr;
}
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 nullptr;
}
/* 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, BORDERPADDING);
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, int(BORDERPADDING_GLOBAL));
}
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};
for (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! */
for (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... */
if (facy > 1) {
/* Keep timeline small in video edit workspace. */
for (ScrArea &area : screen->areabase) {
const int border_width = int(ceil(float(U.border_width) * UI_SCALE_FAC));
int min = ED_area_headersize() + border_width;
if (area.v1->vec.y > screen_rect->ymin) {
min += border_width;
}
if (area.spacetype == SPACE_ACTION && area.v1->vec.y == screen_rect->ymin &&
screen_geom_area_height(&area) <= int(min * 1.5f))
{
ScrEdge *se = BKE_screen_find_edge(screen, area.v2, area.v3);
if (se) {
const int yval = area.v1->vec.y + min - 1;
screen_geom_select_connected_edge(win, se);
/* all selected vertices get the right offset */
for (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;
}
}
}
}
}
}
}
/* Make each window at least ED_area_headersize() high. This
* should be done whether we are increasing or decreasing the
* vertical size since this is called on file load, not just
* during resize operations. */
for (ScrArea &area : screen->areabase) {
const int border_width = int(ceil(float(U.border_width) * UI_SCALE_FAC));
int min = ED_area_headersize() + border_width + border_width - U.pixelsize;
if (area.v3->vec.y >= (screen_rect->ymax - 1)) {
/* Area aligned to top screen edge. */
min = ED_area_headersize() + border_width;
}
else if (area.v4->vec.y <= (screen_rect->ymin + 1)) {
/* Area aligned to bottom screen edge. */
min = ED_area_headersize() + border_width + 1;
}
const int height = screen_geom_area_height(&area);
if (height < min) {
/* 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 - min;
screen_geom_select_connected_edge(win, se);
/* all selected vertices get the right offset */
for (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. */
for (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 * UI_SCALE_FAC;
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;
for (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;
}
}
}
}
}
}
bool screen_geom_edge_can_extend(const wmWindow *win, ScrEdge *edge)
{
bScreen *screen = WM_window_get_active_screen(win);
bool can_extend = false;
const eScreenAxis dir_axis = (edge->v1->vec.x == edge->v2->vec.x) ? SCREEN_AXIS_V :
SCREEN_AXIS_H;
screen_geom_select_connected_edge(win, edge);
for (ScrEdge &se : screen->edgebase) {
if (se.v1->flag + se.v2->flag != 0) {
continue;
}
if (dir_axis == SCREEN_AXIS_H) {
for (ScrVert &v : screen->vertbase) {
if (v.flag && v.vec.x == se.v1->vec.x &&
(abs(v.vec.y - se.v1->vec.y) < EDGE_ALIGN_TOLERANCE ||
abs(v.vec.y - se.v2->vec.y) < EDGE_ALIGN_TOLERANCE))
{
se.v1->flag = se.v2->flag = 1;
can_extend = true;
break;
}
}
}
else if (dir_axis == SCREEN_AXIS_V) {
for (ScrVert &v : screen->vertbase) {
if (v.flag && v.vec.y == se.v1->vec.y &&
(abs(v.vec.x - se.v1->vec.x) < EDGE_ALIGN_TOLERANCE ||
abs(v.vec.x - se.v2->vec.x) < EDGE_ALIGN_TOLERANCE))
{
se.v1->flag = se.v2->flag = 1;
can_extend = true;
break;
}
}
}
}
ED_screen_verts_iter(win, screen, sv)
{
sv->flag = 0;
}
return can_extend;
}
void screen_geom_select_extended_edge(const wmWindow *win, ScrEdge *edge)
{
bScreen *screen = WM_window_get_active_screen(win);
const eScreenAxis dir_axis = (edge->v1->vec.x == edge->v2->vec.x) ? SCREEN_AXIS_V :
SCREEN_AXIS_H;
ED_screen_verts_iter(win, screen, sv)
{
sv->flag = 0;
}
for (ScrVert &v : screen->vertbase) {
if (dir_axis == SCREEN_AXIS_H) {
if (abs(v.vec.y - edge->v1->vec.y) < EDGE_ALIGN_TOLERANCE) {
v.flag = 1;
}
}
else if (dir_axis == SCREEN_AXIS_V) {
if (abs(v.vec.x - edge->v1->vec.x) < EDGE_ALIGN_TOLERANCE) {
v.flag = 1;
}
}
}
}
void screen_geom_edge_aligned_merge(const wmWindow *win, ScrEdge *edge)
{
bScreen *screen = WM_window_get_active_screen(win);
screen_geom_select_extended_edge(win, edge);
const eScreenAxis dir_axis = (edge->v1->vec.x == edge->v2->vec.x) ? SCREEN_AXIS_V :
SCREEN_AXIS_H;
/* Align the vertices if close. */
for (ScrVert &v : screen->vertbase) {
if (dir_axis == SCREEN_AXIS_V && abs(v.vec.x - edge->v2->vec.x) < EDGE_ALIGN_TOLERANCE) {
v.vec.x = edge->v2->vec.x;
}
else if (abs(v.vec.y - edge->v2->vec.y) < EDGE_ALIGN_TOLERANCE) {
v.vec.y = edge->v2->vec.y;
}
}
for (ScrVert &v : screen->vertbase) {
if (v.flag == 1 && v.newv == nullptr) { /* !!! */
ScrVert *v1 = v.next;
while (v1) {
if (v1->newv == nullptr) { /* !?! */
if (abs(v1->vec.x - v.vec.x) < EDGE_ALIGN_TOLERANCE &&
abs(v1->vec.y - v.vec.y) < EDGE_ALIGN_TOLERANCE)
{
v1->newv = &v;
}
}
v1 = v1->next;
}
}
}
/* Replace pointers in edges and faces. */
for (ScrEdge &se : screen->edgebase) {
if (se.v1->newv) {
se.v1 = se.v1->newv;
}
if (se.v2->newv) {
se.v2 = se.v2->newv;
}
BKE_screen_sort_scrvert(&(se.v1), &(se.v2));
}
for (ScrArea &area : screen->areabase) {
if (area.v1->newv) {
area.v1 = area.v1->newv;
}
if (area.v2->newv) {
area.v2 = area.v2->newv;
}
if (area.v3->newv) {
area.v3 = area.v3->newv;
}
if (area.v4->newv) {
area.v4 = area.v4->newv;
}
}
/* Remove. */
for (ScrVert &v : screen->vertbase.items_mutable()) {
if (v.newv) {
BLI_remlink(&screen->vertbase, &v);
MEM_delete(&v);
}
}
ED_screen_verts_iter(win, screen, sv)
{
sv->flag = 0;
}
}
} // namespace blender

View File

@@ -0,0 +1,276 @@
/* SPDX-FileCopyrightText: 2008 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edscr
*/
#pragma once
#include "DNA_space_types.h"
namespace blender {
struct ARegion;
struct AZone;
struct ReportList;
struct bContext;
struct bContextDataResult;
struct bScreen;
struct Main;
struct rcti;
struct ScrArea;
struct ScrAreaMap;
struct ScrEdge;
struct ScrVert;
struct WorkSpaceLayout;
struct wmOperatorType;
struct wmWindow;
struct wmWindowManager;
/* internal exports only */
enum eScreenDir {
/** This can mean unset, unknown or invalid. */
SCREEN_DIR_NONE = -1,
/** West/Left. */
SCREEN_DIR_W = 0,
/** North/Up. */
SCREEN_DIR_N = 1,
/** East/Right. */
SCREEN_DIR_E = 2,
/** South/Down. */
SCREEN_DIR_S = 3,
};
#define SCREEN_DIR_IS_VERTICAL(dir) (ELEM(dir, SCREEN_DIR_N, SCREEN_DIR_S))
#define SCREEN_DIR_IS_HORIZONTAL(dir) (ELEM(dir, SCREEN_DIR_W, SCREEN_DIR_E))
enum eScreenAxis {
/** Horizontal. */
SCREEN_AXIS_H = 'h',
/** Vertical. */
SCREEN_AXIS_V = 'v',
};
enum class AreaDockTarget {
None,
Right, /* Right diagonal quadrant of area. */
Left, /* Left diagonal quadrant of area. */
Top, /* Top diagonal quadrant of area. */
Bottom, /* Bottom diagonal quadrant of area. */
Center, /* Middle portion of area. */
};
#define AZONEFADEIN (5.0f * U.widget_unit) /* when #AZone is totally visible */
#define AZONEFADEOUT (6.5f * U.widget_unit) /* when we start seeing the #AZone */
/* Edges must be within these to allow joining. */
#define AREAJOINTOLERANCEX (AREAMINX * UI_SCALE_FAC)
#define AREAJOINTOLERANCEY (HEADERY * UI_SCALE_FAC)
/* Edges must be within this amount to allow aligned edge merging and moving. */
#define EDGE_ALIGN_TOLERANCE (7 * UI_SCALE_FAC)
/**
* Expanded interaction influence of area borders.
*/
#define BORDERPADDING (U.border_width * UI_SCALE_FAC + 3.0f * UI_SCALE_FAC)
/**
* Number of pixels of the area border corner radius.
*/
#define EDITORRADIUS (6.0f * UI_SCALE_FAC)
/* Less expansion needed for global edges. */
#define BORDERPADDING_GLOBAL (3.0f * UI_SCALE_FAC)
#define AREA_CLOSE_FADEOUT 0.15f /* seconds */
#define AREA_DOCK_FADEOUT 0.15f /* seconds */
#define AREA_DOCK_FADEIN 0.15f /* seconds */
#define AREA_JOIN_FADEOUT 0.15f /* seconds */
#define AREA_SPLIT_FADEOUT 0.15f /* seconds */
#define AREA_MOVE_LINE_FADEIN 0.1f /* seconds */
#define AREA_MOVE_LINE_FADEOUT 0.15f /* seconds */
/* `area.cc` */
/**
* We swap spaces for full-screen to keep all allocated data area vertices were set.
*/
void ED_area_data_copy(ScrArea *area_dst, ScrArea *area_src, bool do_free);
void ED_area_data_swap(ScrArea *area_dst, ScrArea *area_src);
/* for quick toggle, can skip fades */
void region_toggle_hidden(bContext *C, ARegion *region, bool do_fade);
/* `screen_draw.cc` */
/**
* Visual indication of the two areas involved in a proposed join.
*
* \param sa1: Area from which the resultant originates.
* \param sa2: Target area that will be replaced.
*/
void screen_draw_join_highlight(
const wmWindow *win, ScrArea *sa1, ScrArea *sa2, eScreenDir dir, float anim_factor);
void screen_draw_dock_preview(const wmWindow *win,
ScrArea *source,
ScrArea *target,
AreaDockTarget dock_target,
float factor,
int x,
int y,
float anim_factor);
void screen_draw_split_preview(ScrArea *area, eScreenAxis dir_axis, float factor);
void screen_draw_move_highlight(const wmWindow *win,
bScreen *screen,
eScreenAxis dir_axis,
float anim_factor);
void screen_draw_region_scale_highlight(ARegion *region);
void screen_animate_area_highlight(wmWindow *win,
bScreen *screen,
const rcti *rect,
float inner[4],
float outline[4],
float seconds);
/* `screen_edit.cc` */
/**
* Empty screen, with 1 dummy area without space-data. Uses window size.
*/
bScreen *screen_add(Main *bmain, const char *name, const rcti *rect);
/**
* Prepare a newly created screen for initializing it as active screen.
*/
void screen_new_activate_prepare(const wmWindow *win, bScreen *screen_new);
void screen_change_update(bContext *C, wmWindow *win, bScreen *screen);
/**
* \return the screen to activate.
* \warning The returned screen may not always equal \a screen_new!
*/
void screen_change_prepare(
bScreen *screen_old, bScreen *screen_new, Main *bmain, bContext *C, wmWindow *win);
ScrArea *area_split(
const wmWindow *win, bScreen *screen, ScrArea *area, eScreenAxis dir_axis, float fac);
/**
* Join any two neighboring areas. Might involve complex changes.
*/
int screen_area_join(
bContext *C, ReportList *reports, bScreen *screen, ScrArea *sa1, ScrArea *sa2);
/**
* with `sa_a` as center, `sa_b` is located at: 0=W, 1=N, 2=E, 3=S
* -1 = not valid check.
* used with join operator.
*/
eScreenDir area_getorientation(ScrArea *sa_a, ScrArea *sa_b);
/**
* Get alignment offset of adjacent areas. 'dir' value is like #area_getorientation().
*/
void area_getoffsets(ScrArea *sa_a, ScrArea *sa_b, eScreenDir dir, int *r_offset1, int *r_offset2);
/**
* Close a screen area, allowing most-aligned neighbor to take its place.
* not_area is optional area to NOT join into.
*/
bool screen_area_close(
bContext *C, ReportList *reports, bScreen *screen, ScrArea *area, ScrArea *not_area = nullptr);
void screen_area_spacelink_add(const Scene *scene, ScrArea *area, eSpace_Type space_type);
AZone *ED_area_actionzone_find_xy(ScrArea *area, const int xy[2]);
/**
* \return true if any region polling state changed, and an area re-init is needed.
*/
bool area_regions_poll(bContext *C, const bScreen *screen, ScrArea *area);
/* `screen_geometry.cc` */
int screen_geom_area_height(const ScrArea *area);
int screen_geom_area_width(const ScrArea *area);
ScrVert *screen_geom_vertex_add_ex(ScrAreaMap *area_map, short x, short y);
ScrVert *screen_geom_vertex_add(bScreen *screen, short x, short y);
ScrEdge *screen_geom_edge_add_ex(ScrAreaMap *area_map, ScrVert *v1, ScrVert *v2);
ScrEdge *screen_geom_edge_add(bScreen *screen, ScrVert *v1, ScrVert *v2);
bool screen_geom_edge_is_horizontal(ScrEdge *se);
/**
* \param bounds_rect: Either window or screen bounds.
* Used to exclude edges along window/screen edges.
*/
ScrEdge *screen_geom_area_map_find_active_scredge(const ScrAreaMap *area_map,
const rcti *bounds_rect,
int mx,
int my,
int safety = BORDERPADDING);
/**
* Need win size to make sure not to include edges along screen edge.
*/
ScrEdge *screen_geom_find_active_scredge(const wmWindow *win,
const bScreen *screen,
int mx,
int my);
/**
* \brief Main screen-layout calculation function.
*
* * Scale areas nicely on window size and DPI changes.
* * Ensure areas have a minimum height.
* * Correctly set global areas to their fixed height.
*/
void screen_geom_vertices_scale(const wmWindow *win, bScreen *screen);
/**
* \return 0 if no split is possible, otherwise the screen-coordinate at which to split.
*/
short screen_geom_find_area_split_point(const ScrArea *area,
const rcti *window_rect,
eScreenAxis dir_axis,
float fac);
/**
* Select all edges that are directly or indirectly connected to \a edge.
*/
void screen_geom_select_connected_edge(const wmWindow *win, ScrEdge *edge);
/**
* Select all edges that are aligned with \a edge.
*/
void screen_geom_select_extended_edge(const wmWindow *win, ScrEdge *edge);
/**
* True if the edge can be extended.
*/
bool screen_geom_edge_can_extend(const wmWindow *win, ScrEdge *edge);
/**
* Merge aligned edges into a single edge.
*/
void screen_geom_edge_aligned_merge(const wmWindow *win, ScrEdge *edge);
/* `screen_context.cc` */
/**
* Entry point for the screen context.
*/
int ed_screen_context(const bContext *C, const char *member, bContextDataResult *result);
extern "C" const char *screen_context_dir[]; /* doc access */
/* `screen_ops.cc` */
/**
* Stop animation playback in the given screen.
* If there is no animation playing back in that screen, this is a no-op.
*/
void screen_stop_playback(Main *bmain, wmWindowManager *wm, wmWindow *win, bScreen *screen);
/* `screendump.cc` */
void SCREEN_OT_screenshot(wmOperatorType *ot);
void SCREEN_OT_screenshot_area(wmOperatorType *ot);
/* `workspace_layout_edit.cc` */
bool workspace_layout_set_poll(const WorkSpaceLayout *layout);
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,403 @@
/* SPDX-FileCopyrightText: 2009 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup spview3d
*/
#include <cfloat>
#include <cstring>
#include "MEM_guardedalloc.h"
#include "AS_asset_library.hh"
#include "AS_asset_representation.hh"
#include "BLI_listbase.h"
#include "BLI_string_utf8.h"
#include "BLI_utildefines.h"
#include "BLT_translation.hh"
#include "BKE_blender_user_menu.hh"
#include "BKE_context.hh"
#include "BKE_idprop.hh"
#include "BKE_screen.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "ED_asset_list.hh"
#include "ED_asset_menu_utils.hh"
#include "ED_screen.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "RNA_access.hh"
#include "RNA_path.hh"
#include "RNA_prototypes.hh"
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Internal Utilities
* \{ */
static const char *screen_menu_context_string(const bContext *C, const SpaceLink *sl)
{
if (sl->spacetype == SPACE_NODE) {
const SpaceNode *snode = reinterpret_cast<const SpaceNode *>(sl);
return snode->tree_idname;
}
return CTX_data_mode_string(C);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Menu Type
* \{ */
bUserMenu **ED_screen_user_menus_find(const bContext *C, uint *r_len)
{
SpaceLink *sl = CTX_wm_space_data(C);
if (sl == nullptr) {
*r_len = 0;
return nullptr;
}
const char *context_mode = CTX_data_mode_string(C);
const char *context = screen_menu_context_string(C, sl);
uint array_len = 3;
bUserMenu **um_array = MEM_new_array_zeroed<bUserMenu *>(array_len, __func__);
um_array[0] = BKE_blender_user_menu_find(&U.user_menus, sl->spacetype, context);
um_array[1] = (sl->spacetype != SPACE_TOPBAR) ?
BKE_blender_user_menu_find(&U.user_menus, SPACE_TOPBAR, context_mode) :
nullptr;
um_array[2] = (sl->spacetype == SPACE_VIEW3D) ?
BKE_blender_user_menu_find(&U.user_menus, SPACE_PROPERTIES, context_mode) :
nullptr;
*r_len = array_len;
return um_array;
}
bUserMenu *ED_screen_user_menu_ensure(bContext *C)
{
SpaceLink *sl = CTX_wm_space_data(C);
const char *context = screen_menu_context_string(C, sl);
return BKE_blender_user_menu_ensure(&U.user_menus, sl->spacetype, context);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Menu Item
* \{ */
bUserMenuItem_Op *ED_screen_user_menu_item_find_operator(ListBaseT<bUserMenuItem> *lb,
const wmOperatorType *ot,
IDProperty *prop,
const char *op_prop_enum,
wm::OpCallContext opcontext)
{
for (bUserMenuItem &umi : *lb) {
if (umi.type == USER_MENU_TYPE_OPERATOR) {
bUserMenuItem_Op *umi_op = reinterpret_cast<bUserMenuItem_Op *>(&umi);
const bool is_strict = prop && umi_op->prop;
const bool ok_idprop = IDP_EqualsProperties_ex(prop, umi_op->prop, is_strict);
const bool ok_prop_enum = (umi_op->op_prop_enum[0] != '\0') ?
STREQ(umi_op->op_prop_enum, op_prop_enum) :
true;
if (STREQ(ot->idname, umi_op->op_idname) &&
(opcontext == wm::OpCallContext(umi_op->opcontext)) && ok_idprop && ok_prop_enum)
{
return umi_op;
}
}
}
return nullptr;
}
bUserMenuItem_Menu *ED_screen_user_menu_item_find_menu(ListBaseT<bUserMenuItem> *lb,
const MenuType *mt)
{
for (bUserMenuItem &umi : *lb) {
if (umi.type == USER_MENU_TYPE_MENU) {
bUserMenuItem_Menu *umi_mt = reinterpret_cast<bUserMenuItem_Menu *>(&umi);
if (STREQ(mt->idname, umi_mt->mt_idname)) {
return umi_mt;
}
}
}
return nullptr;
}
bUserMenuItem_Prop *ED_screen_user_menu_item_find_prop(ListBaseT<bUserMenuItem> *lb,
const char *context_data_path,
const char *prop_id,
int prop_index)
{
for (bUserMenuItem &umi : *lb) {
if (umi.type == USER_MENU_TYPE_PROP) {
bUserMenuItem_Prop *umi_pr = reinterpret_cast<bUserMenuItem_Prop *>(&umi);
if (STREQ(context_data_path, umi_pr->context_data_path) && STREQ(prop_id, umi_pr->prop_id) &&
(prop_index == umi_pr->prop_index))
{
return umi_pr;
}
}
}
return nullptr;
}
void ED_screen_user_menu_item_add_operator(ListBaseT<bUserMenuItem> *lb,
const char *ui_name,
const wmOperatorType *ot,
const IDProperty *prop,
const char *op_prop_enum,
wm::OpCallContext opcontext)
{
bUserMenuItem_Op *umi_op = reinterpret_cast<bUserMenuItem_Op *>(
BKE_blender_user_menu_item_add(lb, USER_MENU_TYPE_OPERATOR));
umi_op->opcontext = int8_t(opcontext);
if (!STREQ(ui_name, ot->name)) {
STRNCPY_UTF8(umi_op->item.ui_name, ui_name);
}
STRNCPY_UTF8(umi_op->op_idname, ot->idname);
STRNCPY_UTF8(umi_op->op_prop_enum, op_prop_enum);
umi_op->prop = prop ? IDP_CopyProperty(prop) : nullptr;
}
void ED_screen_user_menu_item_add_menu(ListBaseT<bUserMenuItem> *lb,
const char *ui_name,
const MenuType *mt)
{
bUserMenuItem_Menu *umi_mt = reinterpret_cast<bUserMenuItem_Menu *>(
BKE_blender_user_menu_item_add(lb, USER_MENU_TYPE_MENU));
if (!STREQ(ui_name, mt->label)) {
STRNCPY_UTF8(umi_mt->item.ui_name, ui_name);
}
STRNCPY_UTF8(umi_mt->mt_idname, mt->idname);
}
void ED_screen_user_menu_item_add_prop(ListBaseT<bUserMenuItem> *lb,
const char *ui_name,
const char *context_data_path,
const char *prop_id,
int prop_index)
{
bUserMenuItem_Prop *umi_pr = reinterpret_cast<bUserMenuItem_Prop *>(
BKE_blender_user_menu_item_add(lb, USER_MENU_TYPE_PROP));
STRNCPY_UTF8(umi_pr->item.ui_name, ui_name);
STRNCPY_UTF8(umi_pr->context_data_path, context_data_path);
STRNCPY_UTF8(umi_pr->prop_id, prop_id);
umi_pr->prop_index = prop_index;
}
void ED_screen_user_menu_item_remove(ListBaseT<bUserMenuItem> *lb, bUserMenuItem *umi)
{
BLI_remlink(lb, umi);
BKE_blender_user_menu_item_free(umi);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Menu Definition
* \{ */
static bool all_loading_finished()
{
AssetLibraryReference all_library_ref = asset_system::all_library_reference();
return ed::asset::list::is_loaded(&all_library_ref);
}
/**
* When adding operators that reference an asset (see
* `ed::asset::operator_asset_reference_props_is_set`) make sure the asset libraries are loaded.
*/
static void handle_operator_asset_reference_props(const bContext &C,
bUserMenuItem_Op &umi_op,
ui::Layout &row,
wmOperatorType *ot,
int &r_icon,
bool &r_add_operator)
{
if (!umi_op.prop) {
return;
}
PointerRNA opptr = WM_operator_properties_create_ptr(ot);
opptr.data = bke::idprop::create_group("wmOperatorProperties").release();
IDP_CopyPropertyContent(opptr.data_as<IDProperty>(), umi_op.prop);
if (ed::asset::operator_asset_reference_props_is_set(opptr)) {
const bool loading_finished = all_loading_finished();
if (!loading_finished) {
row.label(IFACE_("Loading Asset Libraries"), ICON_INFO);
r_add_operator = false;
}
else {
/* Set `check_context_asset` to false because we're setting the context pointer after getting
* the asset from the operator properties. */
const asset_system::AssetRepresentation *asset =
ed::asset::operator_asset_reference_props_get_asset_from_all_library(
C, opptr, CTX_wm_reports(&C));
if (asset) {
if (asset->is_online_only()) {
r_icon = ICON_INTERNET;
}
PointerRNA asset_ptr = RNA_pointer_create_discrete(
nullptr,
RNA_AssetRepresentation,
const_cast<asset_system::AssetRepresentation *>(asset));
row.context_ptr_set("asset", &asset_ptr);
}
else {
r_add_operator = false;
}
}
}
WM_operator_properties_free(&opptr);
}
static void screen_user_menu_draw(const bContext *C, Menu *menu)
{
/* Enable when we have the ability to edit menus. */
const bool show_missing = false;
char label[512];
uint um_array_len;
bUserMenu **um_array = ED_screen_user_menus_find(C, &um_array_len);
bool is_empty = true;
for (int um_index = 0; um_index < um_array_len; um_index++) {
bUserMenu *um = um_array[um_index];
if (um == nullptr) {
continue;
}
for (bUserMenuItem &umi : um->items) {
std::optional<StringRefNull> ui_name = umi.ui_name[0] ?
std::make_optional<StringRefNull>(umi.ui_name) :
std::nullopt;
if (umi.type == USER_MENU_TYPE_OPERATOR) {
bUserMenuItem_Op *umi_op = reinterpret_cast<bUserMenuItem_Op *>(&umi);
if (wmOperatorType *ot = WM_operatortype_find(umi_op->op_idname, false)) {
if (ui_name) {
ui_name = CTX_IFACE_(ot->translation_context, ui_name->c_str());
}
if (umi_op->op_prop_enum[0] == '\0') {
ui::Layout &row = menu->layout->row(true);
int icon = ICON_NONE;
bool add_operator = true;
handle_operator_asset_reference_props(*C, *umi_op, row, ot, icon, add_operator);
if (add_operator) {
PointerRNA ptr = row.op(
ot, ui_name, icon, wm::OpCallContext(umi_op->opcontext), UI_ITEM_NONE);
if (umi_op->prop) {
IDP_CopyPropertyContent(ptr.data_as<IDProperty>(), umi_op->prop);
}
}
}
else {
/* umi_op->prop could be used to set other properties but it's currently unsupported.
*/
menu->layout->op_menu_enum(C, ot, umi_op->op_prop_enum, ui_name, ICON_NONE);
}
is_empty = false;
}
else {
if (show_missing) {
SNPRINTF_UTF8(label, RPT_("Missing: %s"), umi_op->op_idname);
menu->layout->label(label, ICON_NONE);
}
}
}
else if (umi.type == USER_MENU_TYPE_MENU) {
bUserMenuItem_Menu *umi_mt = reinterpret_cast<bUserMenuItem_Menu *>(&umi);
MenuType *mt = WM_menutype_find(umi_mt->mt_idname, false);
if (mt != nullptr) {
menu->layout->menu(mt, ui_name, ICON_NONE);
is_empty = false;
}
else {
if (show_missing) {
SNPRINTF_UTF8(label, RPT_("Missing: %s"), umi_mt->mt_idname);
menu->layout->label(label, ICON_NONE);
}
}
}
else if (umi.type == USER_MENU_TYPE_PROP) {
bUserMenuItem_Prop *umi_pr = reinterpret_cast<bUserMenuItem_Prop *>(&umi);
char *data_path = strchr(umi_pr->context_data_path, '.');
if (data_path) {
*data_path = '\0';
}
PointerRNA ptr = CTX_data_pointer_get(C, umi_pr->context_data_path);
if (ptr.type == nullptr) {
PointerRNA ctx_ptr = RNA_pointer_create_discrete(nullptr, RNA_Context, (void *)C);
if (!RNA_path_resolve_full(&ctx_ptr, umi_pr->context_data_path, &ptr, nullptr, nullptr))
{
ptr.type = nullptr;
}
}
if (data_path) {
*data_path = '.';
data_path += 1;
}
bool ok = false;
if (ptr.type != nullptr) {
PropertyRNA *prop = nullptr;
PointerRNA prop_ptr = ptr;
if ((data_path == nullptr) ||
RNA_path_resolve_full(&ptr, data_path, &prop_ptr, nullptr, nullptr))
{
prop = RNA_struct_find_property(&prop_ptr, umi_pr->prop_id);
if (prop) {
ok = true;
menu->layout->prop(
&prop_ptr, prop, umi_pr->prop_index, 0, UI_ITEM_NONE, ui_name, ICON_NONE);
is_empty = false;
}
}
}
if (!ok) {
if (show_missing) {
SNPRINTF_UTF8(
label, RPT_("Missing: %s.%s"), umi_pr->context_data_path, umi_pr->prop_id);
menu->layout->label(label, ICON_NONE);
}
}
}
else if (umi.type == USER_MENU_TYPE_SEP) {
menu->layout->separator();
}
}
}
if (um_array) {
MEM_delete(um_array);
}
if (is_empty) {
menu->layout->label(RPT_("No menu items found"), ICON_NONE);
menu->layout->label(RPT_("Right click on buttons to add them to this menu"), ICON_NONE);
}
}
void ED_screen_user_menu_register()
{
MenuType *mt = MEM_new_zeroed<MenuType>(__func__);
STRNCPY_UTF8(mt->idname, "SCREEN_MT_user_menu");
STRNCPY_UTF8(mt->label, N_("Quick Favorites"));
STRNCPY_UTF8(mt->translation_context, BLT_I18NCONTEXT_DEFAULT_BPYRNA);
mt->draw = screen_user_menu_draw;
mt->listener = ed::asset::list::asset_reading_region_listen_fn;
WM_menutype_add(mt);
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,283 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edscr
* Making screenshots of the entire window or sub-regions.
*/
#include <cerrno>
#include <cstring>
#include "MEM_guardedalloc.h"
#include "BLI_path_utils.hh"
#include "BLI_rect.h"
#include "BLI_string_utf8.h"
#include "BLI_utildefines.h"
#include "IMB_imbuf.hh"
#include "IMB_imbuf_types.hh"
#include "DNA_scene_types.h"
#include "DNA_screen_types.h"
#include "DNA_space_types.h"
#include "BKE_context.hh"
#include "BKE_global.hh"
#include "BKE_image.hh"
#include "BKE_image_format.hh"
#include "BKE_main.hh"
#include "BKE_report.hh"
#include "BKE_screen.hh"
#include "BLT_translation.hh"
#include "RNA_access.hh"
#include "RNA_prototypes.hh"
#include "UI_interface.hh"
#include "UI_interface_layout.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "screen_intern.hh"
namespace blender {
struct ScreenshotData {
/* Ownership transferred to ImBuf in exec function. */
uint8_t *dumprect = nullptr;
int dumpsx = 0, dumpsy = 0;
rcti crop = {};
bool use_crop = false;
ImageFormatData im_format;
};
/* call from both exec and invoke */
static int screenshot_data_create(bContext *C, wmOperator *op, ScrArea *area)
{
int dumprect_size[2];
wmWindow *win = CTX_wm_window(C);
/* do redraw so we don't show popups/menus */
WM_redraw_windows(C);
uint8_t *dumprect = WM_window_pixels_read(C, win, dumprect_size);
if (dumprect) {
ScreenshotData *scd = MEM_new<ScreenshotData>("screenshot");
scd->dumpsx = dumprect_size[0];
scd->dumpsy = dumprect_size[1];
scd->dumprect = dumprect;
if (area) {
scd->crop = area->totrct;
}
BKE_image_format_init(&scd->im_format);
op->customdata = scd;
return true;
}
op->customdata = nullptr;
return false;
}
static void screenshot_data_free(wmOperator *op)
{
MEM_delete(static_cast<ScreenshotData *>(op->customdata));
op->customdata = nullptr;
}
static wmOperatorStatus screenshot_exec(bContext *C, wmOperator *op)
{
const bool use_crop = STREQ(op->idname, "SCREEN_OT_screenshot_area");
ScreenshotData *scd = static_cast<ScreenshotData *>(op->customdata);
bool ok = false;
if (scd == nullptr) {
/* when running exec directly */
screenshot_data_create(C, op, use_crop ? CTX_wm_area(C) : nullptr);
scd = static_cast<ScreenshotData *>(op->customdata);
}
if (scd) {
if (scd->dumprect) {
ImBuf *ibuf;
char filepath[FILE_MAX];
RNA_string_get(op->ptr, "filepath", filepath);
BLI_path_abs(filepath, BKE_main_blendfile_path_from_global());
/* operator ensures the extension */
ibuf = IMB_allocImBuf(scd->dumpsx, scd->dumpsy, ImBufFlags::Zero);
ibuf->color_mode = ImColorMode::RGB;
ibuf->assign_byte_data(scd->dumprect);
scd->dumprect = nullptr;
/* crop to show only single editor */
if (use_crop) {
IMB_crop(ibuf,
int2(scd->crop.xmin, scd->crop.ymin),
int2(BLI_rcti_size_x(&scd->crop) + 1, BLI_rcti_size_y(&scd->crop) + 1));
}
if ((scd->im_format.color_mode == ImColorMode::BW) &&
(scd->im_format.imtype != R_IMF_IMTYPE_MULTILAYER))
{
/* bw screenshot? - users will notice if it fails! */
IMB_color_to_bw(ibuf);
}
if (BKE_imbuf_write(ibuf, filepath, &scd->im_format)) {
ok = true;
}
else {
BKE_reportf(op->reports, RPT_ERROR, "Could not write image: %s", strerror(errno));
}
IMB_freeImBuf(ibuf);
}
}
screenshot_data_free(op);
return ok ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
}
static wmOperatorStatus screenshot_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
const bool use_crop = STREQ(op->idname, "SCREEN_OT_screenshot_area");
ScrArea *area = nullptr;
if (use_crop) {
area = CTX_wm_area(C);
bScreen *screen = CTX_wm_screen(C);
ScrArea *area_test = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, event->xy);
if (area_test != nullptr) {
area = area_test;
}
}
if (screenshot_data_create(C, op, area)) {
if (RNA_struct_property_is_set(op->ptr, "filepath")) {
return screenshot_exec(C, op);
}
/* extension is added by 'screenshot_check' after */
char filepath[FILE_MAX];
const char *blendfile_path = BKE_main_blendfile_path_from_global();
if (blendfile_path[0] != '\0') {
STRNCPY(filepath, blendfile_path);
BLI_path_extension_strip(filepath); /* Strip `.blend`. */
}
else {
/* As the file isn't saved, only set the name and let the file selector pick a directory. */
STRNCPY_UTF8(filepath, DATA_("screen"));
}
RNA_string_set(op->ptr, "filepath", filepath);
WM_event_add_fileselect(C, op);
return OPERATOR_RUNNING_MODAL;
}
return OPERATOR_CANCELLED;
}
static bool screenshot_check(bContext * /*C*/, wmOperator *op)
{
ScreenshotData *scd = static_cast<ScreenshotData *>(op->customdata);
return WM_operator_filesel_ensure_ext_imtype(op, &scd->im_format);
}
static void screenshot_cancel(bContext * /*C*/, wmOperator *op)
{
screenshot_data_free(op);
}
static bool screenshot_draw_check_prop(PointerRNA * /*ptr*/,
PropertyRNA *prop,
void * /*user_data*/)
{
const char *prop_id = RNA_property_identifier(prop);
return !STREQ(prop_id, "filepath");
}
static void screenshot_draw(bContext *C, wmOperator *op)
{
ui::Layout &layout = *op->layout;
ScreenshotData *scd = static_cast<ScreenshotData *>(op->customdata);
layout.use_property_split_set(true);
layout.use_property_decorate_set(false);
/* image template */
PointerRNA ptr = RNA_pointer_create_discrete(nullptr, RNA_ImageFormatSettings, &scd->im_format);
uiTemplateImageSettings(&layout, C, &ptr, false);
/* main draw call */
uiDefAutoButsRNA(&layout,
op->ptr,
screenshot_draw_check_prop,
nullptr,
nullptr,
ui::BUT_LABEL_ALIGN_NONE,
false);
}
static bool screenshot_poll(bContext *C)
{
if (G.background) {
return false;
}
return WM_operator_winactive(C);
}
static void screen_screenshot_impl(wmOperatorType *ot)
{
ot->invoke = screenshot_invoke;
ot->check = screenshot_check;
ot->exec = screenshot_exec;
ot->cancel = screenshot_cancel;
ot->ui = screenshot_draw;
ot->poll = screenshot_poll;
WM_operator_properties_filesel(ot,
FILE_TYPE_FOLDER | FILE_TYPE_IMAGE,
FILE_SPECIAL,
FILE_SAVE,
WM_FILESEL_FILEPATH,
FILE_DEFAULTDISPLAY,
FILE_SORT_DEFAULT);
}
void SCREEN_OT_screenshot(wmOperatorType *ot)
{
ot->name = "Save Screenshot";
ot->idname = "SCREEN_OT_screenshot";
ot->description = "Capture a picture of the whole Blender window";
screen_screenshot_impl(ot);
ot->flag = 0;
}
void SCREEN_OT_screenshot_area(wmOperatorType *ot)
{
/* NOTE: the term "area" is a Blender internal name, "Editor" makes more sense for the UI. */
ot->name = "Save Screenshot (Editor)";
ot->idname = "SCREEN_OT_screenshot_area";
ot->description = "Capture a picture of an editor";
screen_screenshot_impl(ot);
ot->flag = OPTYPE_DEPENDS_ON_CURSOR;
}
} // namespace blender

View File

@@ -0,0 +1,749 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edscr
*/
#include <cstdlib>
#include <cstring>
#include <fmt/format.h>
#include "BLI_fileops.h"
#include "BLI_listbase.h"
#include "BLI_path_utils.hh"
#include "BLI_string.h"
#include "BLI_string_utf8.h"
#include "BLI_utildefines.h"
#include "BKE_appdir.hh"
#include "BKE_blendfile.hh"
#include "BKE_context.hh"
#include "BKE_layer.hh"
#include "BKE_lib_id.hh"
#include "BKE_main.hh"
#include "BKE_screen.hh"
#include "BKE_workspace.hh"
#include "BLO_readfile.hh"
#include "DNA_screen_types.h"
#include "DNA_windowmanager_types.h"
#include "DNA_workspace_types.h"
#include "ED_datafiles.h"
#include "ED_object.hh"
#include "ED_screen.hh"
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "UI_interface.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "BLT_translation.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "screen_intern.hh"
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Workspace API
*
* \brief API for managing workspaces and their data.
* \{ */
WorkSpace *ED_workspace_add(Main *bmain, const char *name)
{
return BKE_workspace_add(bmain, name);
}
static void workspace_exit(WorkSpace *workspace, wmWindow *win)
{
/* Scene pinning: Store whatever scene was active when leaving the workspace. It's reactivated
* when the workspace gets reactivated as well. */
if (workspace->flags & WORKSPACE_USE_PIN_SCENE) {
workspace->pin_scene = WM_window_get_active_scene(win);
}
else {
/* The active scene may have been changed. So also always update the unpinned scene to the
* latest when leaving a workspace that has no scene pinning. */
win->unpinned_scene = WM_window_get_active_scene(win);
}
}
/**
* State changes (old workspace to new workspace):
* 1) unpinned -> pinned
* * Store current scene as the unpinned one (done in #workspace_exit()).
* * Change the current scene to the pinned one.
* 2) pinned -> pinned
* * Change the current scene to the new pinned one.
* 3) pinned -> unpinned
* * Change current scene back to the unpinned one
* 4) unpinned -> unpinned
* * Make sure the unpinned scene is active.
*
* Note that the pin scene must also be updated when leaving a workspace with a pinned scene.
* That's done separately via workspace_exit() above.
*/
static void workspace_scene_pinning_update(WorkSpace *workspace_new,
const WorkSpace *workspace_old,
bContext *C)
{
wmWindow *win = CTX_wm_window(C);
Main *bmain = CTX_data_main(C);
Scene *active_scene = WM_window_get_active_scene(win);
const bool is_new_pinned = (workspace_new->flags & WORKSPACE_USE_PIN_SCENE);
const bool is_old_pinned = (workspace_old->flags & WORKSPACE_USE_PIN_SCENE);
/* State changes 1 and 2. */
if (is_new_pinned) {
if (workspace_new->pin_scene && (workspace_new->pin_scene != active_scene)) {
WM_window_set_active_scene(bmain, C, win, workspace_new->pin_scene);
workspace_new->pin_scene = nullptr;
}
}
/* State change 3 - Changing from workspace with pinned scene to unpinned scene. */
else if (is_old_pinned) {
if (win->unpinned_scene) {
WM_window_set_active_scene(bmain, C, win, win->unpinned_scene);
}
else {
/* When leaving a workspace where the pinning was just enabled, the unpinned scene wasn't set
* yet. */
win->unpinned_scene = active_scene;
}
}
else {
/* When leaving a workspace where the pinning was just disabled, we still want to restore the
* unpinned scene. */
if (win->unpinned_scene) {
WM_window_set_active_scene(bmain, C, win, win->unpinned_scene);
}
}
BLI_assert(WM_window_get_active_scene(win));
}
/**
* Changes the object mode (if needed) to the one set in \a workspace_new.
* Object mode is still stored on object level. In future it should all be workspace level instead.
*/
static void workspace_change_update(WorkSpace *workspace_new,
WorkSpace *workspace_old,
bContext *C,
wmWindowManager *wm)
{
workspace_scene_pinning_update(workspace_new, workspace_old, C);
/* needs to be done before changing mode! (to ensure right context) */
UNUSED_VARS(wm);
#if 0
Object *ob_act = CTX_data_active_object(C);
eObjectMode mode_old = workspace_old->object_mode;
eObjectMode mode_new = workspace_new->object_mode;
if (mode_old != mode_new) {
ed::object::mode_set(C, mode_new);
}
#endif
}
static WorkSpaceLayout *workspace_change_get_new_layout(Main *bmain,
WorkSpace *workspace_new,
wmWindow *win)
{
WorkSpaceLayout *layout_old = WM_window_get_active_layout(win);
WorkSpaceLayout *layout_new;
/* ED_workspace_duplicate may have stored a layout to activate
* once the workspace gets activated. */
if (win->workspace_hook->temp_layout_store) {
layout_new = win->workspace_hook->temp_layout_store;
}
else {
layout_new = BKE_workspace_active_layout_for_workspace_get(win->workspace_hook, workspace_new);
if (!layout_new) {
layout_new = static_cast<WorkSpaceLayout *>(workspace_new->layouts.first);
}
}
return ED_workspace_screen_change_ensure_unused_layout(
bmain, workspace_new, layout_new, layout_old, win);
}
bool ED_workspace_change(WorkSpace *workspace_new, bContext *C, wmWindowManager *wm, wmWindow *win)
{
Main *bmain = CTX_data_main(C);
WorkSpace *workspace_old = WM_window_get_active_workspace(win);
WorkSpaceLayout *layout_new = workspace_change_get_new_layout(bmain, workspace_new, win);
bScreen *screen_new = BKE_workspace_layout_screen_get(layout_new);
bScreen *screen_old = BKE_workspace_active_screen_get(win->workspace_hook);
win->workspace_hook->temp_layout_store = nullptr;
if (workspace_old == workspace_new) {
/* Could also return true, everything that needs to be done was done (nothing :P),
* but nothing changed */
return false;
}
workspace_exit(workspace_old, win);
screen_change_prepare(screen_old, screen_new, bmain, C, win);
if (screen_new == nullptr) {
return false;
}
BKE_workspace_active_layout_set(win->workspace_hook, win->winid, workspace_new, layout_new);
BKE_workspace_active_set(win->workspace_hook, workspace_new);
/* update screen *after* changing workspace - which also causes the
* actual screen change and updates context (including CTX_wm_workspace) */
screen_change_update(C, win, screen_new);
workspace_change_update(workspace_new, workspace_old, C, wm);
BLI_assert(CTX_wm_workspace(C) == workspace_new);
/* Automatic mode switching. */
if (workspace_new->object_mode != workspace_old->object_mode) {
const Object *object = nullptr;
if (const Base *base = CTX_data_active_base(C)) {
object = base->object;
/* Behavior that depends on the active area is not expected in the context of workspace
* switching, ignore the view-port even if it's available. */
const View3D *v3d = nullptr;
const bool base_visible = BKE_base_is_visible(v3d, base);
if (!base_visible && object->mode == OB_MODE_OBJECT) {
/* Set this to nullptr to indicate that the mode should not be switched. This matches
* CTX_data_active_object behavior in the 3D Viewport. See `view3d_context` for more
* details. */
object = nullptr;
}
}
if (object) {
ed::object::mode_set(C, eObjectMode(workspace_new->object_mode));
}
}
return true;
}
WorkSpace *ED_workspace_duplicate(WorkSpace *workspace_old, Main *bmain, wmWindow *win)
{
WorkSpaceLayout *layout_active_old = BKE_workspace_active_layout_get(win->workspace_hook);
WorkSpace *workspace_new = id_cast<WorkSpace *>(BKE_id_copy(bmain, &workspace_old->id));
/* Try to keep active the layout from the new workspace matching the current active one from
* the old workspace. */
WorkSpaceLayout *layout_old = static_cast<WorkSpaceLayout *>(workspace_old->layouts.first);
WorkSpaceLayout *layout_new = static_cast<WorkSpaceLayout *>(workspace_new->layouts.first);
for (; layout_old && layout_new; layout_old = layout_old->next, layout_new = layout_new->next) {
if (layout_old == layout_active_old) {
win->workspace_hook->temp_layout_store = layout_new;
}
}
return workspace_new;
}
bool ED_workspace_delete(WorkSpace *workspace, Main *bmain, bContext *C, wmWindowManager *wm)
{
if (bmain->workspaces.is_single()) {
return false;
}
Vector<ID *> ordered = BKE_id_ordered_list(
reinterpret_cast<const ListBaseT<ID> *>(&bmain->workspaces));
const int index = ordered.first_index_of(&workspace->id);
WorkSpace *new_active = reinterpret_cast<WorkSpace *>(index == 0 ? ordered[1] :
ordered[index - 1]);
for (wmWindow &win : wm->windows) {
WorkSpace *workspace_active = WM_window_get_active_workspace(&win);
if (workspace_active == workspace) {
ED_workspace_change(new_active, C, wm, &win);
}
}
/* Also delete managed screens if they have no other users. */
for (WorkSpaceLayout &layout : workspace->layouts) {
BKE_id_free_us(bmain, layout.screen);
layout.screen = nullptr;
}
BKE_id_free(bmain, &workspace->id);
return true;
}
void ED_workspace_scene_data_sync(WorkSpaceInstanceHook *hook, Scene *scene)
{
bScreen *screen = BKE_workspace_active_screen_get(hook);
BKE_screen_view3d_scene_sync(screen, scene);
}
/** \} Workspace API */
/* -------------------------------------------------------------------- */
/** \name Workspace Operators
* \{ */
static WorkSpace *workspace_context_get(bContext *C)
{
ID *id = ui::context_active_but_get_tab_ID(C);
if (id && GS(id->name) == ID_WS) {
return id_cast<WorkSpace *>(id);
}
return CTX_wm_workspace(C);
}
static bool workspace_context_poll(bContext *C)
{
return workspace_context_get(C) != nullptr;
}
static wmOperatorStatus workspace_new_exec(bContext *C, wmOperator * /*op*/)
{
Main *bmain = CTX_data_main(C);
wmWindow *win = CTX_wm_window(C);
WorkSpace *workspace = workspace_context_get(C);
workspace = ED_workspace_duplicate(workspace, bmain, win);
WM_event_add_notifier(C, NC_SCREEN | ND_WORKSPACE_SET, workspace);
return OPERATOR_FINISHED;
}
static void WORKSPACE_OT_duplicate(wmOperatorType *ot)
{
/* identifiers */
ot->name = "New Workspace";
ot->description = "Add a new workspace";
ot->idname = "WORKSPACE_OT_duplicate";
/* API callbacks. */
ot->poll = workspace_context_poll;
ot->exec = workspace_new_exec;
}
static wmOperatorStatus workspace_delete_exec(bContext *C, wmOperator * /*op*/)
{
WorkSpace *workspace = workspace_context_get(C);
WM_event_add_notifier(C, NC_SCREEN | ND_WORKSPACE_DELETE, workspace);
WM_event_add_notifier(C, NC_WINDOW, nullptr);
return OPERATOR_FINISHED;
}
static void WORKSPACE_OT_delete(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Delete Workspace";
ot->description = "Delete the active workspace";
ot->idname = "WORKSPACE_OT_delete";
/* API callbacks. */
ot->poll = workspace_context_poll;
ot->exec = workspace_delete_exec;
}
static wmOperatorStatus workspace_delete_all_others_exec(bContext *C, wmOperator * /*op*/)
{
Main *bmain = CTX_data_main(C);
WorkSpace *workspace = workspace_context_get(C);
for (WorkSpace &ws : bmain->workspaces) {
if (&ws != workspace) {
WM_event_add_notifier(C, NC_SCREEN | ND_WORKSPACE_DELETE, &ws);
WM_event_add_notifier(C, NC_WINDOW, nullptr);
}
}
return OPERATOR_FINISHED;
}
static void WORKSPACE_OT_delete_all_others(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Delete Other Workspaces";
ot->description = "Delete all workspaces except this one";
ot->idname = "WORKSPACE_OT_delete_all_others";
/* api callbacks */
ot->poll = workspace_context_poll;
ot->exec = workspace_delete_all_others_exec;
}
static wmOperatorStatus workspace_append_activate_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
char idname[MAX_ID_NAME - 2], filepath[FILE_MAX];
if (!RNA_struct_property_is_set(op->ptr, "idname") ||
!RNA_struct_property_is_set(op->ptr, "filepath"))
{
return OPERATOR_CANCELLED;
}
RNA_string_get(op->ptr, "idname", idname);
RNA_string_get(op->ptr, "filepath", filepath);
/* Not expected, but a blank filename causes an assert
* (trips up the "importing from self" assert as both paths are blank). */
if (idname[0] == '\0' || filepath[0] == '\0') {
return OPERATOR_CANCELLED;
}
WorkSpace *appended_workspace = nullptr;
/* NOTE: Need to check `filepath`, in the rare case where the usual source of work-spaces
* (the startup blend-file) is the one currently open (see #144305). */
const char *blendfile_path = BKE_main_blendfile_path(bmain);
if ((blendfile_path[0] != '\0') && (BLI_path_cmp(blendfile_path, filepath) == 0)) {
appended_workspace = reinterpret_cast<WorkSpace *>(
BKE_libblock_find_name(bmain, ID_WS, idname, nullptr));
if (appended_workspace) {
/* Copy, to mimic behavior when appending from another file (which always creates a new copy
* of the data). */
appended_workspace = ED_workspace_duplicate(appended_workspace, bmain, CTX_wm_window(C));
}
}
else {
appended_workspace = reinterpret_cast<WorkSpace *>(
WM_file_append_datablock(bmain,
CTX_data_scene(C),
CTX_data_view_layer(C),
CTX_wm_view3d(C),
filepath,
ID_WS,
idname,
BLO_LIBLINK_APPEND_RECURSIVE));
}
if (appended_workspace) {
/* Translate workspace name, unless it was taken from current blendfile. */
if (BLT_translate_new_dataname()) {
BKE_libblock_rename(
*bmain, appended_workspace->id, CTX_DATA_(BLT_I18NCONTEXT_ID_WORKSPACE, idname));
}
/* Set defaults. */
BLO_update_defaults_workspace(appended_workspace, nullptr);
/* Reorder to last position. */
BKE_id_reorder(reinterpret_cast<const ListBaseT<ID> *>(&bmain->workspaces),
&appended_workspace->id,
nullptr,
true);
/* Changing workspace changes context. Do delayed! */
WM_event_add_notifier(C, NC_SCREEN | ND_WORKSPACE_SET, appended_workspace);
return OPERATOR_FINISHED;
}
return OPERATOR_CANCELLED;
}
static void WORKSPACE_OT_append_activate(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Append and Activate Workspace";
ot->description = "Append a workspace and make it the active one in the current window";
ot->idname = "WORKSPACE_OT_append_activate";
/* API callbacks. */
ot->exec = workspace_append_activate_exec;
PropertyRNA *prop;
RNA_def_string(ot->srna,
"idname",
nullptr,
MAX_ID_NAME - 2,
"Identifier",
"Name of the workspace to append and activate");
prop = RNA_def_string(
ot->srna, "filepath", nullptr, FILE_MAX, "Filepath", "Path to the library");
RNA_def_property_subtype(prop, PROP_FILEPATH);
RNA_def_property_flag(prop, PROP_PATH_SUPPORTS_BLEND_RELATIVE);
}
static WorkspaceConfigFileData *workspace_config_file_read(const char *app_template)
{
const std::optional<std::string> cfgdir = BKE_appdir_folder_id(BLENDER_USER_CONFIG,
app_template);
char startup_file_path[FILE_MAX] = {0};
if (cfgdir.has_value()) {
BLI_path_join(
startup_file_path, sizeof(startup_file_path), cfgdir->c_str(), BLENDER_STARTUP_FILE);
}
bool has_path = BLI_exists(startup_file_path);
return (has_path) ? BKE_blendfile_workspace_config_read(startup_file_path, nullptr, 0, nullptr) :
nullptr;
}
static WorkspaceConfigFileData *workspace_system_file_read(const char *app_template)
{
if (app_template == nullptr) {
return BKE_blendfile_workspace_config_read(
nullptr, datatoc_startup_blend, datatoc_startup_blend_size, nullptr);
}
char template_dir[FILE_MAX];
if (!BKE_appdir_app_template_id_search(app_template, template_dir, sizeof(template_dir))) {
return nullptr;
}
char startup_file_path[FILE_MAX];
BLI_path_join(startup_file_path, sizeof(startup_file_path), template_dir, BLENDER_STARTUP_FILE);
bool has_path = BLI_exists(startup_file_path);
return (has_path) ? BKE_blendfile_workspace_config_read(startup_file_path, nullptr, 0, nullptr) :
nullptr;
}
static void workspace_append_button(ui::Layout &layout,
wmOperatorType *ot_append,
const WorkSpace *workspace,
const Main *from_main)
{
const ID *id = id_cast<ID *>(const_cast<WorkSpace *>(workspace));
const char *filepath = from_main->filepath;
if (filepath[0] == '\0') {
filepath = BLO_EMBEDDED_STARTUP_BLEND;
}
BLI_assert(STREQ(ot_append->idname, "WORKSPACE_OT_append_activate"));
PointerRNA opptr;
opptr = layout.op(ot_append,
CTX_DATA_(BLT_I18NCONTEXT_ID_WORKSPACE, workspace->id.name + 2),
ICON_NONE,
wm::OpCallContext::ExecDefault,
UI_ITEM_NONE);
RNA_string_set(&opptr, "idname", id->name + 2);
RNA_string_set(&opptr, "filepath", filepath);
}
static void workspace_add_menu(bContext * /*C*/, ui::Layout *layout, void *template_v)
{
const char *app_template = static_cast<const char *>(template_v);
bool has_startup_items = false;
wmOperatorType *ot_append = WM_operatortype_find("WORKSPACE_OT_append_activate", true);
WorkspaceConfigFileData *startup_config = workspace_config_file_read(app_template);
WorkspaceConfigFileData *builtin_config = workspace_system_file_read(app_template);
if (startup_config) {
for (WorkSpace &workspace : startup_config->workspaces) {
ui::Layout &row = layout->row(false);
workspace_append_button(row, ot_append, &workspace, startup_config->main);
has_startup_items = true;
}
}
if (builtin_config) {
bool has_title = false;
for (WorkSpace &workspace : builtin_config->workspaces) {
if (startup_config &&
BLI_findstring(&startup_config->workspaces, workspace.id.name, offsetof(ID, name)))
{
continue;
}
if (!has_title) {
if (has_startup_items) {
layout->separator();
}
has_title = true;
}
ui::Layout &row = layout->row(false);
workspace_append_button(row, ot_append, &workspace, builtin_config->main);
}
}
if (startup_config) {
BKE_blendfile_workspace_config_data_free(startup_config);
}
if (builtin_config) {
BKE_blendfile_workspace_config_data_free(builtin_config);
}
}
static void workspace_add_menu_draw(ui::Layout &layout)
{
{
PointerRNA props = layout.op("WM_OT_search_single_menu",
"Search...",
ICON_VIEWZOOM,
wm::OpCallContext::InvokeDefault,
UI_ITEM_NONE);
RNA_string_set(&props, "menu_idname", "WORKSPACE_MT_add");
}
layout.separator();
layout.menu_fn(IFACE_("General"), ICON_NONE, workspace_add_menu, nullptr);
ListBaseT<LinkData> templates;
BKE_appdir_app_templates(&templates);
for (LinkData &link : templates) {
char *app_template = static_cast<char *>(link.data);
char display_name[FILE_MAX];
BLI_path_to_display_name(display_name, sizeof(display_name), IFACE_(app_template));
/* Steals ownership of link data string. */
layout.menu_fn_argN_free(display_name, ICON_NONE, workspace_add_menu, app_template);
}
templates.free_no_destruct();
layout.separator();
layout.op("WORKSPACE_OT_duplicate",
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Duplicate Current"),
ICON_DUPLICATE);
}
static void workspace_add_menu_register()
{
MenuType *mt = MEM_new_zeroed<MenuType>("workspace_add_invoke");
STRNCPY_UTF8(mt->idname, "WORKSPACE_MT_add");
STRNCPY_UTF8(mt->label, N_("Add Workspace"));
mt->flag = MenuTypeFlag::SearchOnKeyPress;
mt->draw = [](const bContext * /*C*/, Menu *menu) {
ui::Layout &layout = *menu->layout;
workspace_add_menu_draw(layout);
};
WM_menutype_add(mt);
}
static wmOperatorStatus workspace_add_invoke(bContext *C,
wmOperator * /*op*/,
const wmEvent * /*event*/)
{
WM_menu_name_call(C, "WORKSPACE_MT_add", wm::OpCallContext::InvokeDefault);
return OPERATOR_INTERFACE;
}
static void WORKSPACE_OT_add(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Add Workspace";
ot->description =
"Add a new workspace by duplicating the current one or appending one "
"from the user configuration";
ot->idname = "WORKSPACE_OT_add";
/* API callbacks. */
ot->invoke = workspace_add_invoke;
}
static wmOperatorStatus workspace_reorder_to_back_exec(bContext *C, wmOperator * /*op*/)
{
Main *bmain = CTX_data_main(C);
WorkSpace *workspace = workspace_context_get(C);
BKE_id_reorder(
reinterpret_cast<const ListBaseT<ID> *>(&bmain->workspaces), &workspace->id, nullptr, true);
WM_event_add_notifier(C, NC_WINDOW, nullptr);
return OPERATOR_INTERFACE;
}
static void WORKSPACE_OT_reorder_to_back(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Workspace Reorder to Back";
ot->description = "Reorder workspace to be last in the list";
ot->idname = "WORKSPACE_OT_reorder_to_back";
/* API callbacks. */
ot->poll = workspace_context_poll;
ot->exec = workspace_reorder_to_back_exec;
}
static wmOperatorStatus workspace_reorder_to_front_exec(bContext *C, wmOperator * /*op*/)
{
Main *bmain = CTX_data_main(C);
WorkSpace *workspace = workspace_context_get(C);
BKE_id_reorder(
reinterpret_cast<const ListBaseT<ID> *>(&bmain->workspaces), &workspace->id, nullptr, false);
WM_event_add_notifier(C, NC_WINDOW, nullptr);
return OPERATOR_INTERFACE;
}
static void WORKSPACE_OT_reorder_to_front(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Workspace Reorder to Front";
ot->description = "Reorder workspace to be first in the list";
ot->idname = "WORKSPACE_OT_reorder_to_front";
/* API callbacks. */
ot->poll = workspace_context_poll;
ot->exec = workspace_reorder_to_front_exec;
}
static wmOperatorStatus workspace_scene_pin_toggle_exec(bContext *C, wmOperator * /*op*/)
{
WorkSpace *workspace = workspace_context_get(C);
/* Trivial. The operator is only needed to display a superimposed extra icon, which
* requires an operator. */
workspace->flags ^= WORKSPACE_USE_PIN_SCENE;
WM_event_add_notifier(C, NC_WORKSPACE, nullptr);
return OPERATOR_FINISHED;
}
static void WORKSPACE_OT_scene_pin_toggle(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Pin Scene to Workspace";
ot->description =
"Remember the last used scene for the current workspace and switch to it whenever this "
"workspace is activated again";
ot->idname = "WORKSPACE_OT_scene_pin_toggle";
/* API callbacks. */
ot->poll = workspace_context_poll;
ot->exec = workspace_scene_pin_toggle_exec;
ot->flag = OPTYPE_INTERNAL;
}
void ED_operatortypes_workspace()
{
workspace_add_menu_register();
WM_operatortype_append(WORKSPACE_OT_duplicate);
WM_operatortype_append(WORKSPACE_OT_delete);
WM_operatortype_append(WORKSPACE_OT_delete_all_others);
WM_operatortype_append(WORKSPACE_OT_add);
WM_operatortype_append(WORKSPACE_OT_append_activate);
WM_operatortype_append(WORKSPACE_OT_reorder_to_back);
WM_operatortype_append(WORKSPACE_OT_reorder_to_front);
WM_operatortype_append(WORKSPACE_OT_scene_pin_toggle);
}
/** \} Workspace Operators */
} // namespace blender

View File

@@ -0,0 +1,219 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edscr
*/
#include <cstdlib>
#include "BLI_listbase.h"
#include "BLI_utildefines.h"
#include "DNA_screen_types.h"
#include "DNA_workspace_types.h"
#include "BKE_context.hh"
#include "BKE_lib_id.hh"
#include "BKE_main.hh"
#include "BKE_screen.hh"
#include "BKE_workspace.hh"
#include "WM_api.hh"
#include "ED_screen.hh"
#include "screen_intern.hh"
namespace blender {
WorkSpaceLayout *ED_workspace_layout_add(Main *bmain,
WorkSpace *workspace,
wmWindow *win,
const char *name)
{
bScreen *screen;
rcti screen_rect;
WM_window_screen_rect_calc(win, &screen_rect);
screen = screen_add(bmain, name, &screen_rect);
return BKE_workspace_layout_add(bmain, *workspace, *screen, name);
}
WorkSpaceLayout *ED_workspace_layout_duplicate(Main *bmain,
WorkSpace *workspace,
const WorkSpaceLayout *layout_old,
wmWindow * /*win*/)
{
return BKE_workspace_layout_add_from_layout(bmain, *workspace, *layout_old, LIB_ID_COPY_DEFAULT);
}
static bool workspace_layout_delete_doit(WorkSpace *workspace,
WorkSpaceLayout *layout_old,
WorkSpaceLayout *layout_new,
bContext *C)
{
Main *bmain = CTX_data_main(C);
wmWindow *win = CTX_wm_window(C);
bScreen *screen_new = BKE_workspace_layout_screen_get(layout_new);
ED_screen_change(C, screen_new);
if (BKE_workspace_active_layout_get(win->workspace_hook) != layout_old) {
BKE_workspace_layout_remove(bmain, workspace, layout_old);
return true;
}
return false;
}
bool workspace_layout_set_poll(const WorkSpaceLayout *layout)
{
const bScreen *screen = BKE_workspace_layout_screen_get(layout);
return ((BKE_screen_is_used(screen) == false) &&
/* in typical usage temp screens should have a nonzero winid
* (all temp screens should be used, or closed & freed). */
(screen->temp == false) && (BKE_screen_is_fullscreen_area(screen) == false) &&
(screen->id.name[2] != '.' || !(U.uiflag & USER_HIDE_DOT)));
}
static WorkSpaceLayout *workspace_layout_delete_find_new(const WorkSpaceLayout *layout_old)
{
for (WorkSpaceLayout *layout_new = layout_old->prev; layout_new; layout_new = layout_new->next) {
if (workspace_layout_set_poll(layout_new)) {
return layout_new;
}
}
for (WorkSpaceLayout *layout_new = layout_old->next; layout_new; layout_new = layout_new->next) {
if (workspace_layout_set_poll(layout_new)) {
return layout_new;
}
}
return nullptr;
}
bool ED_workspace_layout_delete(WorkSpace *workspace, WorkSpaceLayout *layout_old, bContext *C)
{
const bScreen *screen_old = BKE_workspace_layout_screen_get(layout_old);
WorkSpaceLayout *layout_new;
BLI_assert(BLI_findindex(&workspace->layouts, layout_old) != -1);
/* Don't allow deleting temp full-screens for now. */
if (BKE_screen_is_fullscreen_area(screen_old)) {
return false;
}
/* A layout/screen can only be in use by one window at a time, so as
* long as we are able to find a layout/screen that is unused, we
* can safely assume ours is not in use anywhere an delete it. */
layout_new = workspace_layout_delete_find_new(layout_old);
if (layout_new) {
return workspace_layout_delete_doit(workspace, layout_old, layout_new, C);
}
return false;
}
static bool workspace_change_find_new_layout_cb(const WorkSpaceLayout *layout, void * /*arg*/)
{
/* return false to stop the iterator if we've found a layout that can be activated */
return workspace_layout_set_poll(layout) ? false : true;
}
static bScreen *screen_fullscreen_find_associated_normal_screen(const Main *bmain, bScreen *screen)
{
for (bScreen &screen_iter : bmain->screens) {
if ((&screen_iter != screen) && ELEM(screen_iter.state, SCREENMAXIMIZED, SCREENFULL)) {
ScrArea *area = static_cast<ScrArea *>(screen_iter.areabase.first);
if (area && area->full == screen) {
return &screen_iter;
}
}
}
return screen;
}
static bool screen_is_used_by_other_window(const wmWindow *win, const bScreen *screen)
{
return BKE_screen_is_used(screen) && (screen->winid != win->winid);
}
WorkSpaceLayout *ED_workspace_screen_change_ensure_unused_layout(
Main *bmain,
WorkSpace *workspace,
WorkSpaceLayout *layout_new,
const WorkSpaceLayout *layout_fallback_base,
wmWindow *win)
{
WorkSpaceLayout *layout_temp = layout_new;
bScreen *screen_temp = BKE_workspace_layout_screen_get(layout_new);
screen_temp = screen_fullscreen_find_associated_normal_screen(bmain, screen_temp);
layout_temp = BKE_workspace_layout_find(workspace, screen_temp);
if (screen_is_used_by_other_window(win, screen_temp)) {
/* Screen is already used, try to find a free one. */
layout_temp = BKE_workspace_layout_iter_circular(
workspace, layout_new, workspace_change_find_new_layout_cb, nullptr, false);
screen_temp = layout_temp ? BKE_workspace_layout_screen_get(layout_temp) : nullptr;
if (!layout_temp || screen_is_used_by_other_window(win, screen_temp)) {
/* Fallback solution: duplicate layout. */
layout_temp = ED_workspace_layout_duplicate(bmain, workspace, layout_fallback_base, win);
}
}
return layout_temp;
}
static bool workspace_layout_cycle_iter_cb(const WorkSpaceLayout *layout, void * /*arg*/)
{
/* return false to stop iterator when we have found a layout to activate */
return !workspace_layout_set_poll(layout);
}
bool ED_workspace_layout_cycle(WorkSpace *workspace, const short direction, bContext *C)
{
wmWindow *win = CTX_wm_window(C);
WorkSpaceLayout *old_layout = BKE_workspace_active_layout_get(win->workspace_hook);
const bScreen *old_screen = BKE_workspace_layout_screen_get(old_layout);
ScrArea *area = CTX_wm_area(C);
if (old_screen->temp || (area && area->full && area->full->temp)) {
return false;
}
BLI_assert(ELEM(direction, 1, -1));
WorkSpaceLayout *new_layout = BKE_workspace_layout_iter_circular(workspace,
old_layout,
workspace_layout_cycle_iter_cb,
nullptr,
(direction == -1) ? true :
false);
if (new_layout && (old_layout != new_layout)) {
bScreen *new_screen = BKE_workspace_layout_screen_get(new_layout);
if (area && area->full) {
/* return to previous state before switching screens */
ED_screen_full_restore(C, area); /* may free screen of old_layout */
}
ED_screen_change(C, new_screen);
return true;
}
return false;
}
} // namespace blender

View File

@@ -0,0 +1,51 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_listbase.h"
#include "BKE_context.hh"
#include "BKE_viewer_path.hh"
#include "ED_screen.hh"
#include "ED_viewer_path.hh"
#include "WM_api.hh"
namespace blender {
/**
* Checks if the viewer path stored in the workspace is still active and resets it if not.
* The viewer path stored in the workspace is the ground truth for other editors, so it should be
* updated before other editors look at it.
*/
static void validate_viewer_paths(bContext &C, WorkSpace &workspace)
{
if (workspace.viewer_path.path.is_empty()) {
return;
}
using namespace blender::ed::viewer_path;
const UpdateActiveGeometryNodesViewerResult result = update_active_geometry_nodes_viewer(
C, workspace.viewer_path);
switch (result) {
case UpdateActiveGeometryNodesViewerResult::StillActive:
return;
case UpdateActiveGeometryNodesViewerResult::Updated:
break;
case UpdateActiveGeometryNodesViewerResult::NotActive:
BKE_viewer_path_clear(&workspace.viewer_path);
break;
}
WM_event_add_notifier(&C, NC_VIEWER_PATH, nullptr);
}
void ED_workspace_do_listen(bContext *C, const wmNotifier * /*note*/)
{
WorkSpace *workspace = CTX_wm_workspace(C);
validate_viewer_paths(*C, *workspace);
}
} // namespace blender