mirror of
https://github.com/Relintai/pandemonium_engine.git
synced 2024-12-30 07:37:16 +01:00
629 lines
16 KiB
C++
629 lines
16 KiB
C++
/*
|
|
Copyright (c) 2019 Flairieve
|
|
Copyright (c) 2020-2022 cobrapitz
|
|
Copyright (c) 2022 Péter Magyar
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in all
|
|
copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
SOFTWARE.
|
|
*/
|
|
|
|
#include "paint_canvas.h"
|
|
|
|
#include "../ui/paint_canvas_background.h"
|
|
#include "../ui/paint_canvas_outline.h"
|
|
#include "../paint_utilities.h"
|
|
#include "../ui/paint_visual_grid.h"
|
|
#include "scene/gui/control.h"
|
|
#include "scene/gui/texture_rect.h"
|
|
|
|
#include "paint_canvas_layer.h"
|
|
|
|
#include "../paint_icons/icons.h"
|
|
|
|
void PaintCanvasOld::_process(float delta) {
|
|
if (!is_visible_in_tree()) {
|
|
return;
|
|
}
|
|
|
|
Vector2 mouse_position = get_local_mouse_position();
|
|
Rect2 rect = Rect2(Vector2(0, 0), get_size());
|
|
mouse_in_region = rect.has_point(mouse_position);
|
|
}
|
|
void PaintCanvasOld::_draw() {
|
|
for (int i = 0; i < layers.size(); ++i) {
|
|
Ref<PaintCanvasLayer> layer = layers[i];
|
|
|
|
ERR_CONTINUE(!layer.is_valid());
|
|
|
|
layer->update_texture();
|
|
}
|
|
|
|
preview_layer->update_texture();
|
|
tool_layer->update_texture();
|
|
}
|
|
|
|
int PaintCanvasOld::get_pixel_size() const {
|
|
return _pixel_size;
|
|
}
|
|
|
|
void PaintCanvasOld::set_pixel_size(const int size) {
|
|
_pixel_size = size;
|
|
set_grid_size(_grid_size);
|
|
set_big_grid_size(_big_grid_size);
|
|
set_canvas_width(_canvas_width);
|
|
set_canvas_height(_canvas_height);
|
|
}
|
|
|
|
int PaintCanvasOld::get_grid_size() const {
|
|
return _grid_size;
|
|
}
|
|
void PaintCanvasOld::set_grid_size(const int size) {
|
|
_grid_size = size;
|
|
|
|
if (grid) {
|
|
int s = size * _pixel_size;
|
|
grid->set_grid_size(s);
|
|
}
|
|
}
|
|
|
|
int PaintCanvasOld::get_big_grid_size() const {
|
|
return _big_grid_size;
|
|
}
|
|
void PaintCanvasOld::set_big_grid_size(const int size) {
|
|
_big_grid_size = size;
|
|
|
|
if (big_grid) {
|
|
int s = size * _pixel_size;
|
|
big_grid->set_size(Size2(s, s));
|
|
}
|
|
}
|
|
|
|
int PaintCanvasOld::get_canvas_width() const {
|
|
return _canvas_width;
|
|
}
|
|
void PaintCanvasOld::set_canvas_width(const int val) {
|
|
_canvas_width = val;
|
|
|
|
Size2 s = get_size();
|
|
s.x = _canvas_width * _pixel_size;
|
|
set_size(s);
|
|
}
|
|
|
|
int PaintCanvasOld::get_canvas_height() const {
|
|
return _canvas_height;
|
|
}
|
|
void PaintCanvasOld::set_canvas_height(const int val) {
|
|
_canvas_height = val;
|
|
|
|
Size2 s = get_size();
|
|
s.y = _canvas_height * _pixel_size;
|
|
set_size(s);
|
|
}
|
|
void PaintCanvasOld::toggle_alpha_locked(const String &layer_name) {
|
|
Ref<PaintCanvasLayer> layer = find_layer_by_name(layer_name);
|
|
|
|
if (layer.is_valid()) {
|
|
layer->toggle_alpha_locked();
|
|
}
|
|
}
|
|
bool PaintCanvasOld::is_alpha_locked() {
|
|
if (!active_layer.is_valid()) {
|
|
return false;
|
|
}
|
|
|
|
return active_layer->alpha_locked;
|
|
}
|
|
Rect2 PaintCanvasOld::get_content_margin() {
|
|
Rect2 rect = Rect2(999999, 999999, -999999, -999999);
|
|
|
|
for (int i = 0; i < layers.size(); ++i) {
|
|
Ref<PaintCanvasLayer> l = layers[i];
|
|
|
|
ERR_CONTINUE(!l.is_valid());
|
|
|
|
Rect2 r = l->image->get_used_rect();
|
|
|
|
if (r.position.x < rect.position.x) {
|
|
rect.position.x = r.position.x;
|
|
}
|
|
|
|
if (r.position.y < rect.position.y) {
|
|
rect.position.y = r.position.y;
|
|
}
|
|
|
|
if (r.size.x > rect.size.x) {
|
|
rect.size.x = r.size.x;
|
|
}
|
|
|
|
if (r.size.y > rect.size.y) {
|
|
rect.size.y = r.size.y;
|
|
}
|
|
}
|
|
|
|
return rect;
|
|
}
|
|
void PaintCanvasOld::crop_to_content() {
|
|
/*
|
|
var rect = get_content_margin()
|
|
|
|
#print(rect)
|
|
|
|
for layer in layers:
|
|
layer.image
|
|
|
|
# set_canvas_width(rect.size.x)
|
|
# set_canvas_height(rect.size.x)
|
|
|
|
# preview_layer.resize(width, height)
|
|
# tool_layer.resize(width, height)
|
|
# for layer in layers:
|
|
# layer.resize(width, height)
|
|
*/
|
|
}
|
|
Ref<PaintCanvasLayer> PaintCanvasOld::get_active_layer() {
|
|
return active_layer;
|
|
}
|
|
Ref<PaintCanvasLayer> PaintCanvasOld::get_preview_layer() {
|
|
return preview_layer;
|
|
}
|
|
void PaintCanvasOld::clear_active_layer() {
|
|
if (active_layer.is_valid()) {
|
|
active_layer->clear();
|
|
}
|
|
}
|
|
void PaintCanvasOld::clear_preview_layer() {
|
|
preview_layer->clear();
|
|
}
|
|
|
|
void PaintCanvasOld::clear_layer(const String &layer_name) {
|
|
for (int i = 0; i < layers.size(); ++i) {
|
|
Ref<PaintCanvasLayer> l = layers[i];
|
|
|
|
ERR_CONTINUE(!l.is_valid());
|
|
|
|
if (l->name == layer_name) {
|
|
l->clear();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
Ref<PaintCanvasLayer> PaintCanvasOld::remove_layer(const String &layer_name) {
|
|
// change current layer if the active layer is removed
|
|
Ref<PaintCanvasLayer> del_layer = find_layer_by_name(layer_name);
|
|
|
|
del_layer->clear();
|
|
|
|
if (del_layer == active_layer) {
|
|
if (layers.size() > 1) {
|
|
for (int i = 0; i < layers.size(); ++i) {
|
|
Ref<PaintCanvasLayer> layer = layers[i];
|
|
|
|
ERR_CONTINUE(!layer.is_valid());
|
|
|
|
if (layer == active_layer) {
|
|
continue;
|
|
}
|
|
|
|
active_layer = layer;
|
|
break;
|
|
}
|
|
} else {
|
|
active_layer.unref();
|
|
}
|
|
}
|
|
|
|
layers.erase(del_layer);
|
|
|
|
return active_layer;
|
|
}
|
|
Ref<PaintCanvasLayer> PaintCanvasOld::add_new_layer(const String &layer_name) {
|
|
for (int i = 0; i < layers.size(); ++i) {
|
|
Ref<PaintCanvasLayer> layer = layers[i];
|
|
|
|
ERR_CONTINUE(!layer.is_valid());
|
|
|
|
if (layer->name == layer_name) {
|
|
return layer;
|
|
}
|
|
}
|
|
|
|
Ref<PaintCanvasLayer> layer;
|
|
layer.instance();
|
|
layer->name = layer_name;
|
|
|
|
TextureRect *texture_rect = memnew(TextureRect);
|
|
texture_rect->set_name(layer_name);
|
|
canvas_layers->add_child(texture_rect, true);
|
|
|
|
texture_rect->set_expand(true);
|
|
texture_rect->set_anchors_and_margins_preset(Control::PRESET_WIDE);
|
|
//texture_rect->set_margin(Margin::MARGIN_RIGHT, 0);
|
|
//texture_rect->set_margin(Margin::MARGIN_BOTTOM, 0);
|
|
|
|
texture_rect->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
|
|
|
|
layer->create(texture_rect, _canvas_width, _canvas_height);
|
|
layers.push_back(layer);
|
|
|
|
if (!active_layer.is_valid()) {
|
|
active_layer = layer;
|
|
}
|
|
|
|
return layer;
|
|
}
|
|
Ref<PaintCanvasLayer> PaintCanvasOld::duplicate_layer(const String &layer_name, const String &new_layer_name) {
|
|
for (int i = 0; i < layers.size(); ++i) {
|
|
Ref<PaintCanvasLayer> layer = layers[i];
|
|
|
|
ERR_CONTINUE(!layer.is_valid());
|
|
|
|
if (layer->name == new_layer_name) {
|
|
return layer;
|
|
}
|
|
}
|
|
|
|
Ref<PaintCanvasLayer> dup_layer = find_layer_by_name(layer_name);
|
|
Ref<PaintCanvasLayer> layer = add_new_layer(new_layer_name);
|
|
layer->image->copy_internals_from(dup_layer->image);
|
|
|
|
return layer;
|
|
}
|
|
void PaintCanvasOld::toggle_layer_visibility(const String &layer_name) {
|
|
for (int i = 0; i < layers.size(); ++i) {
|
|
Ref<PaintCanvasLayer> layer = layers[i];
|
|
|
|
ERR_CONTINUE(!layer.is_valid());
|
|
|
|
if (layer->name == layer_name) {
|
|
layer->set_visible(!layer->get_visible());
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
Ref<PaintCanvasLayer> PaintCanvasOld::find_layer_by_name(const String &layer_name) {
|
|
for (int i = 0; i < layers.size(); ++i) {
|
|
Ref<PaintCanvasLayer> layer = layers[i];
|
|
|
|
ERR_CONTINUE(!layer.is_valid());
|
|
|
|
if (layer->name == layer_name) {
|
|
return layer;
|
|
}
|
|
}
|
|
|
|
return Ref<PaintCanvasLayer>();
|
|
}
|
|
void PaintCanvasOld::toggle_lock_layer(const String &layer_name) {
|
|
find_layer_by_name(layer_name)->toggle_lock();
|
|
}
|
|
bool PaintCanvasOld::is_active_layer_locked() {
|
|
return active_layer->locked;
|
|
}
|
|
void PaintCanvasOld::move_layer_forward(const String &layer_name) {
|
|
TextureRect *layer = find_layer_by_name(layer_name)->texture_rect_ref;
|
|
int new_idx = MAX(layer->get_index() - 1, 0);
|
|
canvas_layers->move_child(layer, new_idx);
|
|
}
|
|
void PaintCanvasOld::move_layer_back(const String &layer_name) {
|
|
TextureRect *layer = find_layer_by_name(layer_name)->texture_rect_ref;
|
|
canvas_layers->move_child(layer, layer->get_index() + 1);
|
|
}
|
|
|
|
void PaintCanvasOld::select_layer(const String &layer_name) {
|
|
active_layer = find_layer_by_name(layer_name);
|
|
}
|
|
void PaintCanvasOld::_on_mouse_entered() {
|
|
mouse_on_top = true;
|
|
}
|
|
void PaintCanvasOld::_on_mouse_exited() {
|
|
mouse_on_top = false;
|
|
}
|
|
bool PaintCanvasOld::is_inside_canvas(const int x, const int y) {
|
|
if (x < 0 || y < 0) {
|
|
return false;
|
|
}
|
|
if (x >= _canvas_width || y >= _canvas_height) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//Note: Arrays are always passed by reference. To get a copy of an array which
|
|
// can be modified independently of the original array, use duplicate.
|
|
// (https://docs.godotengine.org/en/stable/classes/class_array.html)
|
|
void PaintCanvasOld::set_pixel_arr(const PoolVector2iArray &pixels, const Color &color) {
|
|
PoolVector2iArray::Read r = pixels.read();
|
|
|
|
for (int i = 0; i < pixels.size(); ++i) {
|
|
const Vector2i &pixel = r[i];
|
|
|
|
_set_pixel(active_layer, pixel.x, pixel.y, color);
|
|
}
|
|
}
|
|
void PaintCanvasOld::set_pixel_v(const Vector2i &pos, const Color &color) {
|
|
set_pixel(pos.x, pos.y, color);
|
|
}
|
|
void PaintCanvasOld::set_pixel(const int x, const int y, const Color &color) {
|
|
_set_pixel(active_layer, x, y, color);
|
|
}
|
|
void PaintCanvasOld::_set_pixel_v(Ref<PaintCanvasLayer> layer, const Vector2i &v, const Color &color) {
|
|
_set_pixel(layer, v.x, v.y, color);
|
|
}
|
|
void PaintCanvasOld::_set_pixel(Ref<PaintCanvasLayer> layer, const int x, const int y, const Color &color) {
|
|
if (!is_inside_canvas(x, y)) {
|
|
return;
|
|
}
|
|
|
|
layer->set_pixel(x, y, color);
|
|
}
|
|
Color PaintCanvasOld::get_pixel_v(const Vector2i &pos) {
|
|
return get_pixel(pos.x, pos.y);
|
|
}
|
|
Color PaintCanvasOld::get_pixel(const int x, const int y) {
|
|
if (active_layer.is_valid()) {
|
|
return active_layer->get_pixel(x, y);
|
|
}
|
|
|
|
return Color();
|
|
}
|
|
void PaintCanvasOld::set_preview_pixel_v(const Vector2i &pos, const Color &color) {
|
|
set_preview_pixel(pos.x, pos.y, color);
|
|
}
|
|
|
|
void PaintCanvasOld::set_preview_pixel(const int x, const int y, const Color &color) {
|
|
if (!is_inside_canvas(x, y)) {
|
|
return;
|
|
}
|
|
|
|
preview_layer->set_pixel(x, y, color);
|
|
}
|
|
Color PaintCanvasOld::get_preview_pixel_v(const Vector2i &pos) {
|
|
return get_preview_pixel(pos.x, pos.y);
|
|
}
|
|
|
|
Color PaintCanvasOld::get_preview_pixel(const int x, const int y) {
|
|
return preview_layer->get_pixel(x, y);
|
|
}
|
|
|
|
bool PaintCanvasOld::validate_pixel_v(const Vector2i &pos) const {
|
|
if (active_layer.is_valid()) {
|
|
return active_layer->validate_pixel_v(pos);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void PaintCanvasOld::toggle_grid() {
|
|
grid->set_visible(!grid->is_visible());
|
|
}
|
|
void PaintCanvasOld::show_grid() {
|
|
grid->show();
|
|
}
|
|
void PaintCanvasOld::hide_grid() {
|
|
grid->hide();
|
|
}
|
|
|
|
PoolVector2iArray PaintCanvasOld::select_color(const int p_x, const int p_y) {
|
|
PoolVector2iArray same_color_pixels;
|
|
|
|
Color color = get_pixel(p_x, p_y);
|
|
for (int x = 0; x < active_layer->layer_width; ++x) {
|
|
for (int y = 0; y < active_layer->layer_height; ++y) {
|
|
Color pixel_color = active_layer->get_pixel(x, y);
|
|
if (pixel_color == color) {
|
|
same_color_pixels.append(Vector2i(x, y));
|
|
}
|
|
}
|
|
}
|
|
|
|
return same_color_pixels;
|
|
}
|
|
PoolVector2iArray PaintCanvasOld::select_same_color(const int p_x, const int p_y) {
|
|
return get_neighbouring_pixels(p_x, p_y);
|
|
}
|
|
|
|
// returns array of Vector2
|
|
// yoinked from
|
|
// https://www.geeksforgeeks.org/flood-fill-algorithm-implement-fill-paint/
|
|
PoolVector2iArray PaintCanvasOld::get_neighbouring_pixels(const int pos_x, const int pos_y) {
|
|
PoolVector2iArray pixels;
|
|
|
|
PoolIntArray to_check_queue;
|
|
PoolIntArray checked_queue;
|
|
|
|
to_check_queue.append(PaintUtilities::to_1D(pos_x, pos_y, _canvas_width));
|
|
|
|
Color color = get_pixel(pos_x, pos_y);
|
|
|
|
while (!to_check_queue.empty()) {
|
|
int idx = to_check_queue[0];
|
|
to_check_queue.remove(0);
|
|
Vector2i p = PaintUtilities::to_2D(idx, _canvas_width);
|
|
|
|
if (checked_queue.contains(idx)) {
|
|
continue;
|
|
}
|
|
|
|
checked_queue.append(idx);
|
|
|
|
if (get_pixel(p.x, p.y) != color) {
|
|
continue;
|
|
}
|
|
|
|
// add to result
|
|
pixels.append(p);
|
|
|
|
// check neighbours
|
|
int x = p.x - 1;
|
|
int y = p.y;
|
|
if (is_inside_canvas(x, y)) {
|
|
idx = PaintUtilities::to_1D(x, y, _canvas_width);
|
|
to_check_queue.append(idx);
|
|
}
|
|
|
|
x = p.x + 1;
|
|
if (is_inside_canvas(x, y)) {
|
|
idx = PaintUtilities::to_1D(x, y, _canvas_width);
|
|
to_check_queue.append(idx);
|
|
}
|
|
|
|
x = p.x;
|
|
y = p.y - 1;
|
|
if (is_inside_canvas(x, y)) {
|
|
idx = PaintUtilities::to_1D(x, y, _canvas_width);
|
|
to_check_queue.append(idx);
|
|
}
|
|
|
|
y = p.y + 1;
|
|
if (is_inside_canvas(x, y)) {
|
|
idx = PaintUtilities::to_1D(x, y, _canvas_width);
|
|
to_check_queue.append(idx);
|
|
}
|
|
}
|
|
|
|
return pixels;
|
|
}
|
|
|
|
void PaintCanvasOld::resize(int width, int height) {
|
|
//if (get_canvas_width() == width && get_canvas_height() == height) {
|
|
// return;
|
|
// }
|
|
|
|
if (width < 0) {
|
|
width = 1;
|
|
}
|
|
|
|
if (height < 0) {
|
|
height = 1;
|
|
}
|
|
|
|
_canvas_width = width;
|
|
_canvas_height = height;
|
|
|
|
Size2 s;
|
|
s.x = _canvas_width * _pixel_size;
|
|
s.y = _canvas_height * _pixel_size;
|
|
set_size(s);
|
|
|
|
preview_layer->resize(width, height);
|
|
tool_layer->resize(width, height);
|
|
|
|
for (int i = 0; i < layers.size(); ++i) {
|
|
Ref<PaintCanvasLayer> layer = layers[i];
|
|
|
|
ERR_CONTINUE(!layer.is_valid());
|
|
|
|
layer->resize(width, height);
|
|
}
|
|
|
|
update();
|
|
}
|
|
|
|
void PaintCanvasOld::_notification(int p_what) {
|
|
switch (p_what) {
|
|
case NOTIFICATION_POSTINITIALIZE: {
|
|
connect("mouse_entered", this, "_on_mouse_entered");
|
|
connect("mouse_exited", this, "_on_mouse_exited");
|
|
|
|
//canvas_size = Vector2(int(rect_size.x / grid_size), int(rect_size.y / grid_size));
|
|
//pixel_size = canvas_size;
|
|
|
|
active_layer = add_new_layer("Layer1");
|
|
|
|
////hack
|
|
//_canvas_width = 0;
|
|
//_canvas_height = 0;
|
|
|
|
set_process(true);
|
|
} break;
|
|
case NOTIFICATION_ENTER_TREE: {
|
|
resize(64, 64);
|
|
} break;
|
|
case NOTIFICATION_PROCESS: {
|
|
_process(get_process_delta_time());
|
|
} break;
|
|
case NOTIFICATION_DRAW: {
|
|
_draw();
|
|
} break;
|
|
}
|
|
}
|
|
|
|
PaintCanvasOld::PaintCanvasOld() {
|
|
big_grid = nullptr;
|
|
|
|
_pixel_size = 16;
|
|
_canvas_width = 64;
|
|
_canvas_height = 64;
|
|
_grid_size = 16;
|
|
_big_grid_size = 10;
|
|
_can_draw = true;
|
|
|
|
symmetry_x = false;
|
|
symmetry_y = false;
|
|
|
|
mouse_in_region = false;
|
|
mouse_on_top = false;
|
|
|
|
canvas_background = memnew(PaintCanvasBackground);
|
|
canvas_background->set_grid_size(32);
|
|
add_child(canvas_background);
|
|
|
|
canvas_layers = memnew(Control);
|
|
canvas_layers->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
|
|
canvas_layers->set_anchors_and_margins_preset(Control::PRESET_WIDE);
|
|
add_child(canvas_layers);
|
|
|
|
preview_layer_rect = memnew(TextureRect);
|
|
preview_layer_rect->set_expand(true);
|
|
preview_layer_rect->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
|
|
preview_layer_rect->set_anchors_and_margins_preset(Control::PRESET_WIDE);
|
|
add_child(preview_layer_rect);
|
|
|
|
tool_preview_layer_rect = memnew(TextureRect);
|
|
tool_preview_layer_rect->set_expand(true);
|
|
tool_preview_layer_rect->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
|
|
tool_preview_layer_rect->set_anchors_and_margins_preset(Control::PRESET_WIDE);
|
|
add_child(tool_preview_layer_rect);
|
|
|
|
grid = memnew(PaintVisualGrid);
|
|
grid->set_grid_size(4);
|
|
grid->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
|
|
add_child(grid);
|
|
|
|
canvas_outline = memnew(PaintCanvasOutline);
|
|
canvas_outline->color = Color(0, 1, 0, 1);
|
|
canvas_outline->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
|
|
add_child(canvas_outline);
|
|
|
|
preview_layer.instance();
|
|
tool_layer.instance();
|
|
preview_layer->name = "Preview";
|
|
tool_layer->name = "Tool";
|
|
preview_layer->create(preview_layer_rect, _canvas_width, _canvas_height);
|
|
tool_layer->create(tool_preview_layer_rect, _canvas_width, _canvas_height);
|
|
}
|
|
|
|
PaintCanvasOld::~PaintCanvasOld() {
|
|
}
|
|
|
|
void PaintCanvasOld::_bind_methods() {
|
|
ClassDB::bind_method(D_METHOD("_on_mouse_entered"), &PaintCanvasOld::_on_mouse_entered);
|
|
ClassDB::bind_method(D_METHOD("_on_mouse_exited"), &PaintCanvasOld::_on_mouse_exited);
|
|
}
|