#include "../include/xcv_license.h" //PCH namespace Widgets95 {//-. //<-' extern int xcv_spinner_cogwheel = 0; //2019 /*********************************** WIDGETS_95_Spinner:_reset_growth() *************/ double &spin_interface::_growth(int inc) { //CAUTION: This code just preserves the old code's behavior. //My sense is it can use a major review. double &g = e._spin_growth; if(!inc) return g; double lim = fabs(_val_end-_val_start)/ui_spin_min_growth_steps; if(inc<=-1000) //_reset_growth? { //2019: I'm combining these so spin_interface::_activate //can call _reset_growth. (Actually that wasn't possible //but I've still combined the functionality.) if(ui::textbox *et=textbox) //spinbox like? if(1>=et->line_limit()&&_data_type==et->_data_type) { if(!et->_has_limits) { if(_data_type==spin_float) { g = sqrt(fabs(et->_float_val))*0.05; } else if(_data_type==spin_int) { g = 0.4; } else goto zero; //2019 } else if(_data_type==spin_float||_data_type==spin_int) { g = lim; } else zero: //2019 { g = 0; assert(_data_type==spin_float||!_data_type); } goto g; //if(!g) g = 0.001; } //bar::_reset_growth() //scrollbar like? { //FIXED? BASICALLY THIS SEEMS TO DISABLE increase_growth. //g = fabs(_val_end-_val_start)/double(ui_spin_growth_steps); g = lim; if(_data_type==spin_int&&g<1) g = 1; } } else //_increase_growth? { /*REFERENCE //if(growth<(_val_end-_val_start/ui_spin_min_growth_steps)) if(growth0&&g>=lim) goto g; //HACK: This is how the old code worked. while(inc-->0) g*=exp; } g: if(g<0.001) g = 0.001; //if(g>lim) g = lim; //This is not how the old code actually worked. return g; } /****************************** WIDGETS_95_Spinner::mouse_down_handler() **********/ bool ui::spinner::_mouse_down_handler(int local_x, int local_y) { _state = find_arrow(local_x,local_y); if(!_state) return true; //2019 if(needs_idle()) e::xcv_setIdleFuncIfNecessary(true); /* printf( "spinbox: mouse down : %d/%d arrow:%d\n",local_x,local_y,find_arrow(local_x,local_y)); */ if(_state!=state_up&&_state!=state_down) { assert(0); //SCROLL? return false; //return true; //??? } _reset_growth(); //NOTE: I'm going to try to add this logic to do_click. //2019: Either do_click should set the value or it shouldn't be also //taken. The demos have no problem incrementing... in fact they have //the opposite problem. /*** ints and floats behave a bit differently. When you click on an int spinbox, you expect the value to immediately go up by 1, whereas for a float it'll go up only by a fractional amount. Therefore, we go ahead and increment by one for int spinners ***/ //if(_data_type==spin_int) //if(double step=_state==state_up?1:state_down?-0.9:0) //0.9??? //{ // set_val(_float_val+step); //} xcv_spinner_cogwheel = 0; do_click(); //2019: This is new... it should probably be GLUT_ELAPSED_TIME based. xcv_spinner_cogwheel = 45; return false; } /******************************** WIDGETS_95_Spinner::mouse_up_handler() **********/ bool ui::spinner::_mouse_up_handler(int local_x, int local_y, bool inside) { if(textbox&&!e.wheel_event) //2019 { textbox->activate(ACTIVATE_MOUSE); } _state = state_none; //2019: The idle function should now do this automatically. //e::xcv_setIdleFuncIfNecessary(); /* printf("spinbox: mouse up : %d/%d inside: %d\n",local_x,local_y,inside); */ //This is done automatically. //glutSetCursor(GLUT_CURSOR_LEFT_ARROW); redraw(); //Make of this what you will??? /* do_callbacks(); --- stub */ /* if(callback) callback(user_id); */ return false; } /***************************** WIDGETS_95_Spinner::mouse_held_down_handler() ******/ static int xcv_spinner_last_y = 0; bool ui::spinner::_mouse_held_down_handler(int local_x, int local_y, bool inside) { if(_state==state_none) return false; /* printf("spinbox: mouse held: %d/%d inside: %d\n",local_x,local_y,inside); */ if(_state==state_both) /* dragging? */ { do_drag(local_x,local_y); } else /* not dragging */ { int new_state = find_arrow(local_x,local_y); if(new_state==_state) { /** Still in same arrow **/ do_click(); } else { //if(inside||1) //2019: ??? { /** The _state changed, but we're still inside - that means we moved off the arrow: begin dragging **/ _state = state_both; } //else //??? { /*** Here check y of mouse position to determine whether to drag ***/ /* ... */ } } /*** We switched to up/down dragging ***/ if(_state==state_both) { glutSetCursor(GLUT_CURSOR_UP_DOWN); //_last_x = local_x; xcv_spinner_last_y = local_y; /** If the spinbox has limits, we reset the _growth value, since _reset_growth() will compute a new _growth value for dragging vs. clicking. If the spinbox has no limits, then we just let the _growth remain at whatever the user has incremented it up to **/ if(ti::limit_none!=textbox->_has_limits) { _reset_growth(); } } } return false; } /****************************** WIDGETS_95_Spinner::draw() **********/ void ui::spinner::_draw() { if(!_data_type) return; //Hidden? int x1 = ui_spinner_arrow_top; int x2 = _w-x1; int y1 = x1; int y2 = y1+ui_spinner_arrow_drop; int i,j; if(!_enabled) { i = bitmap::spinner_up_dis; j = bitmap::spinner_down_dis; } else { i = _state==state_up||_state==state_both? bitmap::spinner_up_on:bitmap::spinner_up_off; j = _state==state_down||_state==state_both? bitmap::spinner_down_on:bitmap::spinner_down_off; } xcv_bitmaps[i]->draw(x1,y1); xcv_bitmaps[j]->draw(x1,y2); /*2019: spin_interface::_activate is preventing tab //navigation. Page, Arrow, Home, End should be used. //if(active) if(this==e::get_active()) window::draw_active_box(w-ui_spinner_arrow_span-2,w,0,h); */ //Shadow effect for bitmaps? /*Note: The difference is neglible for buddy spinners. //(And that currently all spinners are buddy spinners.) if(i==bitmap::spinner_up_off||i==bitmap::spinner_up_dis) { window::draw_color(175); window::draw_border_rect(x1,y1+1,x2+1,y2+2,4); } if(j==bitmap::spinner_down_off||j==bitmap::spinner_down_dis) { y1+=ui_spinner_arrow_drop; y2+=ui_spinner_arrow_drop; window::draw_color(175); window::draw_border_rect(x1,y1+1,x2+1,y2+1,4|8); }*/ } /********************************* WIDGETS_95_Spinner::special_handler() **********/ bool spin_interface::_special_handler(int key, int modifiers) { //NOTE: It's currently not really possible to do key input //into a spinbox becaue the focus goes to the text control. //NOTE: The Home/End/Page stuff was added for xcv_html.cpp. if(key>=GLUT_KEY_LEFT&&key<=GLUT_KEY_DOWN) { int x,y; if(_horizontal!=(key==GLUT_KEY_UP||key==GLUT_KEY_RIGHT)) { x = y = 3; goto xy; } else if(_horizontal!=(key==GLUT_KEY_DOWN||key==GLUT_KEY_LEFT)) { x = _horizontal?_w-3:3; y = !_horizontal?_h-3:3; xy: x+=_x_abs; y+=_y_abs; x+=_x_lr; y+=_y_off_top; //scrollbar? xcv_spinner_cogwheel = 0; //NEW if(e::Click(this,x,y)) //OVERKILL? return true; } } else if(key==GLUT_KEY_HOME||key==GLUT_KEY_END) //NEW { bool inv = key==GLUT_KEY_HOME; if(_val_start>_val_end) inv = !inv; set_val(inv?_val_start:_val_end); do_callbacks(); } else if(key==GLUT_KEY_PAGE_UP||key==GLUT_KEY_PAGE_DOWN) //NEW { int pg = 1; if(_val_start>_val_end) pg = -pg; if(key==GLUT_KEY_PAGE_DOWN) pg = -pg; if(_tick_cb) //2022: Copying do_click with false { double new_val = _float_val; double t = new_val; int i = _tick_cb(this,-1,new_val,!true); //false if(i!=-1) { //NEW: This condition is designed to go to the next //valid tick when the current value isn't on a tick. if(pg>=0?new_val>=t:new_val<=t) _tick_cb(this,i-pg,new_val,!true); //false set_val(new_val); if(t!=_float_val) do_callbacks(); return false; } } if(!associated_object) return false; //true? if(ti) return ti->special_handler(key,modifiers); //HACK if(_object_cb) //Scrollbar? HTML? { int lh = associated_object->_font.height; int ah = associated_object->active_area<3>(); int lines = ah/lh; if(lines>2) { set_int_val(pg*lines+_int_val); do_callbacks(); } } } else return true; return false; } /************************************ WIDGETS_95_Spinner::update_area() **********/ void ui::spinner::_update_live() { if(ui::ti*tb=textbox) switch(_data_type) //ui::spinner? { default: //NEW: plain text mode if(_text!=tb->_text) tb->set_text(_text); break; case spin_int: if(_int_val!=tb->_int_val||tb->_text.empty()) tb->set_int_val(_int_val); break; case spin_float: if(_float_val!=tb->_float_val||tb->_text.empty()) tb->set_float_val(_float_val); break; } } /************************************ WIDGETS_95_Spinner::find_arrow() ************/ int ui::spinner::find_arrow(int local_x, int local_y) { local_x-=_x_abs; local_y-=_y_abs; if(_horizontal) { assert(0); //ui::spinner? } else if(local_x>=0&&local_x<=_w) { if(local_y>=0&&local_y<=ui_spinner_arrow_top+ui_spinner_arrow_drop) { return state_up; } if(local_y>=ui_spinner_arrow_top+ui_spinner_arrow_drop&&local_y<=_h) { return state_down; } } return state_none; } /***************************************** WIDGETS_95_Spinner::do_click() **********/ void ui::spinner::do_click() { //NEW: In case GLUT fails to send mouse-up event. if(!e.curr_button_down) { _state = state_none; redraw(); return; } int direction = _state==state_up?1:_state==state_down?-1:0; if(!direction) return; if(xcv_spinner_cogwheel--) return; xcv_spinner_cogwheel = 10; double cmp = _float_val, new_val = cmp; //limits if(_tick_cb) { double t = new_val; int i = _tick_cb(this,-1,new_val,true); if(i==-1) goto _1; //NEW: This condition is designed to go to the next //valid tick when the current value isn't on a tick. if(direction<0?new_val>=t:new_val<=t) _tick_cb(this,i+direction,new_val,true); } else _1: { double cm_factor = 1; /*I want to be able to use Shift with my MM3D software //to drive 3D scale uniformly since it uses Shift also //to move the views simultaneously. switch(e.curr_modifiers) { case GLUT_ACTIVE_SHIFT: cm_factor = 10; break; //100 //TOO MUCH case GLUT_ACTIVE_CTRL: cm_factor = 0.1; break; //0.01 //TOO LITTLE }*/ if(e.curr_modifiers&GLUT_ACTIVE_CTRL) { //REMINDER: ui::_special is blocking GLUT_ACTIVE_ALT //for hotkeys, but this is mouse input only? cm_factor = e.curr_modifiers&GLUT_ACTIVE_ALT?10:0.1; } //NEW: Use direction only for unbounded (0~INT_MAX) value. double incr = direction; if(_speed) { incr*=_increase_growth()*cm_factor*_speed; } new_val = _float_val+incr; if(_data_type==spin_int) if(_int_val==(int)new_val) //NEW { if(incr) new_val = _int_val+(incr>0?1:-1); } } /** Remember, textbox mirrors the float and int values ***/ set_val(new_val); if(cmp!=_float_val) do_callbacks(); } /***************************************** WIDGETS_95_Spinner::do_drag() **********/ void ui::spinner::do_drag(int x, int y) { /* delta_x = x-_last_x; */ int delta_y = -(y-xcv_spinner_last_y); double cmp = _float_val, new_val = cmp; //limits if(_tick_cb) { //HACK: Decrease sensitivity? if(abs(delta_y)<4) return; int i = _tick_cb(this,-1,new_val,true); if(i==-1) goto _1; int direction = delta_y<0?-1:1; _tick_cb(this,i+direction,new_val,true); } else _1: { double cm_factor = 1; switch(e.curr_modifiers) { case GLUT_ACTIVE_SHIFT: cm_factor = 100; break; case GLUT_ACTIVE_CTRL: cm_factor = 0.01; break; } //if(_data_type==spin_float||1) //??? { /** Remember, textbox mirrors the float and int values ***/ //NEW: Use direction only for unbounded (0~INT_MAX) value. double incr; if(_speed) { incr = _growth()*delta_y*cm_factor*_speed; } else incr = delta_y; new_val+=incr; } } set_val(new_val); //_last_x = x; xcv_spinner_last_y = y; //_last_y = y; if(cmp!=_float_val) do_callbacks(); } /************************************ WIDGETS_95_Spinner::do_callbacks() **********/ void spin_interface::do_callbacks() { /*** This is not necessary, b/c textbox automatically updates us ***/ //if(!textbox) return; //_float_val = textbox->_float_val; int_val = textbox->int_val; /********************************************/ //Merging scrollbar logic //Note: Historically spinbox does not set object_cb //2019 addition //I'm changing this so both callbacks are processed //in order to be consistent with listbox. I can't make //an argument for why to disable the user callbacks. //if(0) { //execute_callback(); //old way for spinbox if(associated_object &&_id==associated_object->_id &&this==associated_object->spinner) { assert(ti); //NEW: Masquerade as spinbox/textbox so //that procedures don't have to care if //changes originate in one or the other. associated_object->execute_callback(); } else execute_callback(); } //else //scrollbar style logic { //Have to reverse the sense to make it work //if(!associated_object) if(!_object_cb) { // execute_callback(); } else // Use internal Callbacks { //if(_object_cb) { //object_cb(associated_object,int_val); _object_cb(this); } } } } //---. }//<-'