Page 38 of 51

Re: Universal Control Remapper (UCR) - v0.1.19 26th Dec 2017

Posted: 13 Jan 2018, 07:37
by Oliver
Ah OK! I thought I had to actually use it but it's merely an example of the GUI, got it.

Re: Universal Control Remapper (UCR) - v0.1.19 26th Dec 2017

Posted: 13 Jan 2018, 08:50
by evilC
I made it a bit more production ready - you no longer need to add 0,0 at the start and 100,100 at the end.
I also added a little bit of validation / ordering logic

I packaged it all up into a UCR plugin - hopefully this should be functional and do what you need

Code: Select all

class AxisToAxisDetent extends _UCR.Classes.Plugin {
	Type := "Remapper (Axis To Axis with Detent)"
	Description := "Maps an axis input to a virtual axis output (With Detent)"
	vAxis := 0
	vDevice := 0
	; Set up the GUI to allow the user to select input and output axes
	Init(){
		Gui, Add, GroupBox, % "xm w135 h130 section Center", Input Axis
		this.AddControl("InputAxis", "IA1", 0, this.MyInputChangedState.Bind(this), "xp+5 w125 y25")
		this.AddControl("AxisPreview", "", 0, this.IOControls.IA1, "xp y+5 w125", 50)
		
		Gui, Add, GroupBox, % "x150 ym w60 h130 section Center", Invert
		this.AddControl("CheckBox", "Invert", 0, "xs+25 w30 y40")
		
		Gui, Add, GroupBox, % "x220 ym w150 h130 section Center", Detent settings
		this.SegmentManager := new this.SegmentControl(this.OnSegmentChanged.Bind(this), this, "xs+10 y25")
		
		Gui, Add, GroupBox, % "x385 ym w135 h130 section Center", Output Virtual Axis
		this.AddControl("OutputAxis", "OA1", this.MyOutputChangedValue.Bind(this), "xs+5 y25 w125")
		this.AddControl("AxisPreview", "", 0, this.IOControls.OA1, "xp y+5 w125", 50)
		
		this.AddControl("Edit", "Segments", this.OnSettingsLoad.Bind(this), "x+25 yp hidden")
	}
	
	OnSegmentChanged(value){
		GuiControl, , % this.GuiControls.Segments.hwnd, % value
	}
	
	OnSettingsLoad(value){
		if (value != ""){
			this.SegmentManager.LoadSettings(value)
		}
	}
	
	; The user changed options - store stick and axis selected for fast retreival
	MyOutputChangedValue(value){
		this.vAxis := value.Binding[1]
		this.vDevice := value.DeviceID
		this.OutputBound := value.IsBound()
	}
	
	; The user moved the selected input axis. Manipulate the output axis accordingly
	MyInputChangedState(value){
		value := UCR.Libraries.StickOps.AHKToInternal(value)
		if (this.OutputBound){
			if (this.GuiControls.Invert.Get()){
				value := UCR.Libraries.StickOps.Invert(value)
			}
			value := UCR.Libraries.StickOps.InternalToAHK(value)
			
			value := this.SegmentManager.GetValue(value)
			
			this.IOControls.OA1.Set(value)
		}
	}
	
	class SegmentControl {
		Segments := []
		Points := []
		
		__New(callback, parent, options := ""){
			this.parent := parent
			this.callback := callback
			Gui, Add, ListView, % "hwndhwnd w100 h75 section " options, Input|Output
			LV_ModifyCol(1, 45)
			LV_ModifyCol(2, 45)
			this.hLV := hwnd
			Gui, Add, Button, hwndhwnd x+5 yp w20 h75, -
			fn := this.DeleteClicked.Bind(this)
			GuiControl, +g, % hwnd, % fn
			Gui, Add, Edit, xs y+5 hwndhwnd w45
			this.hEditInput := hwnd
			Gui, Add, Edit, x+10 yp hwndhwnd w45
			this.hEditOutput := hwnd
			Gui, Add, Button, hwndhwnd x+5 yp w20, +
			fn := this.AddClicked.Bind(this)
			GuiControl, +g, % hwnd, % fn
		}
		
		; Set save to false when loading settings, to avoid triggering save on load
		_AddPoint(i, o, save := true){
			if (!this.IsNumeric(i) || !this.IsNumeric(o)){
				return false
			}
			inserted := false
			for index, obj in this.Points {
				if (obj[1] == i)
					return false
				if (obj[1] > i){
					this.Points.Insert(index, [i,o])
					inserted := true
					break
				}
			}
			if (!inserted){
				this.Points.push([i, o])
			}
			this.BuildSegments()
			if (save){
				this.SaveSettings()
			}
			return true
		}
		
		_RemovePoint(index){
			this.Points.RemoveAt(index)
			this.BuildSegments()
			this.SaveSettings()
		}
		
		LoadSettings(str){
			chunks := StrSplit(str, "|")
			for i, pt in chunks {
				p := StrSplit(pt, ",")
				res := this._AddPoint(p[1], p[2], false)
			}
		}
		
		SaveSettings(){
			str := ""
			for i, chunk in this.Points {
				if (i > 1){
					str .= "|"
				}
				str .= chunk[1] "," chunk[2]
			}
			this.callback.Call(str)
		}
		
		BuildSegments(){
			gui, % this.parent.hwnd ":default"
			Gui, ListView, % this.hLV
			LV_Delete()
			if (this.Points.Length() == 0){
				return
			}
			for i, p in this.Points {
				LV_Add(, p[1], p[2])
			}
			pts := this.Points.clone()
			;~ pts.Insert(1, [0,0])
			pts.Push([100,100])
			;~ p0 := pts.removeat(1)
			p0 := [0,0]
			this.Segments := []
			for i, p1 in pts {
				this.Segments.push( {k : (p1.2-p0.2) / (p1.1-p0.1) , m : ( p1.1*p0.2 - p0.1*p1.2 ) / (p1.1-p0.1), end : p1.1 } ), p0 := p1
			}
		}
		
		AddClicked(){
			GuiControlGet, i, , % this.hEditInput
			GuiControlGet, o, , % this.hEditOutput
			if (!this._AddPoint(i, o)){
				SoundBeep, 200, 500
			}
		}
		
		DeleteClicked(){
			Gui, ListView, % this.hLV
			row := LV_GetNext()
			this._RemovePoint(row)
		}
		
		IsNumeric(str){
			if str is number
				return true
			return false
		}
		
		GetValue(x){
			for i, seg in this.Segments
				if (x <= seg.end)
					return x*seg.k+seg.m
		}
	}
	
}

Re: Universal Control Remapper (UCR) - v0.1.19 26th Dec 2017

Posted: 13 Jan 2018, 09:33
by Oliver
Yeah basically [0,0] at the start and [100,100] at the end do not need to be user selectable as they have to be there anyway.

Gonna try it in a couple of hours! The GUI and all seems to work perfectly so far.

Re: Universal Control Remapper (UCR) - v0.1.19 26th Dec 2017

Posted: 13 Jan 2018, 12:34
by Helgef
It seems fine evilC :thumbup:. (As far as I can see using the vJoy feeder)

Re: Universal Control Remapper (UCR) - v0.1.19 26th Dec 2017

Posted: 13 Jan 2018, 13:09
by evilC
I am starting to think though that I should not hide the settings field.
You may want to dupe the settings to another plugin, so it may be an idea to leave it visible?

Re: Universal Control Remapper (UCR) - v0.1.19 26th Dec 2017

Posted: 13 Jan 2018, 13:13
by Oliver
Whatever you do, I'm happy, because it works!

Re: Universal Control Remapper (UCR) - v0.1.19 26th Dec 2017

Posted: 13 Jan 2018, 13:16
by evilC
It could also maybe do with a "Use current" button which takes the current value of the input and puts it in the Edit Box, so you do not have to work it out.

Re: Universal Control Remapper (UCR) - v0.1.19 26th Dec 2017

Posted: 13 Jan 2018, 13:57
by evilC
OK, I made the settings box visible, plus added a "Copy Current" button to place the current value of the input axis into the Input box of the GuiControl.

Image

Code: Select all

class AxisToAxisDetent extends _UCR.Classes.Plugin {
	Type := "Remapper (Axis To Axis with Detent)"
	Description := "Maps an axis input to a virtual axis output (With Detent)"
	vAxis := 0
	vDevice := 0
	; Set up the GUI to allow the user to select input and output axes
	Init(){
		Gui, Add, GroupBox, % "xm w135 h130 section Center", Input Axis
		this.AddControl("InputAxis", "IA1", 0, this.MyInputChangedState.Bind(this), "xs+5 w125 y25")
		this.AddControl("AxisPreview", "", 0, this.IOControls.IA1, "xs+5 y+5 w125", 50)

		fn := this.UseCurrentClicked.Bind(this)
		Gui, Add, Button, hwndhwnd xs+5 y+5 w125, Copy Current >>>>
		GuiControl, +g, % hwnd, % fn

		Gui, Add, GroupBox, % "x160 ym w60 h130 section Center", Invert
		this.AddControl("CheckBox", "Invert", 0, "xs+25 w30 y40")
		
		Gui, Add, GroupBox, % "x235 ym w150 h130 section Center", Breakpoint settings
		this.SegmentManager := new this.SegmentControl(this.OnSegmentChanged.Bind(this), this, "xs+10 y25")
		
		Gui, Add, GroupBox, % "x400 ym w135 h130 section Center", Output Virtual Axis
		this.AddControl("OutputAxis", "OA1", this.MyOutputChangedValue.Bind(this), "xs+5 y25 w125")
		this.AddControl("AxisPreview", "", 0, this.IOControls.OA1, "xp y+5 w125", 50)
		
		Gui, Add, GroupBox, x540 ym w135 h130 section center, Settings String
		Gui, Add, Text, xs+5 y25, Copy this to another plugin`nof the same kind`nto duplicate settings
		this.AddControl("Edit", "Segments", this.OnSettingsLoad.Bind(this), "xs+5 y+10")
	}
	
	OnSegmentChanged(value){
		GuiControl, , % this.GuiControls.Segments.hwnd, % value
	}
	
	OnSettingsLoad(value){
		this.SegmentManager.LoadSettings(value)
	}
	
	UseCurrentClicked(){
		this.SegmentManager.SetInputBox(Round(this.IOControls.IA1.Get(), 2))
	}
	
	; The user changed options - store stick and axis selected for fast retreival
	MyOutputChangedValue(value){
		this.vAxis := value.Binding[1]
		this.vDevice := value.DeviceID
		this.OutputBound := value.IsBound()
	}
	
	; The user moved the selected input axis. Manipulate the output axis accordingly
	MyInputChangedState(value){
		value := UCR.Libraries.StickOps.AHKToInternal(value)
		if (this.OutputBound){
			if (this.GuiControls.Invert.Get()){
				value := UCR.Libraries.StickOps.Invert(value)
			}
			value := UCR.Libraries.StickOps.InternalToAHK(value)
			
			value := this.SegmentManager.GetValue(value)
			
			this.IOControls.OA1.Set(value)
		}
	}
	
	class SegmentControl {
		Segments := []
		Points := []
		
		__New(callback, parent, options := ""){
			this.parent := parent
			this.callback := callback
			Gui, Add, ListView, % "hwndhwnd w110 h75 section " options, Input|Output
			LV_ModifyCol(1, 50)
			LV_ModifyCol(2, 50)
			this.hLV := hwnd
			Gui, Add, Button, hwndhwnd x+5 yp w20 h75, -
			fn := this.DeleteClicked.Bind(this)
			GuiControl, +g, % hwnd, % fn
			Gui, Add, Edit, xs y+5 hwndhwnd w50
			this.hEditInput := hwnd
			Gui, Add, Edit, x+10 yp hwndhwnd w50
			this.hEditOutput := hwnd
			Gui, Add, Button, hwndhwnd x+5 yp w20, +
			fn := this.AddClicked.Bind(this)
			GuiControl, +g, % hwnd, % fn
		}
		
		; Set save to false when loading settings, to avoid triggering save on load
		_AddPoint(i, o){
			if (!this.IsNumeric(i) || !this.IsNumeric(o)){
				return false
			}
			inserted := false
			for index, obj in this.Points {
				if (obj[1] == i)
					return false
				if (obj[1] > i){
					this.Points.Insert(index, [round(i, 2), round(o, 2)])
					inserted := true
					break
				}
			}
			if (!inserted){
				this.Points.push([i, o])
			}
			return true
		}
		
		_RemovePoint(index){
			this.Points.RemoveAt(index)
			this.BuildSegments()
			this.SaveSettings()
		}
		
		LoadSettings(str){
			this.Points := []
			chunks := StrSplit(str, "|")
			for i, pt in chunks {
				p := StrSplit(pt, ",")
				res := this._AddPoint(p[1], p[2], false)
			}
			this.BuildSegments()
		}
		
		SaveSettings(){
			str := ""
			for i, chunk in this.Points {
				if (i > 1){
					str .= "|"
				}
				str .= chunk[1] "," chunk[2]
			}
			this.callback.Call(str)
		}
		
		SetInputBox(value){
			GuiControl, , % this.hEditInput, % value
		}
		
		BuildSegments(){
			gui, % this.parent.hwnd ":default"
			Gui, ListView, % this.hLV
			LV_Delete()
			for i, p in this.Points {
				LV_Add(, p[1], p[2])
			}
			pts := this.Points.clone()
			;~ pts.Insert(1, [0,0])
			pts.Push([100,100])
			;~ p0 := pts.removeat(1)
			p0 := [0,0]
			this.Segments := []
			for i, p1 in pts {
				this.Segments.push( {k : (p1.2-p0.2) / (p1.1-p0.1) , m : ( p1.1*p0.2 - p0.1*p1.2 ) / (p1.1-p0.1), end : p1.1 } ), p0 := p1
			}
		}
		
		AddClicked(){
			GuiControlGet, i, , % this.hEditInput
			GuiControlGet, o, , % this.hEditOutput
			if (!this._AddPoint(i, o)){
				SoundBeep, 200, 500
				return
			}
			this.BuildSegments()
			this.SaveSettings()
		}
		
		DeleteClicked(){
			Gui, ListView, % this.hLV
			row := LV_GetNext()
			this._RemovePoint(row)
		}
		
		IsNumeric(str){
			if str is number
				return true
			return false
		}
		
		GetValue(x){
			for i, seg in this.Segments
				if (x <= seg.end)
					return x*seg.k+seg.m
		}
	}
	
}

Re: Universal Control Remapper (UCR) - v0.1.19 26th Dec 2017

Posted: 13 Jan 2018, 14:03
by Helgef
Maybe you should allow to add points with the same breakpoint, eg, [50,0] [50,70] causes a jump (step) after 50. Although, the difference between that and [50,0] [50.01, 70] isn't noticable. Also, the listview is too small (win7, normal dpi), when I add a point I get scroll-bars, which lies inside the listview.

Cheers.

Re: Universal Control Remapper (UCR) - v0.1.19 26th Dec 2017

Posted: 13 Jan 2018, 14:56
by evilC
I suppose it is valid, and as we know, never discount a use-case cause you can't think of a use for it ;)

Re: Universal Control Remapper (UCR) - v0.1.19 26th Dec 2017

Posted: 13 Jan 2018, 14:59
by evilC
Oh, and could you maybe have a fiddle with the layout and find something that lays out ok on your system?

Re: Universal Control Remapper (UCR) - v0.1.19 26th Dec 2017

Posted: 13 Jan 2018, 15:25
by Helgef
I will later.

Re: Universal Control Remapper (UCR) - v0.1.19 26th Dec 2017

Posted: 14 Jan 2018, 04:54
by Oliver
Gents, the copy current function does not take into account the invert checkbox. E.g. when I place my physical throttle at 70% (30% in UCR's terms), select invert, and hit copy current, it creates a breapoint at 30% instead of 70%.

Re: Universal Control Remapper (UCR) - v0.1.19 26th Dec 2017

Posted: 14 Jan 2018, 11:36
by evilC
Good catch, will fix.

Re: Universal Control Remapper (UCR) - v0.1.19 26th Dec 2017

Posted: 14 Jan 2018, 13:06
by evilC
If invert is selected, clicking "Copy Current" adds inverted value.

Code: Select all

class AxisToAxisDetent extends _UCR.Classes.Plugin {
	Type := "Remapper (Axis To Axis with Detent)"
	Description := "Maps an axis input to a virtual axis output (With Detent)"
	vAxis := 0
	vDevice := 0
	; Set up the GUI to allow the user to select input and output axes
	Init(){
		Gui, Add, GroupBox, % "xm w135 h130 section Center", Input Axis
		this.AddControl("InputAxis", "IA1", 0, this.MyInputChangedState.Bind(this), "xs+5 w125 y25")
		this.AddControl("AxisPreview", "", 0, this.IOControls.IA1, "xs+5 y+5 w125", 50)

		fn := this.UseCurrentClicked.Bind(this)
		Gui, Add, Button, hwndhwnd xs+5 y+5 w125, Copy Current >>>>
		GuiControl, +g, % hwnd, % fn

		Gui, Add, GroupBox, % "x160 ym w60 h130 section Center", Invert
		this.AddControl("CheckBox", "Invert", 0, "xs+25 w30 y40")
		
		Gui, Add, GroupBox, % "x235 ym w150 h130 section Center", Breakpoint settings
		this.SegmentManager := new this.SegmentControl(this.OnSegmentChanged.Bind(this), this, "xs+10 y25")
		
		Gui, Add, GroupBox, % "x400 ym w135 h130 section Center", Output Virtual Axis
		this.AddControl("OutputAxis", "OA1", this.MyOutputChangedValue.Bind(this), "xs+5 y25 w125")
		this.AddControl("AxisPreview", "", 0, this.IOControls.OA1, "xp y+5 w125", 50)
		
		Gui, Add, GroupBox, x540 ym w135 h130 section center, Settings String
		Gui, Add, Text, xs+5 y25, Copy this to another plugin`nof the same kind`nto duplicate settings
		this.AddControl("Edit", "Segments", this.OnSettingsLoad.Bind(this), "xs+5 y+10")
	}
	
	OnSegmentChanged(value){
		GuiControl, , % this.GuiControls.Segments.hwnd, % value
	}
	
	OnSettingsLoad(value){
		this.SegmentManager.LoadSettings(value)
	}
	
	UseCurrentClicked(){
		value := UCR.Libraries.StickOps.AHKToInternal(this.IOControls.IA1.Get())
		if (this.GuiControls.Invert.Get()){
			value := UCR.Libraries.StickOps.Invert(value)
		}
		value := UCR.Libraries.StickOps.InternalToAHK(value)
		this.SegmentManager.SetInputBox(Round(value, 2))
	}
	
	; The user changed options - store stick and axis selected for fast retreival
	MyOutputChangedValue(value){
		this.vAxis := value.Binding[1]
		this.vDevice := value.DeviceID
		this.OutputBound := value.IsBound()
	}
	
	; The user moved the selected input axis. Manipulate the output axis accordingly
	MyInputChangedState(value){
		value := UCR.Libraries.StickOps.AHKToInternal(value)
		if (this.OutputBound){
			if (this.GuiControls.Invert.Get()){
				value := UCR.Libraries.StickOps.Invert(value)
			}
			value := UCR.Libraries.StickOps.InternalToAHK(value)
			
			value := this.SegmentManager.GetValue(value)
			
			this.IOControls.OA1.Set(value)
		}
	}
	
	class SegmentControl {
		Segments := []
		Points := []
		
		__New(callback, parent, options := ""){
			this.parent := parent
			this.callback := callback
			Gui, Add, ListView, % "hwndhwnd w110 h75 section " options, Input|Output
			LV_ModifyCol(1, 50)
			LV_ModifyCol(2, 50)
			this.hLV := hwnd
			Gui, Add, Button, hwndhwnd x+5 yp w20 h75, -
			fn := this.DeleteClicked.Bind(this)
			GuiControl, +g, % hwnd, % fn
			Gui, Add, Edit, xs y+5 hwndhwnd w50
			this.hEditInput := hwnd
			Gui, Add, Edit, x+10 yp hwndhwnd w50
			this.hEditOutput := hwnd
			Gui, Add, Button, hwndhwnd x+5 yp w20, +
			fn := this.AddClicked.Bind(this)
			GuiControl, +g, % hwnd, % fn
		}
		
		; Set save to false when loading settings, to avoid triggering save on load
		_AddPoint(i, o){
			if (!this.IsNumeric(i) || !this.IsNumeric(o)){
				return false
			}
			inserted := false
			for index, obj in this.Points {
				if (obj[1] == i)
					return false
				if (obj[1] > i){
					this.Points.Insert(index, [round(i, 2), round(o, 2)])
					inserted := true
					break
				}
			}
			if (!inserted){
				this.Points.push([i, o])
			}
			return true
		}
		
		_RemovePoint(index){
			this.Points.RemoveAt(index)
			this.BuildSegments()
			this.SaveSettings()
		}
		
		LoadSettings(str){
			this.Points := []
			chunks := StrSplit(str, "|")
			for i, pt in chunks {
				p := StrSplit(pt, ",")
				res := this._AddPoint(p[1], p[2], false)
			}
			this.BuildSegments()
		}
		
		SaveSettings(){
			str := ""
			for i, chunk in this.Points {
				if (i > 1){
					str .= "|"
				}
				str .= chunk[1] "," chunk[2]
			}
			this.callback.Call(str)
		}
		
		SetInputBox(value){
			value := UCR.Libraries.StickOps.AHKToInternal(value)
			if (this.GuiControls.Invert.Get()){
				value := UCR.Libraries.StickOps.Invert(value)
			}
			value := UCR.Libraries.StickOps.InternalToAHK(value)
			GuiControl, , % this.hEditInput, % value
		}
		
		BuildSegments(){
			gui, % this.parent.hwnd ":default"
			Gui, ListView, % this.hLV
			LV_Delete()
			for i, p in this.Points {
				LV_Add(, p[1], p[2])
			}
			pts := this.Points.clone()
			;~ pts.Insert(1, [0,0])
			pts.Push([100,100])
			;~ p0 := pts.removeat(1)
			p0 := [0,0]
			this.Segments := []
			for i, p1 in pts {
				this.Segments.push( {k : (p1.2-p0.2) / (p1.1-p0.1) , m : ( p1.1*p0.2 - p0.1*p1.2 ) / (p1.1-p0.1), end : p1.1 } ), p0 := p1
			}
		}
		
		AddClicked(){
			GuiControlGet, i, , % this.hEditInput
			GuiControlGet, o, , % this.hEditOutput
			if (!this._AddPoint(i, o)){
				SoundBeep, 200, 500
				return
			}
			this.BuildSegments()
			this.SaveSettings()
		}
		
		DeleteClicked(){
			Gui, ListView, % this.hLV
			row := LV_GetNext()
			this._RemovePoint(row)
		}
		
		IsNumeric(str){
			if str is number
				return true
			return false
		}
		
		GetValue(x){
			for i, seg in this.Segments
				if (x <= seg.end)
					return x*seg.k+seg.m
		}
	}
	
}

Re: Universal Control Remapper (UCR) - v0.1.19 26th Dec 2017

Posted: 15 Jan 2018, 01:06
by Oliver
Hmm, not for me it doesn't.

Re: Universal Control Remapper (UCR) - v0.1.19 26th Dec 2017

Posted: 15 Jan 2018, 05:32
by evilC
Hmm yeah, I think I had goofed. Should be good now.

Re: Universal Control Remapper (UCR) - v0.1.19 26th Dec 2017

Posted: 15 Jan 2018, 13:52
by Oliver
Well, to put it bluntly, I've got everything I asked for.

What are your plans? Or you looking to further tweak or add settings? I'd say it serves it's purpose very well right now! Maybe indeed the option to set the values and have a graph as visual representation of what you've just set.

Re: Universal Control Remapper (UCR) - v0.1.19 26th Dec 2017

Posted: 15 Jan 2018, 14:03
by Helgef
The ListView problem I had was easy to fix by changing to

Code: Select all

LV_ModifyCol(1, 44)
LV_ModifyCol(2, 44)
in AxisToAxisDetent.SegmentControl.__new.

Cheers. :wave:

Re: Universal Control Remapper (UCR) - v0.1.19 26th Dec 2017

Posted: 15 Jan 2018, 15:17
by evilC
Looks like the listview was simply not wide enough to properly accommodate 100.00, so I made it bigger.
Barring any other issues, I think this one should be the final version:

Code: Select all

class AxisToAxisDetent extends _UCR.Classes.Plugin {
	Type := "Remapper (Axis To Axis with Detent)"
	Description := "Maps an axis input to a virtual axis output (With Detent)"
	vAxis := 0
	vDevice := 0
	; Set up the GUI to allow the user to select input and output axes
	Init(){
		Gui, Add, GroupBox, % "xm w135 h130 section Center", Input Axis
		this.AddControl("InputAxis", "IA1", 0, this.MyInputChangedState.Bind(this), "xs+5 w125 y25")
		this.AddControl("AxisPreview", "", 0, this.IOControls.IA1, "xs+5 y+5 w125", 50)

		fn := this.UseCurrentClicked.Bind(this)
		Gui, Add, Button, hwndhwnd xs+5 y+5 w125, Copy Current >>>>
		GuiControl, +g, % hwnd, % fn

		Gui, Add, GroupBox, % "x155 ym w60 h130 section Center", Invert
		this.AddControl("CheckBox", "Invert", 0, "xs+25 w30 y40")
		
		Gui, Add, GroupBox, % "x225 ym w170 h130 section Center", Breakpoint settings
		this.SegmentManager := new this.SegmentControl(this.OnSegmentChanged.Bind(this), this, "xs+10 y25")
		
		Gui, Add, GroupBox, % "x400 ym w135 h130 section Center", Output Virtual Axis
		this.AddControl("OutputAxis", "OA1", this.MyOutputChangedValue.Bind(this), "xs+5 y25 w125")
		this.AddControl("AxisPreview", "", 0, this.IOControls.OA1, "xp y+5 w125", 50)
		
		Gui, Add, GroupBox, x540 ym w135 h130 section center, Settings String
		Gui, Add, Text, xs+5 y25, Copy this to another plugin`nof the same kind`nto duplicate settings
		this.AddControl("Edit", "Segments", this.OnSettingsLoad.Bind(this), "xs+5 y+10")
	}
	
	OnSegmentChanged(value){
		GuiControl, , % this.GuiControls.Segments.hwnd, % value
	}
	
	OnSettingsLoad(value){
		this.SegmentManager.LoadSettings(value)
	}
	
	UseCurrentClicked(){
		value := UCR.Libraries.StickOps.AHKToInternal(this.IOControls.IA1.Get())
		if (this.GuiControls.Invert.Get()){
			value := UCR.Libraries.StickOps.Invert(value)
		}
		value := UCR.Libraries.StickOps.InternalToAHK(value)
		this.SegmentManager.SetInputBox(value)
	}
	
	; The user changed options - store stick and axis selected for fast retreival
	MyOutputChangedValue(value){
		this.vAxis := value.Binding[1]
		this.vDevice := value.DeviceID
		this.OutputBound := value.IsBound()
	}
	
	; The user moved the selected input axis. Manipulate the output axis accordingly
	MyInputChangedState(value){
		value := UCR.Libraries.StickOps.AHKToInternal(value)
		if (this.OutputBound){
			if (this.GuiControls.Invert.Get()){
				value := UCR.Libraries.StickOps.Invert(value)
			}
			value := UCR.Libraries.StickOps.InternalToAHK(value)
			
			value := this.SegmentManager.GetValue(value)
			
			this.IOControls.OA1.Set(value)
		}
	}
	
	class SegmentControl {
		Segments := []
		Points := []
		
		__New(callback, parent, options := ""){
			this.parent := parent
			this.callback := callback
			Gui, Add, ListView, % "hwndhwnd w110 h75 section " options, Input|Output
			LV_ModifyCol(1, 50)
			LV_ModifyCol(2, 50)
			this.hLV := hwnd
			Gui, Add, Button, hwndhwnd x+5 yp w20 h75, -
			fn := this.DeleteClicked.Bind(this)
			GuiControl, +g, % hwnd, % fn
			Gui, Add, Edit, xs y+5 hwndhwnd w50
			this.hEditInput := hwnd
			Gui, Add, Edit, x+10 yp hwndhwnd w50
			this.hEditOutput := hwnd
			Gui, Add, Button, hwndhwnd x+5 yp w20, +
			fn := this.AddClicked.Bind(this)
			GuiControl, +g, % hwnd, % fn
		}
		
		; Set save to false when loading settings, to avoid triggering save on load
		_AddPoint(i, o){
			if (!this.IsNumeric(i) || !this.IsNumeric(o)){
				return false
			}
			inserted := false
			for index, obj in this.Points {
				if (obj[1] == i)
					return false
				if (obj[1] > i){
					this.Points.Insert(index, [round(i, 2), round(o, 2)])
					inserted := true
					break
				}
			}
			if (!inserted){
				this.Points.push([round(i, 2), round(o, 2)])
			}
			return true
		}
		
		_RemovePoint(index){
			this.Points.RemoveAt(index)
			this.BuildSegments()
			this.SaveSettings()
		}
		
		LoadSettings(str){
			this.Points := []
			chunks := StrSplit(str, "|")
			for i, pt in chunks {
				p := StrSplit(pt, ",")
				res := this._AddPoint(p[1], p[2], false)
			}
			this.BuildSegments()
		}
		
		SaveSettings(){
			str := ""
			for i, chunk in this.Points {
				if (i > 1){
					str .= "|"
				}
				str .= chunk[1] "," chunk[2]
			}
			this.callback.Call(str)
		}
		
		SetInputBox(value){
			GuiControl, , % this.hEditInput, % round(value, 2)
		}
		
		BuildSegments(){
			gui, % this.parent.hwnd ":default"
			Gui, ListView, % this.hLV
			LV_Delete()
			for i, p in this.Points {
				LV_Add(, p[1], p[2])
			}
			pts := this.Points.clone()
			;~ pts.Insert(1, [0,0])
			pts.Push([100,100])
			;~ p0 := pts.removeat(1)
			p0 := [0,0]
			this.Segments := []
			for i, p1 in pts {
				this.Segments.push( {k : (p1.2-p0.2) / (p1.1-p0.1) , m : ( p1.1*p0.2 - p0.1*p1.2 ) / (p1.1-p0.1), end : p1.1 } ), p0 := p1
			}
		}
		
		AddClicked(){
			GuiControlGet, i, , % this.hEditInput
			GuiControlGet, o, , % this.hEditOutput
			if (!this._AddPoint(i, o)){
				SoundBeep, 200, 500
				return
			}
			this.BuildSegments()
			this.SaveSettings()
		}
		
		DeleteClicked(){
			Gui, ListView, % this.hLV
			row := LV_GetNext()
			this._RemovePoint(row)
		}
		
		IsNumeric(str){
			if str is number
				return true
			return false
		}
		
		GetValue(x){
			for i, seg in this.Segments
				if (x <= seg.end)
					return x*seg.k+seg.m
		}
	}
	
}