FluidSim backward caching

This commit is contained in:
MakhamDev 2023-10-03 12:14:28 +07:00
parent 0311e2eeb6
commit 864e79d8a4
12 changed files with 119 additions and 81 deletions

View file

@ -7,8 +7,8 @@ function Node_VFX_Spawner_Base(_x, _y, _group = noone) : Node(_x, _y, _group) co
inputs[| 1] = nodeValue("Spawn delay", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, 4, "Frames delay between each particle spawn." )
.rejectArray();
inputs[| 2] = nodeValue("Spawn amount", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, 2, "Amount of particle spawn in that frame." )
.rejectArray();
inputs[| 2] = nodeValue("Spawn amount", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, [ 2, 2 ], "Amount of particle spawn in that frame." )
.setDisplay(VALUE_DISPLAY.range, { linked : true });
inputs[| 3] = nodeValue("Spawn area", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, [ DEF_SURF_W / 2, DEF_SURF_H / 2, DEF_SURF_W / 2, DEF_SURF_H / 2, AREA_SHAPE.rectangle ] )
.setDisplay(VALUE_DISPLAY.area);
@ -48,7 +48,7 @@ function Node_VFX_Spawner_Base(_x, _y, _group = noone) : Node(_x, _y, _group) co
inputs[| 16] = nodeValue("Spawn type", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, 0)
.rejectArray()
.setDisplay(VALUE_DISPLAY.enum_button, [ "Stream", "Burst" ]);
.setDisplay(VALUE_DISPLAY.enum_button, [ "Stream", "Burst", "Trigger" ]);
inputs[| 17] = nodeValue("Spawn size", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, [ 1, 1 ] )
.setDisplay(VALUE_DISPLAY.range, { linked : true });
@ -144,11 +144,16 @@ function Node_VFX_Spawner_Base(_x, _y, _group = noone) : Node(_x, _y, _group) co
.setDisplay(VALUE_DISPLAY.vector, { label: [ "Amplitude", "Period" ], linkable: false, per_line: true })
.rejectArray();
triggerSpawn = function() { inputs[| 44].setAnim(true); inputs[| 44].setValue(true); };
inputs[| 44] = nodeValue("Spawn", self, JUNCTION_CONNECT.input, VALUE_TYPE.trigger, false )
.setDisplay(VALUE_DISPLAY.button, { name: "Trigger", onClick: triggerSpawn, output: true })
.rejectArray();
input_len = ds_list_size(inputs);
input_display_list = [ 32,
["Sprite", false], 0, 22, 23, 26,
["Spawn", true], 27, 16, 1, 2, 3, 4, 30, 31, 24, 25, 5,
["Spawn", true], 27, 16, 44, 1, 2, 3, 4, 30, 31, 24, 25, 5,
["Movement", true], 29, 6, 18,
["Physics", true], 7, 19, 33, 34, 35, 36,
["Ground", true], 37, 38, 39, 40,
@ -193,7 +198,6 @@ function Node_VFX_Spawner_Base(_x, _y, _group = noone) : Node(_x, _y, _group) co
var _inSurf = current_data[0];
var _spawn_amount = current_data[ 2];
var _amo = _spawn_amount;
var _spawn_area = current_data[ 3];
var _distrib = current_data[ 4];
@ -237,9 +241,12 @@ function Node_VFX_Spawner_Base(_x, _y, _group = noone) : Node(_x, _y, _group) co
var _posDist = [];
if(_distrib == 2) _posDist = get_points_from_dist(_dist_map, _amo, seed);
random_set_seed(seed);
var _amo = irandom_range(_spawn_amount[0], _spawn_amount[1]);
for( var i = 0; i < _amo; i++ ) {
random_set_seed(seed);
seed += 100;
random_set_seed(seed);
parts_runner = clamp(parts_runner, 0, array_length(parts) - 1);
var part = parts[parts_runner];
@ -404,6 +411,9 @@ function Node_VFX_Spawner_Base(_x, _y, _group = noone) : Node(_x, _y, _group) co
var _spawn_delay = inputs[| 1].getValue(_time);
var _spawn_type = inputs[| 16].getValue(_time);
var _spawn_active = inputs[| 27].getValue(_time);
var _spawn_trig = inputs[| 44].getValue(_time);
//print($"{_time} : {_spawn_trig} | {ds_list_to_array(inputs[| 44].animator.values)}");
for( var i = 0; i < ds_list_size(inputs); i++ )
current_data[i] = inputs[| i].getValue(_time);
@ -424,14 +434,9 @@ function Node_VFX_Spawner_Base(_x, _y, _group = noone) : Node(_x, _y, _group) co
if(_spawn_active) {
switch(_spawn_type) {
case 0 :
if(safe_mod(_time, _spawn_delay) == 0)
spawn(_time);
break;
case 1 :
if(_time == _spawn_delay)
spawn(_time);
break;
case 0 : if(safe_mod(_time, _spawn_delay) == 0) spawn(_time); break;
case 1 : if(_time == _spawn_delay) spawn(_time); break;
case 2 : if(_spawn_trig) spawn(_time); break;
}
}
@ -455,6 +460,7 @@ function Node_VFX_Spawner_Base(_x, _y, _group = noone) : Node(_x, _y, _group) co
var _dirAng = getInputData(29);
var _turn = getInputData(34);
var _colGnd = getInputData(37);
var _spwTyp = getInputData(16);
inputs[| 6].setVisible(!_dirAng);
@ -474,6 +480,12 @@ function Node_VFX_Spawner_Base(_x, _y, _group = noone) : Node(_x, _y, _group) co
inputs[| 23].setVisible(false);
inputs[| 26].setVisible(false);
inputs[| 1].setVisible(_spwTyp < 2);
if(_spwTyp == 0) inputs[| 1].name = "Spawn delay";
else if(_spwTyp == 1) inputs[| 1].name = "Spawn frame";
inputs[| 44].setVisible(_spwTyp == 2);
if(is_array(_inSurf)) {
inputs[| 22].setVisible(true);
var _type = getInputData(22);
@ -496,10 +508,6 @@ function Node_VFX_Spawner_Base(_x, _y, _group = noone) : Node(_x, _y, _group) co
static update = function(frame = PROJECT.animator.current_frame) { #region
checkPartPool();
var _spawn_type = getInputData(16);
if(_spawn_type == 0) inputs[| 1].name = "Spawn delay";
else inputs[| 1].name = "Spawn frame";
onUpdate();
} #endregion

View file

@ -15,4 +15,16 @@ function Node_Fluid(_x, _y, _group = noone) : Node(_x, _y, _group) constructor {
_to.node.updateForward(frame);
}
}
static cachedPropagate = function() {
setRenderStatus(true);
for( var i = 0, n = ds_list_size(inputs); i < n; i++ ) {
var _input = inputs[| i];
if(_input.value_from == noone) continue;
if(!is_instanceof(_input.value_from.node, Node_Fluid)) continue;
_input.value_from.node.cachedPropagate();
}
}
}

View file

@ -160,7 +160,8 @@ function Node(_x, _y, _group = PANEL_GRAPH.getCurrentContext()) : __Node_Base(_x
auto_render_time = true;
updated = false;
use_cache = CACHE_USE.none;
use_cache = CACHE_USE.none;
cached_manual = false;
clearCacheOnChange = true;
cached_output = [];
cache_result = [];
@ -378,7 +379,7 @@ function Node(_x, _y, _group = PANEL_GRAPH.getCurrentContext()) : __Node_Base(_x
if(!is_instanceof(inputs[| i].editWidget, buttonClass)) continue;
var trig = inputs[| i].getValue();
if(trig) {
if(trig && !inputs[| i].display_data.output) {
inputs[| i].editWidget.onClick();
inputs[| i].setValue(false);
}
@ -431,7 +432,7 @@ function Node(_x, _y, _group = PANEL_GRAPH.getCurrentContext()) : __Node_Base(_x
var render_timer = get_timer();
if(use_cache == CACHE_USE.auto && recoverCache()) {
if(cached_manual || (use_cache == CACHE_USE.auto && recoverCache())) {
render_cached = true;
} else {
render_cached = false;
@ -442,7 +443,7 @@ function Node(_x, _y, _group = PANEL_GRAPH.getCurrentContext()) : __Node_Base(_x
anim_last_step = isAnimated() || _hash != input_hash || !rendered;
LOG_BLOCK_START();
LOG_IF(global.FLAG.render, $">>>>>>>>>> DoUpdate called from {internalName} [{anim_last_step}] <<<<<<<<<<");
LOG_IF(global.FLAG.render, $">>>>>>>>>> DoUpdate called from {internalName} [{anim_last_step}] [{update_on_frame}, {isAnimated()}, {_hash != input_hash}, {!rendered}] <<<<<<<<<<");
try {
if(anim_last_step) update(); ///Update only if input hash differs from previous.
@ -455,10 +456,7 @@ function Node(_x, _y, _group = PANEL_GRAPH.getCurrentContext()) : __Node_Base(_x
}
}
if(!is_instanceof(self, Node_Collection)) {
setRenderStatus(true);
render_time = get_timer() - render_timer;
}
cached_manual = false;
if(!use_cache && PROJECT.onion_skin) {
for( var i = 0; i < ds_list_size(outputs); i++ ) {
@ -479,6 +477,12 @@ function Node(_x, _y, _group = PANEL_GRAPH.getCurrentContext()) : __Node_Base(_x
}
if(autoUpdatedTrigger) updatedTrigger.setValue(true);
if(!is_instanceof(self, Node_Collection)) {
setRenderStatus(true);
render_time = get_timer() - render_timer;
}
LOG_BLOCK_END();
} #endregion
@ -968,7 +972,7 @@ function Node(_x, _y, _group = PANEL_GRAPH.getCurrentContext()) : __Node_Base(_x
draw_set_color(COLORS.speed[0]);
}
if(render_cached) draw_set_color(COLORS._main_text_sub);
if(render_cached || !anim_last_step) draw_set_color(COLORS._main_text_sub);
draw_text(round(tx), round(ty), string(rt) + " " + unit);
}

View file

@ -2,7 +2,6 @@ function Node_Fluid_Domain(_x, _y, _group = noone) : Node_Fluid(_x, _y, _group)
name = "Fluid Domain";
color = COLORS.node_blend_fluid;
icon = THEME.fluid_sim;
update_on_frame = true;
min_h = 128;

View file

@ -17,11 +17,13 @@ function Node_Fluid_Group(_x, _y, _group = noone) : Node_Collection(_x, _y, _gro
_update.inputs[| 0].setFrom(_domain.outputs[| 0]);
}
static onStep = function() {
RETURN_ON_REST
setRenderStatus(false);
RENDER_ALL
static update = function() {
for( var i = 0, n = ds_list_size(nodes); i < n; i++ ) {
var node = nodes[| i];
if(!is_instanceof(node, Node_Fluid_Render)) continue;
if(node.cacheExist()) node.cachedPropagate();
}
}
PATCH_STATIC

View file

@ -62,6 +62,8 @@ function valueKey(_time, _value, _anim = noone, _in = 0, _ot = 0) constructor {
return key;
} #endregion
static toString = function() { return $"[Keyframe] {time}: {value}"; }
}
function valueAnimator(_val, _prop, _sep_axis = false) constructor {
@ -473,9 +475,12 @@ function valueAnimator(_val, _prop, _sep_axis = false) constructor {
} else if(!sep_axis && typeArray(prop.display_type)) {
_val = [];
if(is_array(value))
for(var j = 0; j < array_length(value); j++)
_val[j] = processValue(value[j]);
if(is_array(value)) {
for(var j = 0; j < array_length(value); j++)
_val[j] = processValue(value[j]);
} else if(is_array(base))
for(var j = 0; j < array_length(base); j++)
_val[j] = processValue(value);
}
//print($"Deserialize {prop.node.name}:{prop.name} = {_val} ");

View file

@ -423,13 +423,13 @@ function NodeObject(_name, _spr, _node, _create, tags = []) constructor { #regio
addNodeObject(generator, "4 Points Gradient", s_node_gradient_4points, "Node_Gradient_Points", [1, Node_Gradient_Points],, "Create image from 4 color points.");
ds_list_add(generator, "Drawer");
//addNodeObject(generator, "Pixel Builder", s_node_pixel_builder, "Node_Pixel_Builder", [1, Node_Pixel_Builder]).setVersion(1147);
addNodeObject(generator, "Line", s_node_line, "Node_Line", [1, Node_Line],, "Draw line on an image. Connect path data to it to draw line from path.");
addNodeObject(generator, "Draw Text", s_node_text_render, "Node_Text", [1, Node_Text],, "Draw text on an image.");
addNodeObject(generator, "Shape", s_node_shape, "Node_Shape", [1, Node_Shape],, "Draw simple shapes using signed distance field.");
addNodeObject(generator, "Polygon Shape", s_node_shape_polygon, "Node_Shape_Polygon", [1, Node_Shape_Polygon],, "Draw simple shapes using triangles.").setVersion(1130);
addNodeObject(generator, "Interpret Number",s_node_interpret_number,"Node_Interpret_Number",[1, Node_Interpret_Number]).setVersion(11530);
addNodeObject(generator, "Random Shape", s_node_random_shape, "Node_Random_Shape", [1, Node_Random_Shape]).setVersion(1147);
addNodeObject(generator, "Pixel Builder", s_node_pixel_builder, "Node_Pixel_Builder", [1, Node_Pixel_Builder]).setVersion(11540);
ds_list_add(generator, "Noises");
addNodeObject(generator, "Noise", s_node_noise, "Node_Noise", [1, Node_Noise],, "Generate white noise.");
@ -807,7 +807,7 @@ function NodeObject(_name, _spr, _node, _create, tags = []) constructor { #regio
addNodeObject(hid, "Sort Input", s_node_grid_hex_noise, "Node_Iterator_Sort_Input", [1, Node_Iterator_Sort_Input]);
addNodeObject(hid, "Sort Output", s_node_grid_hex_noise, "Node_Iterator_Sort_Output", [1, Node_Iterator_Sort_Output]);
addNodeObject(hid, "Onion Skin", s_node_cache, "Node_Onion_Skin", [1, Node_Onion_Skin]).setVersion(1147);
addNodeObject(hid, "Pixel Builder", s_node_pixel_builder, "Node_Pixel_Builder", [1, Node_Pixel_Builder]).setVersion(1150);
//addNodeObject(hid, "Pixel Builder", s_node_pixel_builder, "Node_Pixel_Builder", [1, Node_Pixel_Builder]).setVersion(1150);
addNodeObject(hid, "Input", s_node_pixel_builder, "Node_DynaSurf_In", [1, Node_DynaSurf_In]);
addNodeObject(hid, "Output", s_node_pixel_builder, "Node_DynaSurf_Out", [1, Node_DynaSurf_Out]);

View file

@ -721,6 +721,8 @@ function NodeValue(_name, _node, _connect, _type, _value, _tooltip = "") constru
case VALUE_DISPLAY.button : #region
editWidget = button(display_data.onClick);
editWidget.text = display_data.name;
if(!struct_has(display_data, "output")) display_data.output = false;
visible = false;
return; #endregion
}

View file

@ -32,8 +32,8 @@ function Node_Widget_Test(_x, _y, _group = noone) : Node(_x, _y, _group) constru
inputs[| 22] = nodeValue("buttonGradient", self, JUNCTION_CONNECT.input, VALUE_TYPE.gradient, new gradientObject(c_white)) .setDisplay(VALUE_DISPLAY._default)
inputs[| 23] = nodeValue("pathArrayBox", self, JUNCTION_CONNECT.input, VALUE_TYPE.path, []) .setDisplay(VALUE_DISPLAY.path_array, { filter: [ "*.png", "" ] })
inputs[| 24] = nodeValue("textBox", self, JUNCTION_CONNECT.input, VALUE_TYPE.path, "") .setDisplay(VALUE_DISPLAY.path_load)
inputs[| 25] = nodeValue("textBox", self, JUNCTION_CONNECT.input, VALUE_TYPE.path, "") .setDisplay(VALUE_DISPLAY.path_save)
inputs[| 24] = nodeValue("pathLoad", self, JUNCTION_CONNECT.input, VALUE_TYPE.path, "") .setDisplay(VALUE_DISPLAY.path_load)
inputs[| 25] = nodeValue("pathSave", self, JUNCTION_CONNECT.input, VALUE_TYPE.path, "") .setDisplay(VALUE_DISPLAY.path_save)
inputs[| 26] = nodeValue("fontScrollBox", self, JUNCTION_CONNECT.input, VALUE_TYPE.path, "") .setDisplay(VALUE_DISPLAY.path_font)
inputs[| 27] = nodeValue("curveBox", self, JUNCTION_CONNECT.input, VALUE_TYPE.curve, CURVE_DEF_11) .setDisplay(VALUE_DISPLAY._default)

View file

@ -7,7 +7,7 @@ enum TEXT_AREA_FORMAT {
node_title,
}
function textArea(_input, _onModify, _extras = noone) : textInput(_input, _onModify, _extras) constructor {
function textArea(_input, _onModify) : textInput(_input, _onModify) constructor {
font = f_p0;
hide = false;
color = COLORS._main_text;
@ -546,7 +546,7 @@ function textArea(_input, _onModify, _extras = noone) : textInput(_input, _onMod
var target = -999;
draw_set_text(font, fa_left, fa_top, color);
draw_set_alpha(0.5 + 0.5 * interactable)
draw_set_alpha(0.5 + 0.5 * interactable);
var ch_x = _x;
var ch_y = _y;
@ -636,20 +636,20 @@ function textArea(_input, _onModify, _extras = noone) : textInput(_input, _onMod
}
}
if(target != -999 && mouse_press(mb_left, active) && HOVER != autocomplete_box.id) {
if(click_block == 1) {
cursor = target;
cursor_select = -1;
click_block = 0;
if(target != -999 && HOVER != autocomplete_box.id) {
if(mouse_press(mb_left, active) && !click_block) {
cursor_select = target;
cursor = target;
autocomplete_box.active = false;
} else if(cursor != target) {
if(cursor_select == -1)
cursor_select = cursor;
autocomplete_box.active = true;
} else if(mouse_click(mb_left, active) && cursor != target) {
cursor = target;
autocomplete_box.active = false;
}
if(mouse_press(mb_left, active))
click_block = false;
}
} #endregion
@ -676,9 +676,9 @@ function textArea(_input, _onModify, _extras = noone) : textInput(_input, _onMod
w = _w;
}
if(extras && instanceof(extras) == "buttonClass") {
extras.setFocusHover(active, hover);
extras.draw(_x + _w - ui(32), _y + _h / 2 - ui(32 / 2), ui(32), ui(32), _m, THEME.button_hide);
if(side_button && instanceof(side_button) == "buttonClass") {
side_button.setFocusHover(active, hover);
side_button.draw(_x + _w - ui(32), _y + _h / 2 - ui(32 / 2), ui(32), ui(32), _m, THEME.button_hide);
_w -= ui(40);
}
@ -715,7 +715,7 @@ function textArea(_input, _onModify, _extras = noone) : textInput(_input, _onMod
var hoverRect = point_in_rectangle(_m[0], _m[1], _x, _y, _x + _w, _y + hh);
if(self == WIDGET_CURRENT) {
if(selecting) {
WIDGET_TAB_BLOCK = true;
draw_set_text(font, fa_left, fa_top, COLORS._main_text);
@ -831,6 +831,7 @@ function textArea(_input, _onModify, _extras = noone) : textInput(_input, _onMod
onModify(DRAGGING.data);
}
selecting = self == WIDGET_CURRENT;
resetFocus();
shift_new_line = true;

View file

@ -3,7 +3,7 @@ enum TEXTBOX_INPUT {
number
}
function textBox(_input, _onModify, _extras = noone) : textInput(_input, _onModify, _extras) constructor {
function textBox(_input, _onModify) : textInput(_input, _onModify) constructor {
align = _input == TEXTBOX_INPUT.number? fa_right : fa_left;
hide = false;
font = noone;
@ -250,8 +250,7 @@ function textBox(_input, _onModify, _extras = noone) : textInput(_input, _onModi
static display_text = function(_x, _y, _text, _w, _m = -1) { #region
_text = string_real(_text);
BLEND_OVERRIDE;
if(!interactable) draw_set_alpha(0.5);
draw_set_alpha(0.5 + 0.5 * interactable);
switch(format) {
case TEXT_AREA_FORMAT._default :
@ -265,34 +264,37 @@ function textBox(_input, _onModify, _extras = noone) : textInput(_input, _onModi
}
draw_set_alpha(1);
BLEND_NORMAL;
var _xx = _x, _ch, _chw;
var _xx = _x + disp_x;
var _mm = _m;
var target = -999;
if(!sliding && _m != -1) {
if(!sliding && _mm >= 0) {
for( var i = 1; i <= string_length(_text); i++ ) {
_ch = string_char_at(_text, i);
_chw = string_width(_ch);
var _ch = string_char_at(_text, i);
var _chw = string_width(_ch);
if(_m < _xx + _chw / 2) {
if(_mm < _xx + _chw / 2) {
target = i - 1;
break;
} else if(_m < _xx + _chw) {
} else if(_mm < _xx + _chw) {
target = i;
break;
}
_xx += _chw;
}
}
if(target != -999) {
if(mouse_press(mb_left, active) || click_block == 1) {
if(mouse_press(mb_left, active) && !click_block) {
cursor_select = target;
cursor = target;
click_block = 0;
} else if(mouse_click(mb_left, active) && cursor != target)
cursor = target;
if(mouse_press(mb_left, active))
click_block = false;
}
} #endregion
@ -393,7 +395,7 @@ function textBox(_input, _onModify, _extras = noone) : textInput(_input, _onModi
var hoverRect = point_in_rectangle(_m[0], _m[1], _x, _y, _x + _w, _y + _h);
if(self == WIDGET_CURRENT) {
if(selecting) {
if(sprite_index == -1)
draw_sprite_stretched_ext(THEME.textbox, 2, _x, _y, _w, _h, COLORS._main_accent, 1);
else
@ -468,16 +470,18 @@ function textBox(_input, _onModify, _extras = noone) : textInput(_input, _onModi
var _mx = -1;
var _my = -1;
if(mouse_press(mb_any, active) && hover && hoverRect) {
if(hover && hoverRect) {
_mx = _m[0];
_my = _m[1];
}
surface_set_target(text_surface);
DRAW_CLEAR
display_text(tx - tb_surf_x, _h / 2 - th / 2, txt, _w - ui(4), _mx);
surface_reset_target();
surface_set_shader(text_surface, noone, true, BLEND.add);
display_text(tx - tb_surf_x, _h / 2 - th / 2, txt, _w - ui(4), _mx - tb_surf_x);
surface_reset_shader();
BLEND_ALPHA
draw_surface(text_surface, tb_surf_x, tb_surf_y);
BLEND_NORMAL
draw_set_color(COLORS._main_text_accent);
draw_line_width(cursor_pos, c_y0, cursor_pos, c_y1, 2);
@ -485,7 +489,7 @@ function textBox(_input, _onModify, _extras = noone) : textInput(_input, _onModi
disp_x_to = clamp(disp_x_to, disp_x_min, disp_x_max);
if(!point_in_rectangle(_m[0], _m[1], _x, _y, _x + _w, _y + _h) && mouse_press(mb_left))
if(!hoverRect && mouse_press(mb_left))
deactivate();
} else {
draw_set_text(font == noone? f_p0 : font, fa_left, fa_center);
@ -534,14 +538,13 @@ function textBox(_input, _onModify, _extras = noone) : textInput(_input, _onModi
}
}
surface_set_target(text_surface);
DRAW_CLEAR
//draw_set_color(c_black);
//draw_line(0, _h / 2 - th / 2, 9999, _h / 2 - th / 2);
surface_set_shader(text_surface, noone, true, BLEND.add);
display_text(tx - tb_surf_x, _h / 2 - th / 2, _display_text, _w - ui(4));
surface_reset_target();
surface_reset_shader();
BLEND_ALPHA
draw_surface(text_surface, tb_surf_x, tb_surf_y);
BLEND_NORMAL
}
if(DRAGGING && (DRAGGING.type == "Text" || DRAGGING.type == "Number") && hover && hoverRect) {
@ -550,6 +553,7 @@ function textBox(_input, _onModify, _extras = noone) : textInput(_input, _onModi
onModify(DRAGGING.data);
}
selecting = self == WIDGET_CURRENT;
resetFocus();
sprite_index = -1;
return _h;

View file

@ -1,7 +1,8 @@
function textInput(_input, _onModify, _extras = noone) : widget() constructor {
function textInput(_input, _onModify) : widget() constructor {
input = _input;
onModify = _onModify;
side_button = noone;
selecting = false;
static _resetFocus = function() { resetFocus(); }