diff options
-rw-r--r-- | drivers/gles3/rasterizer_scene_gles3.cpp | 67 | ||||
-rw-r--r-- | drivers/gles3/shaders/copy.glsl | 24 | ||||
-rw-r--r-- | drivers/gles3/shaders/scene.glsl | 77 | ||||
-rw-r--r-- | editor/editor_node.cpp | 3 | ||||
-rw-r--r-- | editor/plugins/script_editor_plugin.cpp | 5 | ||||
-rw-r--r-- | editor/project_manager.cpp | 3 | ||||
-rw-r--r-- | platform/javascript/javascript_eval.cpp | 82 | ||||
-rw-r--r-- | scene/resources/audio_stream_sample.cpp | 3 | ||||
-rw-r--r-- | scene/resources/default_theme/default_theme.cpp | 2 |
9 files changed, 190 insertions, 76 deletions
diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp index ba8d490762..6117c91a6a 100644 --- a/drivers/gles3/rasterizer_scene_gles3.cpp +++ b/drivers/gles3/rasterizer_scene_gles3.cpp @@ -2350,22 +2350,7 @@ void RasterizerSceneGLES3::_draw_sky(RasterizerStorageGLES3::Sky *p_sky, const C glDepthFunc(GL_LEQUAL); glColorMask(1, 1, 1, 1); - float flip_sign = p_vflip ? -1 : 1; - - Vector3 vertices[8] = { - Vector3(-1, -1 * flip_sign, 1), - Vector3(0, 1, 0), - Vector3(1, -1 * flip_sign, 1), - Vector3(1, 1, 0), - Vector3(1, 1 * flip_sign, 1), - Vector3(1, 0, 0), - Vector3(-1, 1 * flip_sign, 1), - Vector3(0, 0, 0) - - }; - - //sky uv vectors - float vw, vh, zn; + // Camera CameraMatrix camera; if (p_custom_fov) { @@ -2380,17 +2365,39 @@ void RasterizerSceneGLES3::_draw_sky(RasterizerStorageGLES3::Sky *p_sky, const C camera = p_projection; } - camera.get_viewport_size(vw, vh); - zn = p_projection.get_z_near(); + float flip_sign = p_vflip ? -1 : 1; - for (int i = 0; i < 4; i++) { + /* + If matrix[2][0] or matrix[2][1] we're dealing with an asymmetrical projection matrix. This is the case for stereoscopic rendering (i.e. VR). + To ensure the image rendered is perspective correct we need to move some logic into the shader. For this the USE_ASYM_PANO option is introduced. + It also means the uv coordinates are ignored in this mode and we don't need our loop. + */ + bool asymmetrical = ((camera.matrix[2][0] != 0.0) || (camera.matrix[2][1] != 0.0)); - Vector3 uv = vertices[i * 2 + 1]; - uv.x = (uv.x * 2.0 - 1.0) * vw; - uv.y = -(uv.y * 2.0 - 1.0) * vh; - uv.z = -zn; - vertices[i * 2 + 1] = p_transform.basis.xform(uv).normalized(); - vertices[i * 2 + 1].z = -vertices[i * 2 + 1].z; + Vector3 vertices[8] = { + Vector3(-1, -1 * flip_sign, 1), + Vector3(0, 1, 0), + Vector3(1, -1 * flip_sign, 1), + Vector3(1, 1, 0), + Vector3(1, 1 * flip_sign, 1), + Vector3(1, 0, 0), + Vector3(-1, 1 * flip_sign, 1), + Vector3(0, 0, 0) + }; + + if (!asymmetrical) { + float vw, vh, zn; + camera.get_viewport_size(vw, vh); + zn = p_projection.get_z_near(); + + for (int i = 0; i < 4; i++) { + Vector3 uv = vertices[i * 2 + 1]; + uv.x = (uv.x * 2.0 - 1.0) * vw; + uv.y = -(uv.y * 2.0 - 1.0) * vh; + uv.z = -zn; + vertices[i * 2 + 1] = p_transform.basis.xform(uv).normalized(); + vertices[i * 2 + 1].z = -vertices[i * 2 + 1].z; + } } glBindBuffer(GL_ARRAY_BUFFER, state.sky_verts); @@ -2399,16 +2406,24 @@ void RasterizerSceneGLES3::_draw_sky(RasterizerStorageGLES3::Sky *p_sky, const C glBindVertexArray(state.sky_array); - storage->shaders.copy.set_conditional(CopyShaderGLES3::USE_PANORAMA, true); + storage->shaders.copy.set_conditional(CopyShaderGLES3::USE_ASYM_PANO, asymmetrical); + storage->shaders.copy.set_conditional(CopyShaderGLES3::USE_PANORAMA, !asymmetrical); storage->shaders.copy.set_conditional(CopyShaderGLES3::USE_MULTIPLIER, true); storage->shaders.copy.bind(); storage->shaders.copy.set_uniform(CopyShaderGLES3::MULTIPLIER, p_energy); + if (asymmetrical) { + // pack the bits we need from our projection matrix + storage->shaders.copy.set_uniform(CopyShaderGLES3::ASYM_PROJ, camera.matrix[2][0], camera.matrix[0][0], camera.matrix[2][1], camera.matrix[1][1]); + ///@TODO I couldn't get mat3 + p_transform.basis to work, that would be better here. + storage->shaders.copy.set_uniform(CopyShaderGLES3::PANO_TRANSFORM, p_transform); + } glDrawArrays(GL_TRIANGLE_FAN, 0, 4); glBindVertexArray(0); glColorMask(1, 1, 1, 1); + storage->shaders.copy.set_conditional(CopyShaderGLES3::USE_ASYM_PANO, false); storage->shaders.copy.set_conditional(CopyShaderGLES3::USE_MULTIPLIER, false); storage->shaders.copy.set_conditional(CopyShaderGLES3::USE_PANORAMA, false); } diff --git a/drivers/gles3/shaders/copy.glsl b/drivers/gles3/shaders/copy.glsl index d33193ee50..743fe122d1 100644 --- a/drivers/gles3/shaders/copy.glsl +++ b/drivers/gles3/shaders/copy.glsl @@ -27,6 +27,8 @@ void main() { #if defined(USE_CUBEMAP) || defined(USE_PANORAMA) cube_interp = cube_in; +#elif defined(USE_ASYM_PANO) + uv_interp = vertex_attrib.xy; #else uv_interp = uv_in; #ifdef V_FLIP @@ -59,6 +61,11 @@ in vec3 cube_interp; in vec2 uv_interp; #endif +#ifdef USE_ASYM_PANO +uniform highp mat4 pano_transform; +uniform highp vec4 asym_proj; +#endif + #ifdef USE_CUBEMAP uniform samplerCube source_cube; //texunit:0 #else @@ -70,7 +77,7 @@ uniform sampler2D source; //texunit:0 uniform float multiplier; #endif -#ifdef USE_PANORAMA +#if defined(USE_PANORAMA) || defined(USE_ASYM_PANO) vec4 texturePanorama(vec3 normal,sampler2D pano ) { @@ -122,6 +129,21 @@ void main() { vec4 color = texturePanorama( normalize(cube_interp), source ); +#elif defined(USE_ASYM_PANO) + + // When an assymetrical projection matrix is used (applicable for stereoscopic rendering i.e. VR) we need to do this calculation per fragment to get a perspective correct result. + // Note that we're ignoring the x-offset for IPD, with Z sufficiently in the distance it becomes neglectible, as a result we could probably just set cube_normal.z to -1. + // The Matrix[2][0] (= asym_proj.x) and Matrix[2][1] (= asym_proj.z) values are what provide the right shift in the image. + + vec3 cube_normal; + cube_normal.z = -1000000.0; + cube_normal.x = (cube_normal.z * (-uv_interp.x - asym_proj.x)) / asym_proj.y; + cube_normal.y = (cube_normal.z * (-uv_interp.y - asym_proj.z)) / asym_proj.a; + cube_normal = mat3(pano_transform) * cube_normal; + cube_normal.z = -cube_normal.z; + + vec4 color = texturePanorama( normalize(cube_normal.xyz), source ); + #elif defined(USE_CUBEMAP) vec4 color = texture( source_cube, normalize(cube_interp) ); diff --git a/drivers/gles3/shaders/scene.glsl b/drivers/gles3/shaders/scene.glsl index 341a5bf2c7..b322a4c957 100644 --- a/drivers/gles3/shaders/scene.glsl +++ b/drivers/gles3/shaders/scene.glsl @@ -865,11 +865,57 @@ float contact_shadow_compute(vec3 pos, vec3 dir, float max_distance) { #endif -// GGX Specular -// Source: http://www.filmicworlds.com/images/ggx-opt/optimized-ggx.hlsl -float G1V(float dotNV, float k) -{ - return 1.0 / (dotNV * (1.0 - k) + k); + +// This returns the G_GGX function divided by 2 cos_theta_m, where in practice cos_theta_m is either N.L or N.V. +// We're dividing this factor off because the overall term we'll end up looks like +// (see, for example, the first unnumbered equation in B. Burley, "Physically Based Shading at Disney", SIGGRAPH 2012): +// +// F(L.V) D(N.H) G(N.L) G(N.V) / (4 N.L N.V) +// +// We're basically regouping this as +// +// F(L.V) D(N.H) [G(N.L)/(2 N.L)] [G(N.V) / (2 N.V)] +// +// and thus, this function implements the [G(N.m)/(2 N.m)] part with m = L or V. +// +// The contents of the D and G (G1) functions (GGX) are taken from +// E. Heitz, "Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs", J. Comp. Graph. Tech. 3 (2) (2014). +// Eqns 71-72 and 85-86 (see also Eqns 43 and 80). + +float G_GGX_2cos(float cos_theta_m, float alpha) { + // Schlick's approximation + // C. Schlick, "An Inexpensive BRDF Model for Physically-based Rendering", Computer Graphics Forum. 13 (3): 233 (1994) + // Eq. (19), although see Heitz (2014) the about the problems with his derivation. + // It nevertheless approximates GGX well with k = alpha/2. + float k = 0.5*alpha; + return 0.5 / (cos_theta_m * (1.0 - k) + k); + + // float cos2 = cos_theta_m*cos_theta_m; + // float sin2 = (1.0-cos2); + // return 1.0 /( cos_theta_m + sqrt(cos2 + alpha*alpha*sin2) ); +} + +float D_GXX(float cos_theta_m, float alpha) { + float alpha2 = alpha*alpha; + float d = 1.0 + (alpha2-1.0)*cos_theta_m*cos_theta_m; + return alpha2/(M_PI * d * d); +} + +float G_GGX_anisotropic_2cos(float cos_theta_m, float alpha_x, float alpha_y, float cos_phi, float sin_phi) { + float cos2 = cos_theta_m * cos_theta_m; + float sin2 = (1.0-cos2); + float s_x = alpha_x * cos_phi; + float s_y = alpha_y * sin_phi; + return 1.0 / (cos_theta_m + sqrt(cos2 + (s_x*s_x + s_y*s_y)*sin2 )); +} + +float D_GXX_anisotropic(float cos_theta_m, float alpha_x, float alpha_y, float cos_phi, float sin_phi) { + float cos2 = cos_theta_m * cos_theta_m; + float sin2 = (1.0-cos2); + float r_x = cos_phi/alpha_x; + float r_y = sin_phi/alpha_y; + float d = cos2 + sin2*(r_x * r_x + r_y * r_y); + return 1.0 / (M_PI * alpha_x * alpha_y * d * d ); } @@ -1019,7 +1065,6 @@ LIGHT_SHADER_CODE #elif defined(SPECULAR_SCHLICK_GGX) // shlick+ggx as default - float alpha = roughness * roughness; vec3 H = normalize(V + L); @@ -1035,26 +1080,22 @@ LIGHT_SHADER_CODE float ay = ry*ry; float XdotH = dot( T, H ); float YdotH = dot( B, H ); - float denom = XdotH*XdotH / (ax*ax) + YdotH*YdotH / (ay*ay) + cNdotH*cNdotH; - float D = 1.0 / ( M_PI * ax*ay * denom*denom ); + float D = D_GXX_anisotropic(cNdotH, ax, ay, XdotH, YdotH); + float G = G_GGX_anisotropic_2cos(cNdotL, ax, ay, XdotH, YdotH) * G_GGX_anisotropic_2cos(cNdotV, ax, ay, XdotH, YdotH); #else - float alphaSqr = alpha * alpha; - float denom = cNdotH * cNdotH * (alphaSqr - 1.0) + 1.0; - float D = alphaSqr / (M_PI * denom * denom); + float alpha = roughness * roughness; + float D = D_GGX(cNdotH, alpha); + float G = G_GGX_2cos(cNdotL, alpha) * G_GGX_2cos(cNdotV, alpha); #endif // F float F0 = 1.0; // FIXME float cLdotH5 = SchlickFresnel(cLdotH); float F = mix(cLdotH5, 1.0, F0); - // V - float k = alpha / 2.0f; - float vis = G1V(cNdotL, k) * G1V(cNdotV, k); - - float speci = cNdotL * D * F * vis; + float specular_brdf_NL = cNdotL * D * F * G; - specular_light += speci * light_color * specular_blob_intensity * attenuation; + specular_light += specular_brdf_NL * light_color * specular_blob_intensity * attenuation; #endif #if defined(LIGHT_USE_CLEARCOAT) @@ -1069,7 +1110,7 @@ LIGHT_SHADER_CODE #endif float Dr = GTR1(cNdotH, mix(.1, .001, clearcoat_gloss)); float Fr = mix(.04, 1.0, cLdotH5); - float Gr = G1V(cNdotL, .25) * G1V(cNdotV, .25); + float Gr = G_GGX_2cos(cNdotL, .25) * G_GGX_2cos(cNdotV, .25); specular_light += .25*clearcoat*Gr*Fr*Dr; diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index ff415c83f1..1ca88133b8 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -373,6 +373,9 @@ void EditorNode::_fs_changed() { String err = "Preset \"" + export_defer.preset + "\" doesn't have a platform."; ERR_PRINT(err.utf8().get_data()); } else { + // ensures export_project does not loop infinitely, because notifications may + // come during the export + export_defer.preset = ""; platform->export_project(preset, export_defer.debug, export_defer.path, /*p_flags*/ 0); } } diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index 477d440f28..d56756502d 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -467,6 +467,8 @@ void ScriptEditor::_update_recent_scripts() { recent_scripts->add_separator(); recent_scripts->add_shortcut(ED_SHORTCUT("script_editor/clear_recent", TTR("Clear Recent Files"))); + + recent_scripts->set_as_minsize(); } void ScriptEditor::_open_recent_script(int p_idx) { @@ -474,7 +476,7 @@ void ScriptEditor::_open_recent_script(int p_idx) { // clear button if (p_idx == recent_scripts->get_item_count() - 1) { previous_scripts.clear(); - _update_recent_scripts(); + call_deferred("_update_recent_scripts"); return; } @@ -2240,6 +2242,7 @@ void ScriptEditor::_bind_methods() { ClassDB::bind_method("_live_auto_reload_running_scripts", &ScriptEditor::_live_auto_reload_running_scripts); ClassDB::bind_method("_unhandled_input", &ScriptEditor::_unhandled_input); ClassDB::bind_method("_script_changed", &ScriptEditor::_script_changed); + ClassDB::bind_method("_update_recent_scripts", &ScriptEditor::_update_recent_scripts); ClassDB::bind_method(D_METHOD("get_current_script"), &ScriptEditor::_get_current_script); ClassDB::bind_method(D_METHOD("get_open_scripts"), &ScriptEditor::_get_open_scripts); diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp index 9f23df5c03..d1210ee26a 100644 --- a/editor/project_manager.cpp +++ b/editor/project_manager.cpp @@ -750,6 +750,9 @@ void ProjectManager::_unhandled_input(const Ref<InputEvent> &p_ev) { if (!k->is_pressed()) return; + if (tabs->get_current_tab() != 0) + return; + bool scancode_handled = true; switch (k->get_scancode()) { diff --git a/platform/javascript/javascript_eval.cpp b/platform/javascript/javascript_eval.cpp index 74f8d80a76..1d737879f6 100644 --- a/platform/javascript/javascript_eval.cpp +++ b/platform/javascript/javascript_eval.cpp @@ -39,24 +39,41 @@ JavaScript *JavaScript::get_singleton() { return singleton; } +extern "C" EMSCRIPTEN_KEEPALIVE uint8_t *resize_poolbytearray_and_open_write(PoolByteArray *p_arr, PoolByteArray::Write *r_write, int p_len) { + + p_arr->resize(p_len); + *r_write = p_arr->write(); + return r_write->ptr(); +} + Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) { union { - int i; + bool b; double d; char *s; } js_data[4]; + + PoolByteArray arr; + PoolByteArray::Write arr_write; + /* clang-format off */ Variant::Type return_type = static_cast<Variant::Type>(EM_ASM_INT({ + const CODE = $0; + const USE_GLOBAL_EXEC_CONTEXT = $1; + const PTR = $2; + const ELEM_LEN = $3; + const BYTEARRAY_PTR = $4; + const BYTEARRAY_WRITE_PTR = $5; var eval_ret; try { - if ($3) { // p_use_global_exec_context + if (USE_GLOBAL_EXEC_CONTEXT) { // indirect eval call grants global execution context var global_eval = eval; - eval_ret = global_eval(UTF8ToString($2)); + eval_ret = global_eval(UTF8ToString(CODE)); } else { - eval_ret = eval(UTF8ToString($2)); + eval_ret = eval(UTF8ToString(CODE)); } } catch (e) { Module.printErr(e); @@ -66,16 +83,11 @@ Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) { switch (typeof eval_ret) { case 'boolean': - // bitwise op yields 32-bit int - setValue($0, eval_ret|0, 'i32'); + setValue(PTR, eval_ret, 'i32'); return 1; // BOOL case 'number': - if ((eval_ret|0)===eval_ret) { - setValue($0, eval_ret|0, 'i32'); - return 2; // INT - } - setValue($0, eval_ret, 'double'); + setValue(PTR, eval_ret, 'double'); return 3; // REAL case 'string': @@ -85,7 +97,7 @@ Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) { if (array_ptr===0) { throw new Error('String allocation failed (probably out of memory)'); } - setValue($0, array_ptr|0 , '*'); + setValue(PTR, array_ptr , '*'); stringToUTF8(eval_ret, array_ptr, array_len); return 4; // STRING } catch (e) { @@ -102,41 +114,50 @@ Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) { break; } - else if (typeof eval_ret.x==='number' && typeof eval_ret.y==='number') { - setValue($0, eval_ret.x, 'double'); - setValue($0+$1, eval_ret.y, 'double'); + if (ArrayBuffer.isView(eval_ret) && !(eval_ret instanceof Uint8Array)) { + eval_ret = new Uint8Array(eval_ret.buffer); + } + else if (eval_ret instanceof ArrayBuffer) { + eval_ret = new Uint8Array(eval_ret); + } + if (eval_ret instanceof Uint8Array) { + var bytes_ptr = ccall('resize_poolbytearray_and_open_write', 'number', ['number', 'number' ,'number'], [BYTEARRAY_PTR, BYTEARRAY_WRITE_PTR, eval_ret.length]); + HEAPU8.set(eval_ret, bytes_ptr); + return 20; // POOL_BYTE_ARRAY + } + + if (typeof eval_ret.x==='number' && typeof eval_ret.y==='number') { + setValue(PTR, eval_ret.x, 'double'); + setValue(PTR + ELEM_LEN, eval_ret.y, 'double'); if (typeof eval_ret.z==='number') { - setValue($0+$1*2, eval_ret.z, 'double'); + setValue(PTR + ELEM_LEN*2, eval_ret.z, 'double'); return 7; // VECTOR3 } else if (typeof eval_ret.width==='number' && typeof eval_ret.height==='number') { - setValue($0+$1*2, eval_ret.width, 'double'); - setValue($0+$1*3, eval_ret.height, 'double'); + setValue(PTR + ELEM_LEN*2, eval_ret.width, 'double'); + setValue(PTR + ELEM_LEN*3, eval_ret.height, 'double'); return 6; // RECT2 } return 5; // VECTOR2 } - else if (typeof eval_ret.r==='number' && typeof eval_ret.g==='number' && typeof eval_ret.b==='number') { - // assume 8-bit rgb components since we're on the web - setValue($0, eval_ret.r, 'double'); - setValue($0+$1, eval_ret.g, 'double'); - setValue($0+$1*2, eval_ret.b, 'double'); - setValue($0+$1*3, typeof eval_ret.a==='number' ? eval_ret.a : 1, 'double'); + if (typeof eval_ret.r === 'number' && typeof eval_ret.g === 'number' && typeof eval_ret.b === 'number') { + setValue(PTR, eval_ret.r, 'double'); + setValue(PTR + ELEM_LEN, eval_ret.g, 'double'); + setValue(PTR + ELEM_LEN*2, eval_ret.b, 'double'); + setValue(PTR + ELEM_LEN*3, typeof eval_ret.a === 'number' ? eval_ret.a : 1, 'double'); return 14; // COLOR } break; } return 0; // NIL - }, js_data, sizeof *js_data, p_code.utf8().get_data(), p_use_global_exec_context)); + }, p_code.utf8().get_data(), p_use_global_exec_context, js_data, sizeof *js_data, &arr, &arr_write)); /* clang-format on */ switch (return_type) { case Variant::BOOL: - return !!js_data->i; - case Variant::INT: - return js_data->i; + return js_data->b; case Variant::REAL: return js_data->d; case Variant::STRING: { @@ -153,7 +174,10 @@ Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) { case Variant::RECT2: return Rect2(js_data[0].d, js_data[1].d, js_data[2].d, js_data[3].d); case Variant::COLOR: - return Color(js_data[0].d / 255., js_data[1].d / 255., js_data[2].d / 255., js_data[3].d); + return Color(js_data[0].d, js_data[1].d, js_data[2].d, js_data[3].d); + case Variant::POOL_BYTE_ARRAY: + arr_write = PoolByteArray::Write(); + return arr; } return Variant(); } diff --git a/scene/resources/audio_stream_sample.cpp b/scene/resources/audio_stream_sample.cpp index 1fd84a860e..fdc3b79db6 100644 --- a/scene/resources/audio_stream_sample.cpp +++ b/scene/resources/audio_stream_sample.cpp @@ -486,7 +486,8 @@ PoolVector<uint8_t> AudioStreamSample::get_data() const { { PoolVector<uint8_t>::Write w = pv.write(); - copymem(w.ptr(), data, data_bytes); + uint8_t *dataptr = (uint8_t *)data; + copymem(w.ptr(), dataptr + DATA_PAD, data_bytes); } } diff --git a/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp index 923639bc79..402e06f621 100644 --- a/scene/resources/default_theme/default_theme.cpp +++ b/scene/resources/default_theme/default_theme.cpp @@ -512,6 +512,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const // HSlider theme->set_stylebox("slider", "HSlider", make_stylebox(hslider_bg_png, 4, 4, 4, 4)); + theme->set_stylebox("grabber_area", "HSlider", make_stylebox(hslider_bg_png, 4, 4, 4, 4)); theme->set_stylebox("grabber_highlight", "HSlider", make_stylebox(hslider_grabber_hl_png, 6, 6, 6, 6)); theme->set_stylebox("grabber_disabled", "HSlider", make_stylebox(hslider_grabber_disabled_png, 6, 6, 6, 6)); theme->set_stylebox("focus", "HSlider", focus); @@ -524,6 +525,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const // VSlider theme->set_stylebox("slider", "VSlider", make_stylebox(vslider_bg_png, 4, 4, 4, 4)); + theme->set_stylebox("grabber_area", "VSlider", make_stylebox(vslider_bg_png, 4, 4, 4, 4)); theme->set_stylebox("grabber_highlight", "VSlider", make_stylebox(vslider_grabber_hl_png, 6, 6, 6, 6)); theme->set_stylebox("grabber_disabled", "VSlider", make_stylebox(vslider_grabber_disabled_png, 6, 6, 6, 6)); theme->set_stylebox("focus", "VSlider", focus); |