Tadaa!
So, what's so special about it? There's lots of explanation in the tsmartspinner file, but in short:
<> can look like anything, by using your own hooked *external* functions, you never need to touch the smartspinner core
<> horizontal or vertical dragging with polarity choice
<> supports cursors, home, end, page up/dn, mousewheel
<> each spinner can record a value into one of the 10 slots, and one can easily recall such values with a single keypress
<> has one built-in simple displayer, showing the actual value, and with shadow & color options
<> right-mouse-button generates a gadgetaction event
So what can you do with it?
Obviously it's a spinner, so whenever you want a box with a number you can change: smartspinner is your solution.
It has tactful events, telling you when spinning starts, happens and stops. This is convenient when some renderer is constantly rendering according to values defined in these spinners. When changing these values the start and stop events could tell the renderer to temporarily stop rendering and continue when the new value is set.
The real power lies in the external drawing. Rather than bloating the original type with zillions of styles, it now sends out an update event while a canvas is opened for drawing. As long as you make a function that listens to that event, this function can be your new spinnerstyle. Two examples are given, one as a hooked function, and one as a hooked method in a type.
Examples of new drawing functions:
- display the (int) value as a float, by dividing it by 10000 orso
- have a strip of images in a type of a rotating knob, and draw this knob
- have a lookup table with strings in a type and draw these strings (0: "yes", 1: "no", 2: "cancel"), with the rightclick you can click this spinner and so this spinner more or less acts as a normal button of which the content can change.
- make a displayer using your own font
Using it requires barely any brains. If there are no spinners included then it still works, even if you've set a smartspinner to refer to one. Another advantage of using events, so to say..
I created wrapper functions to define things, whereas some would've preferred methods. I purely chose this way to make it look uniform with Blitz' own gadget functions. It isn't much work to make 'em methods nonetheless, allthough I don't think you really gain something with it. (and I don't feel like doing it :P)
So, have a go with it, if there're no obvious flaws, I'll dump it in the archives.
(oh, minor detail: I've tested it only on my 98 system in Bmax1.22 :P)
tsmartspinner_example.bmx
tsmartspinner_spinners.bmx
tsmartspinner.bmx
So, what's so special about it? There's lots of explanation in the tsmartspinner file, but in short:
<> can look like anything, by using your own hooked *external* functions, you never need to touch the smartspinner core
<> horizontal or vertical dragging with polarity choice
<> supports cursors, home, end, page up/dn, mousewheel
<> each spinner can record a value into one of the 10 slots, and one can easily recall such values with a single keypress
<> has one built-in simple displayer, showing the actual value, and with shadow & color options
<> right-mouse-button generates a gadgetaction event
So what can you do with it?
Obviously it's a spinner, so whenever you want a box with a number you can change: smartspinner is your solution.
It has tactful events, telling you when spinning starts, happens and stops. This is convenient when some renderer is constantly rendering according to values defined in these spinners. When changing these values the start and stop events could tell the renderer to temporarily stop rendering and continue when the new value is set.
The real power lies in the external drawing. Rather than bloating the original type with zillions of styles, it now sends out an update event while a canvas is opened for drawing. As long as you make a function that listens to that event, this function can be your new spinnerstyle. Two examples are given, one as a hooked function, and one as a hooked method in a type.
Examples of new drawing functions:
- display the (int) value as a float, by dividing it by 10000 orso
- have a strip of images in a type of a rotating knob, and draw this knob
- have a lookup table with strings in a type and draw these strings (0: "yes", 1: "no", 2: "cancel"), with the rightclick you can click this spinner and so this spinner more or less acts as a normal button of which the content can change.
- make a displayer using your own font
Using it requires barely any brains. If there are no spinners included then it still works, even if you've set a smartspinner to refer to one. Another advantage of using events, so to say..
I created wrapper functions to define things, whereas some would've preferred methods. I purely chose this way to make it look uniform with Blitz' own gadget functions. It isn't much work to make 'em methods nonetheless, allthough I don't think you really gain something with it. (and I don't feel like doing it :P)
So, have a go with it, if there're no obvious flaws, I'll dump it in the archives.
(oh, minor detail: I've tested it only on my 98 system in Bmax1.22 :P)
tsmartspinner_example.bmx
SuperStrict Include "tsmartspinner_spinners.bmx" Local window:tgadget=CreateWindow("",300,200,640,480) Local MySpinner1:TSmartspinner=CreateSmartspinner(4,32,64,16,window,1) SetSmartspinnerExtern MySpinner1,0 Local MySpinner1b:TSmartspinner=CreateSmartspinner(72,32,64,16,window,1) SetSmartspinnerExtern MySpinner1,0 SetSmartspinnerStyle MySpinner1b,0,0 Local MySpinner2:TSmartspinner=CreateSmartspinner(4,64,64,24,window,1) SetSmartspinnerExtern MySpinner2,2 SetSmartspinnerStyle MySpinner2,1,1 Local MySpinner3:TSmartspinner=CreateSmartspinner(4,96,32,32,window,1) SetSmartspinnerExtern MySpinner3,1 Local spin2:TSpinner2=New TSpinner2 Repeat WaitEvent() If EventID()=EVENT_WINDOWCLOSE End SetGadgetText window,"Smartspinner - CS_TBL (GC: "+GCMemAlloced()+")" If EventID()=EVENT_GADGETACTION Print"rightclicked "+EventData() If EventID()=EVENT_SMARTSPINNERSTART Print"started changing "+EventData() If EventID()=EVENT_SMARTSPINNERCHANGING Print"....changing.... "+EventData() If EventID()=EVENT_SMARTSPINNERCHANGED Print"stopped changing "+EventData() Forever
tsmartspinner_spinners.bmx
Include "tsmartspinner.bmx" AddHook EmitEventHook,Spinner1 Function Spinner1:Object(id:Int,data:Object,context:Object) Local ev:TEvent=TEvent(data);If ev.id<>EVENT_SMARTSPINNERUPDATE Return data Local p:TSmartspinner=TSmartspinner(ev.extra);If p.style<>1 Return data ' each style is to have a unique number ' in this case it's: '1' p.ext=1 ' 0: the internal number display is shown when this function is done ' this can be used to draw an image using a function like this, to serve as ' backdrop for the internal number display (you might then want to disable the shadow option though) ' 1: the internal number display is not shown when this function is done ' --------------------------------------------------------------------------------------------------------- ' Everything below is up to you! with p.<field> you can access the fields of the smartspinner ' (stick to the relevant fields, as shown at the top of the Type!) ' --------------------------------------------------------------------------------------------------------- SetClsColor 128,128,128;Cls Local x:Int=p.canvaswidth/2 Local y:Int=p.canvasheight/2 SetColor 255,255,255 DrawLine x,y,x+Sin(p.currentvalue*3)*15,y+Cos(p.currentvalue*3)*15 DrawOval x-2,y-2,5,5 EndFunction Type TSpinner2 Function eventhook:Object(id:Int,data:Object,context:Object) If TSpinner2(context) TSpinner2(context).ev TEvent(data);Return data EndFunction Method New() AddHook EmitEventHook,eventhook,Self End Method Method Free() RemoveHook EmitEventHook,eventhook GCCollect() End Method Method ev(event:TEvent) If event.id=EVENT_SMARTSPINNERUPDATE Local p:TSmartspinner=TSmartspinner(event.extra) If p.style<>2 Return ' spinnerstyle: '2' p.ext=0 ' internal displayer will be shown after this function ' ------------------------------------------------------------------------------------------------- ' Everything below is up to you! with p.<field> you can access the fields of the smartspinner ' (stick to the relevant fields, as shown at the top of the Type!) ' ------------------------------------------------------------------------------------------------- SetClsColor 128,64,0;Cls SetColor 128,192,255 DrawRect p.currentvalue,0,4,4 EndIf End Method End Type
tsmartspinner.bmx
Const EVENT_SMARTSPINNERUPDATE:Int=13370330 Const EVENT_SMARTSPINNERSTART:Int=13370331 Const EVENT_SMARTSPINNERCHANGING:Int=13370332 Const EVENT_SMARTSPINNERCHANGED:Int=13370333 Type TSmartspinner Rem TSmartspinner v1.0 - by CS_TBL, 1st and 2nd Easter day '07 +--------+ | Usage: | +--------+ Local MySpinner:TSmartspinner=CreateSmartspinner(x,y,width,height,parent,canvasstyle) +------------------+ | Relevant events: | +------------------+ EVENT_SMARTSPINNERUPDATE > Used in external hooked functions, .extra contains the smartspinner object EVENT_SMARTSPINNERSTART > emited when the user starts spinning, .source contains the smartspinner, .data contains the currentvalue EVENT_SMARTSPINNERCHANGING > emited when the spinner is spinning, .source contains the smartspinner, .data contains the currentvalue EVENT_SMARTSPINNERCHANGED > emited when the user stops spinning, .source contains the smartspinner, .data contains the currentvalue EVENT_GADGETACTION > emited when the user rightclicks a spinner, .source contains the smartspinner, .data contains the currentvalue +----------------------+ | (wrapped-)Functions: | +----------------------+ (wrapper-)functions: SetSmartspinnerValues smartspinner,value,lowlimit,highlimit,homevalue,endvalue > Sets the current value, the low and high limits, and the values for the [home] and [end] keys SetSmartspinnerColors smartspinner,txtR,txtG,txtB,bckR,bckG,bckB > Sets the rgb values for text and background for the default/internal displaystyle SetSmartspinnerStyle smartspinner,direction,polarity > Sets the dragging direction (0 or 1), and the polarity (0 or 1) SetSmartspinnerShadow smartspinner,state > Sets shadow on (1) or off (0) for the default/internal displaystyle SetSmartspinnerExtern smartspinner,style > Sets the spinnerstyle, this number corresponds with a style check in a hooked function or hooked type-method This allows another function to take over the drawing process and thus create a wide range of different visual styles. Put even something simple as representing the internal int value as a float by manually dividing by 10000 in such a function orso. This outsourcing without ever touching the SmartSpinner core is what makes it so flexible and easy to use! SetSmartspinnerDragrange smartspinner,amount > Sets the dragrange, this is a divider (so can't be 0) intending to allow larger drag range to change the value Essential to avoid having to use a microscope to change a value by one pixel, often a pain in the A$$ in music- software.. :S EnableSmartspinner smartspinner > Enables the smartspinner gadget DisableSmartspinner smartspinner > Disables the smartspinner gadget ShowSmartspinner smartspinner > Shows the smartspinner gadget HideSmartspinner smartspinner > Hides the smartspinner gadget Function RelocateSmartspinner smartspinner,x,y > Relocates the smartspinner gadget +------------+ | Operating: | +------------+ leftmouse dragging, horizontally or vertically rightmouse clicking, emits a gadgetaction event mousewheel cursors page up/dn home, end 0..9 (choose value from a memory slot) SHIFT + 0..9 (record value into a memory slot) Mouse and keys that change the value are influenced by the polarity +-----------------------------------------------+ | Possible improvements, prolly not done by me: | +-----------------------------------------------+ - Alternative way to visualise a disabled smartspinner (had some issues with blend/shade modes and drawtext o_O) - Resize the canvas/spinner - way more common spinnerstyles - Extra wrapper functions to change cursor/page/wheel amounts EndRem ' --------------------------------------------------------------------------------------------------------- ' relevant values for external drawing Field value:Int Field minvalue:Int=-999 Field maxvalue:Int=999 Field homevalue:Int=0 Field endvalue:Int=999 Field pagevalue:Int=20 ' value to add using PageUp/Dn Field cursorvalue:Int=1 ' value to add using the cursors Field wheelvalue:Int=1 ' value to add using the mousewheel Field dragrange:Int=6 ' a divider, must not be 0 for obvious reasons, allows to work in a less microscopic way Field dir:Int=0 ' 0: up-down, 1: left-right Field polarity:Int=1 ' 1, -1 (note: 0 and 1 are used as argument in the wrapper function) Field drawshadow:Int=0 ' draws a shadow in the default/internal spinnerstyle Field style:Int=0 ' 0=internal, the rest is custom Field txtR:Int=192 Field txtG:Int=192 Field txtB:Int=255 Field bckR:Int=96 Field bckG:Int=96 Field bckB:Int=160 Field currentvalue:Int Field canvaswidth:Int Field canvasheight:Int ' internal stuff Field parent:tgadget Field dragvalue:Int Field startdragvalue:Int Field canvas:tgadget Field lmd:Int Field ext:Int Field SHIFT:Int Field rec:Int[10],record:Int Field active:Int=1 Function eventhook:Object(id:Int,data:Object,context:Object) If TSmartspinner(context) TSmartspinner(context).ev TEvent(data);Return data EndFunction Method New() AddHook EmitEventHook,eventhook,Self End Method Method Free() RemoveHook EmitEventHook,eventhook GCCollect() End Method Method ev(event:TEvent) If event.source=canvas If event.id=EVENT_GADGETPAINT update If event.id=EVENT_MOUSEDOWN If event.data=1 ' left mousebutton lmd=1 startdragvalue=delta(event) dragvalue=0 update Send EVENT_SMARTSPINNERSTART EndIf If event.data=2 ' right mousebutton Send EVENT_GADGETACTION EndIf EndIf If event.id=EVENT_MOUSEENTER ActivateGadget canvas EndIf If event.id=EVENT_MOUSELEAVE record=0 update ActivateGadget parent EndIf If event.id=EVENT_MOUSEUP If event.data=1 lmd=0 value:+delta(event)-startdragvalue value=Limit(value,minvalue,maxvalue) update Send EVENT_SMARTSPINNERCHANGED EndIf EndIf If event.id=EVENT_MOUSEMOVE If lmd dragvalue=delta(event)-startdragvalue value=Limit(value,minvalue,maxvalue) update Send EVENT_SMARTSPINNERCHANGING EndIf EndIf If event.id=EVENT_MOUSEWHEEL value:-Sgn(event.data)*wheelvalue*polarity value=Limit(value,minvalue,maxvalue) update Send EVENT_SMARTSPINNERCHANGED EndIf If event.id=EVENT_KEYDOWN Or event.id=EVENT_KEYREPEAT Select event.data Case 33 ' pgup value:-pagevalue*polarity Case 34 ' pgup value:+pagevalue*polarity Case 35 ' end value=endvalue Case 36 ' home value=homevalue Case 37 ' left value:-cursorvalue*polarity Case 39 ' right value:+cursorvalue*polarity Case 38 ' up value:-cursorvalue*polarity Case 40 ' down value:+cursorvalue*polarity Case 160,161 SHIFT=1 EndSelect If event.data>=48 And event.data<=57 ' 0..9 If SHIFT record=1 rec[event.data-48]=value EndIf value=rec[event.data-48] EndIf value=Limit(value,minvalue,maxvalue) update Send EVENT_SMARTSPINNERCHANGING EndIf If event.id=EVENT_KEYUP Select event.data Case 160,161 SHIFT=0 record=0 EndSelect update Send EVENT_SMARTSPINNERCHANGED EndIf EndIf End Method Method Send(what:Int) MakeCurrentvalue Local newevent:TEvent=New TEvent newevent.id=what newevent.source=Self newevent.data=currentvalue EmitEvent newevent newevent=Null GCCollect End Method Method MakeCurrentvalue() currentvalue=value If lmd currentvalue:+dragvalue currentvalue=Limit(currentvalue,minvalue,maxvalue) End Method Method Limit:Int(v:Int,l:Int,h:Int) If v<l Return l If v>h Return h Return v End Method Method delta:Int(e:TEvent) Select dir Case 0 Return (e.y*polarity)/dragrange Case 1 Return (e.x*polarity)/dragrange End Select End Method Method update() ext=0 SetGraphics CanvasGraphics(canvas) SetClsColor bckR,bckG,bckB;Cls MakeCurrentvalue Local newevent:TEvent=New TEvent newevent.id=EVENT_SMARTSPINNERUPDATE newevent.extra=Self EmitEvent newevent newevent=Null GCCollect If Not ext If drawshadow SetColor bckR/3,bckG/3,bckB/3 DrawRect 0,0,canvaswidth,1 DrawRect 0,1,1,canvasheight-1 SetColor bckR/1.5,bckG/1.5,bckB/1.5 DrawRect 1,1,canvaswidth-1,1 DrawRect 1,2,1,canvasheight-2 EndIf SetColor txtR,txtG,txtB If lmd SetColor txtR*1.5,txtG*1.5,txtB*1.5 DrawText currentvalue,canvaswidth/2-TextWidth(currentvalue)/2,canvasheight/2-TextHeight(currentvalue)/2 EndIf If record SetColor 64,0,0; DrawOval 2,2,7,7 SetColor 255,0,0; DrawOval 3,3,5,5 EndIf If Not active SetColor 160,160,160 For Local t:Int=0 To canvasheight-1 Step 2 DrawRect 0,t,canvaswidth,1 Next EndIf Flip End Method End Type Function CreateSmartspinner:TSmartspinner(x:Int,y:Int,w:Int,h:Int,parent:tgadget,canvasstyle:Int=0) If parent<>Null Local a:TSmartspinner=New TSmartspinner a.canvas=CreateCanvas(x,y,w,h,parent,canvasstyle) a.canvaswidth=ClientWidth(a.canvas) a.canvasheight=ClientHeight(a.canvas) a.parent=parent Return a Else RuntimeError "Error: parent not found! CreateSmartspinner("+x+", "+y+", "+w+", "+h+", ???, "+canvasstyle+")" EndIf End Function Function SetSmartspinnerValues(smartspinner:TSmartspinner,v:Int,low:Int,high:Int,homevalue:Int,endvalue:Int) If smartspinner<>Null smartspinner.value=v smartspinner.minvalue=low smartspinner.maxvalue=high smartspinner.homevalue=homevalue smartspinner.endvalue=endvalue smartspinner.update Else RuntimeError "Error: smartspinner not found! SetSmartspinnerValues(???, "+v+", "+low+", "+high+", "+homevalue+", "+endvalue+")" EndIf End Function Function SetSmartspinnerColors(smartspinner:TSmartspinner,txtR:Int, txtG:Int, txtB:Int, bckR:Int, bckG:Int, bckB:Int) If smartspinner<>Null smartspinner.bckR=bckR smartspinner.bckG=bckG smartspinner.bckB=bckB smartspinner.txtR=txtR smartspinner.txtG=txtG smartspinner.txtB=txtB smartspinner.update Else RuntimeError "Error: smartspinner not found! SetSmartspinnerColors(???, "+txtR+", "+txtG+", "+txtB+", "+bckR+", "+bckG+", "+bckB+")" EndIf End Function Function SetSmartspinnerStyle(smartspinner:TSmartspinner,direction:Int, polarity:Int) If smartspinner<>Null polarity:&1 smartspinner.dir=direction smartspinner.polarity=-1+polarity*2 Else RuntimeError "Error: smartspinner not found! SetSmartspinnerStyle(???, "+direction+", "+polarity+")" EndIf End Function Function SetSmartspinnerShadow(smartspinner:TSmartspinner,state:Int) If smartspinner<>Null smartspinner.drawshadow=state Else RuntimeError "Error: smartspinner not found! SetSmartspinnerShadow(???, "+state+")" EndIf End Function Function SetSmartspinnerExtern(smartspinner:TSmartspinner,style:Int) If smartspinner<>Null smartspinner.style=style Else RuntimeError "Error: smartspinner not found! SetSmartspinnerExtern(???, "+style+")" EndIf End Function Function SetSmartspinnerDragrange(smartspinner:TSmartspinner,amount:Int) If smartspinner<>Null If amount<1 RuntimeError "Error: dragrange <1 SetSmartspinnerDragrange(smartspinner, "+amount+")" Else smartspinner.dragrange=amount EndIf Else RuntimeError "Error: smartspinner not found! SetSmartspinnerDragrange(???, "+amount+")" EndIf End Function Function EnableSmartspinner(smartspinner:TSmartspinner) If smartspinner<>Null smartspinner.active=1 smartspinner.update EnableGadget smartspinner.canvas Else RuntimeError "Error: smartspinner not found! EnableSmartspinner(???)" EndIf End Function Function DisableSmartspinner(smartspinner:TSmartspinner) If smartspinner<>Null smartspinner.active=0 smartspinner.update DisableGadget smartspinner.canvas Else RuntimeError "Error: smartspinner not found! DisableSetSmartspinner(???)" EndIf End Function Function ShowSmartspinner(smartspinner:TSmartspinner) If smartspinner<>Null ShowGadget smartspinner.canvas Else RuntimeError "Error: smartspinner not found! ShowSmartspinner(???)" EndIf End Function Function HideSmartspinner(smartspinner:TSmartspinner) If smartspinner<>Null HideGadget smartspinner.canvas Else RuntimeError "Error: smartspinner not found! HideSmartspinner(???)" EndIf End Function Function RelocateSmartspinner(smartspinner:TSmartspinner,x:Int,y:Int) If smartspinner<>Null SetGadgetShape smartspinner.canvas,x,y,GadgetWidth(smartspinner.canvas),GadgetHeight(smartspinner.canvas) Else RuntimeError "Error: smartspinner not found! HideSmartspinner(???)" EndIf End Function