How to make your own Timeline (NOT ENCRYPTED)


#1

  try(cui.UnregisterDialogBar timelineRollout) catch()
  try(destroydialog timelineRollout) catch()
  rollout timelineRollout "timelineRollout" width:2000 height:24
  (
  	mapped fn setLifetimeControl control = dotnet.setLifetimeControl control #dotnet
  	dotnetcontrol panel "UserControl" pos:[0,0] height:24
  	
  	on panel Paint s e do if s.tag != off do
  	(
  		s.tag = off
  		s.Refresh()
  		s.tag = on
  	)	
  	on timelineRollout resized size do panel.Width = size.x
  		
  	local slidepanel
  	fn makeSlidePanel = 
  	(
  		panel.backcolor = panel.backcolor.Gray
  		
  		s_sp = dotnetobject "NumericUpDown"
  		s_sp.dock = s_sp.dock.Left
  		s_sp.Width = 50
  
  		e_sp = dotnetobject "NumericUpDown"
  		e_sp.dock = e_sp.dock.Right
  		e_sp.Width = 50
  		
  		slidepanel = dotnetobject "UserControl"
  		slidepanel.dock = s_sp.dock.Fill
  
  		slider = dotnetobject "UserControl"
  		slider.Width = 100 
  		slider.height = 20
  		
  		s_bt = dotnetobject "Button"
  		s_bt.dock = s_sp.dock.Left
  		
  		e_bt = dotnetobject "Button"
  		e_bt.dock = s_sp.dock.Right
  		
  		a_bt = dotnetobject "Button"
  		a_bt.backcolor = s_bt.backcolor.LightBlue
  		a_bt.dock = s_sp.dock.Fill
  		
  		#(s_bt, e_bt).width = 10
  		#(s_bt, e_bt).height = 20
  		#(s_bt, e_bt).backcolor = s_bt.backcolor.SkyBlue
  		#(a_bt, s_bt, e_bt).flatstyle = s_bt.flatstyle.flat
  		a_bt.flatappearance.bordercolor = s_bt.backcolor.steelblue
  		s_bt.flatappearance.bordercolor = e_bt.flatappearance.bordercolor = s_bt.backcolor.darkslategray
  		slider.controls.addrange #(a_bt, s_bt, e_bt)
  		
  		slidepanel.controls.addrange #(slider)
  		panel.controls.addrange #(slidepanel, s_sp, e_sp)
  		
  		fn onMouseDown s e = (s.tag = e.x)
  		dotnet.addeventhandler a_bt "MouseDown" onMouseDown
  		dotnet.addeventhandler s_bt "MouseDown" onMouseDown
  		dotnet.addeventhandler e_bt "MouseDown" onMouseDown
  		
  		fn onMouseMove s e = if e.Button == e.Button.Left do
  		(
  			d = s.parent.location.x + e.x - s.tag
  			if d >= 0 and (d + s.parent.width) <= s.parent.parent.width do s.parent.location.x = d
  		)
  		dotnet.addeventhandler a_bt "MouseMove" onMouseMove
  		fn onMouseMove s e = if e.Button == e.Button.Left do
  		(
  			d = e.x - s.tag
  			if (s.parent.width - d) > 40 do 
  			(
  				s.parent.location.x += d
  				s.parent.width -= d
  			)
  		)
  		dotnet.addeventhandler s_bt "MouseMove" onMouseMove
  		fn onMouseMove s e = if e.Button == e.Button.Left do
  		(
  			d = e.x - s.tag
  			if (s.parent.width + d) > 40 do s.parent.width += d
  		)
  		dotnet.addeventhandler e_bt "MouseMove" onMouseMove
  		
  		fn onSizeChanged s e = if s.controls.count > 0 do
  		(
  			c = s.controls.item[0] 
  			if (c.location.x + c.width) > s.width do c.location.x = amax 0 (s.width - c.width)
  			if c.width > s.width do c.width = amax 40 s.width
  		)
  		dotnet.addeventhandler slidepanel "Resize" onSizeChanged
  
  		setLifetimeControl #(a_bt, s_bt, e_bt, slidepanel)
  		slidepanel
  	)
  	
  	on timelineRollout open do slidepanel = makeSlidePanel()
  )
  createdialog timelineRollout pos:[0,-100] minSize:[1900,24]
  cui.RegisterDialogBar timelineRollout style:#(#cui_dock_bottom, #cui_floatable)
  cui.DockDialogBar timelineRollout #cui_dock_bottom
  

all other is much easier… the logic of its work you can steal from Maya

if the community will be interested and I have time I can continue this project.


#2

i want to show this project in a process of development… i hope that it might be helpful for script beginners. also i hope that some ideas and tricks will be interesting for higher level of max script developers as well.

here is my plan for the progress:

change the framework to WPF (just as a sample of WPF using in MXS)

make the UI not flicker

hook to the trackbar

setup time change and time units change callbacks

any other ideas for the development?


#3

what benefit does this timeline have over the existing one?


#4

Don’t get me wrong but most of timeline operations are hidden (must be used shortcuts), whitch
is good, but for the beginners this is useless. They need to open all the time
Time Configuration dialog to change time range or some other parameters, which is a slower solution then using Denis approach.
Denis your “to do list” is fantastic. Just would like to see further progress of your code.
Cheers!


#5

i will just replicate the Maya timeline control. also i see one benefit… the working animation time range will be saved with the file. max saves only current animation range.

so i’m adding to my plan:

save the tool settings with file

all time-change operations originaly not undoable in max. try to make them undoable through the using of the tool

here is another idea…

support the snapping to tags


#6

there is an opposite situation for me. i’m old. and i’m working with many tools at the same time, and can’t remember all shortcuts for all of them… :slight_smile:
that’s why i prefer the visual UI.


#7

You forgot “Get rid of those horrible NumericUpDowns” :slight_smile:

I’ve recently written a max spinner replica dotnet control in c#, complete with screen wrapping. I’ll try to clean up the code and post it soon.


#8

the question is - do we need a spinner to set time numbers? honestly working for many years in MAX I’ve never used ‘arrows’ control of a time spinner. maybe only Right Click sometimes to reset.
I always type…


#9

I’ve decided to stay with System.Windows.Forms, and only at the end of the project show the WPF version.

well… to enter time we need a control. let’s start with simple Numeric (Integer) TextBox to support frames.

it has to meet two conditions:

allow only entering digits, control chars (enter, escape, backspace, del, etc.), and “-”

“-” symbol has be only one and only first

any other?

here is my version:


   	fn makeTimeBox width:50 backcolor:undefined =
   	(
   		tb = dotnetobject "TextBox"
   		tb.Width = width
   		tb.Backcolor = backcolor
   		
   		fn onGotFocus s e = enableAccelerators = off
   		fn onLostFocus s e = enableAccelerators = on
   		dotnet.addeventhandler tb "GotFocus" onGotFocus
   		dotnet.addeventhandler tb "LostFocus" onLostFocus
   
   		fn onKeyPress s e = 
   		(
   			char = dotnetclass "System.Char"
   			c = e.KeyChar
   			if not (char.IsControl c) and not (char.IsDigit c) do
   			(
   				e.Handled = not 
   				(
   					(c == "-") and (findstring s.Text c) == undefined
   				)
   			)
   		)
   		dotnet.addeventhandler tb "KeyPress" onKeyPress
   		fn onTextChanged s e = if s.Text != "-" and s.Text as Integer == undefined do
   		(
   			i = amax 1 s.SelectionStart
   			s.Text = replace s.Text i 1 ""
   
   			s.SelectionStart = i-1
   			s.SelectionLength = 0
   		)
   		dotnet.addeventhandler tb "TextChanged" onTextChanged
   		tb
   	)
   
  /*
  try(destroydialog timelineRollout) catch()
  rollout timelineRollout "timelineRollout" width:200 height:24
  (
  	dotnetcontrol panel "UserControl" pos:[0,0] height:24
  
  	on timelineRollout open do
  	(
  		sp = makeTimeBox()
  		sp.Dock = sp.Dock.Left
  		panel.controls.add sp
  		dotnet.setLifetimeControl sp #dotnet
  	)
  )
  createdialog timelineRollout
  */
  
   

any other smarter or more compact is welcome.


#10

also we need to set some number when the control lost focus… but we will add it later when will hook our controls to max time system (variables)


#11

missing closing parentheses in line

( c == "-") and (findstring s.Text c) == undefined )

Also, I can not enter “-” at all for some reason…


#12

i know… and fixed … thanx


#13

ops. i see the problem… i can’t set first “-” if no other numbers entered… so it need a little tweaking.
… fixed.


#14

here is a la max NumericUpDown…

it was more complicated, but hopefully i didn’t forget anything:


fn makeTimeSpinner width:70 value:0  =
(
	sp = dotnetobject "NumericUpDown"
	sp.Width = width
	
	sp.Value = value
	sp.Minimum = -1e9
	sp.Maximum = 1e9
	
	fn onGotFocus s e = enableAccelerators = off
	fn onLostFocus s e = 
	(
		s.Text = s.Value as string
		enableAccelerators = on
	)
	dotnet.addeventhandler sp "GotFocus" onGotFocus
	dotnet.addeventhandler sp "LostFocus" onLostFocus

	fn onKeyPress s e = 
	(
		char = dotnetclass "System.Char"
		c = e.KeyChar
		if not (char.IsControl c) and not (char.IsDigit c) do
		(
			e.Handled = not ((c == "-") and (findstring s.Text c) == undefined)
		)
	)
	dotnet.addeventhandler sp "KeyPress" onKeyPress
	fn onTextChanged s e = if s.Text != "-" and s.Text as Integer == undefined do
	(
		t = s.Controls.Item[1]
		i = amax 1 t.SelectionStart
		s.Text = replace s.Text i 1 ""

		t.SelectionStart = i-1
		t.SelectionLength = 0
	)
	dotnet.addeventhandler sp "TextChanged" onTextChanged

	fn onMouseDown s e = 
	(
		tag = s.tag
		case e.Button of
		(
			 (e.Button.Left): tag[2] == e.y
			(e.Button.Right): if tag[3] == 1 do s.value = tag[1]
		)
		s.tag = tag
	)
	dotnet.addeventhandler sp "MouseDown" onMouseDown
	fn onMouseUp s e = 
	(
		tag = s.tag
		case e.Button of
		(
			 (e.Button.Left): tag[1] == s.value
			(e.Button.Right): if tag[3] == 0 do s.value = 0
		)
		s.tag = tag
	)
	dotnet.addeventhandler sp "MouseUp" onMouseUp
	fn onMouseMove s e = 
	(
		case e.Button of
		(
			(e.Button.Left):
			(
				tag = s.tag
				tag[3] = 1
				s.value = tag[1] + (tag[2] - e.y)*s.Increment 
				s.tag = tag
			)
			(e.Button.None): s.tag = #(s.value, e.y, 0)
		)
	)
	dotnet.addeventhandler sp "MouseMove" onMouseMove

	sp.tag = #(sp.value,0,0)
	sp
)



#15

Only screen wrapping :slight_smile:


#16

NumericUpDown doesn’t catch Enter key… i don’t want to do any User32 things. but I can’t find another solution yet.


#17

This is my try to use TextBox for Numeric input (see attachment).
But maybe this is not the best solution.:hmm:
I do not know whether you’re planning to add something similar like THIS in your script
It’s not bad idea.


#18

LO means that when we drag mouse UP/DOWN it stops on screen edges. That’s the right point.

here is a version that fixes this problem. It calls edgeless mouse movement. Also I stick X cursor position during dragging, and change cursor to Size North/South…


fn makeTimeSpinner width:60 value:0 onChange: =
(
	sp = dotnetobject "NumericUpDown"
	sp.Width = width
	sp.Backcolor = backcolor
	
	sp.Minimum = -1e9
	sp.Maximum = 1e9
	sp.Value = value
	
	fn onGotFocus s e = enableAccelerators = off
	fn onLostFocus s e = 
	(
		s.Text = s.Value as string
		enableAccelerators = on
	)
	dotnet.addeventhandler sp "GotFocus" onGotFocus
	dotnet.addeventhandler sp "LostFocus" onLostFocus

	fn onKeyPress s e = 
	(
		char = dotnetclass "System.Char"
		c = e.KeyChar
		if not (char.IsControl c) and not (char.IsDigit c) do
		(
			e.Handled = not ((c == "-") and (findstring s.Text c) == undefined)
		)
	)
	dotnet.addeventhandler sp "KeyPress" onKeyPress
	fn onTextChanged s e = 
	(
		if s.Text != "-" and s.Text as Integer == undefined do
		(
			t = s.Controls.Item[1]
			i = amax 1 t.SelectionStart
			s.Text = replace s.Text i 1 ""

			t.SelectionStart = i-1
			t.SelectionLength = 0
		)
	)
	dotnet.addeventhandler sp "TextChanged" onTextChanged

	fn onMouseDown s e = 
	(
		tag = s.tag
		case e.Button of
		(
			 (e.Button.Left): tag[2] == e.y
			(e.Button.Right): if tag[3] == 1 do s.value = tag[1]
		)
		s.tag = tag
	)
	dotnet.addeventhandler sp "MouseDown" onMouseDown
	fn onMouseUp s e = 
	(
		tag = s.tag
		case e.Button of
		(
			 (e.Button.Left): tag[1] == s.value
			(e.Button.Right): if tag[3] == 0 do s.value = 0
		)
		s.tag = tag
	)
	dotnet.addeventhandler sp "MouseUp" onMouseUp
	fn onMouseMove s e = 
	(
		p = s.Cursor.Current.Position
		tag = s.tag
		
		case e.Button of
		(
			(e.Button.Left):
			(
				s.Cursor.Current = (dotnetclass "System.Windows.Forms.Cursors").SizeNS
				
				h = (dotnetclass "SystemInformation").VirtualScreen.Height 
				
				if act = (p.x != tag[4]) do p.x = tag[4]
				case p.y of 
				(
					0:  
					(
						p.y = h-1 
						tag[2] += h-2
						act = on
					)
					(h-1):
					(
						p.y = 0
						tag[2] -= h-2
						act = on
					)
				)
				if act do s.Cursor.Current.Position = p
			
				tag[3] = 1
				s.value = tag[1] + (tag[2] - e.y)*s.Increment 
				
				s.tag = tag
			)
			(e.Button.None): 
			(
				s.Cursor.Current = (dotnetclass "System.Windows.Forms.Cursors").Arrow
				s.tag = #(s.value, e.y, 0, p.x)
			)
		)
	)
	dotnet.addeventhandler sp "MouseMove" onMouseMove

	if onChange != unsupplied do dotnet.addeventhandler sp "ValueChanged" onChange
		
	sp.tag = #(sp.value,0,0,0)
	sp
)

try(destroydialog timelineRollout) catch()
rollout timelineRollout "timelineRollout" width:200 height:24
(
	dotnetcontrol panel "UserControl" pos:[0,0] height:24

	on timelineRollout open do
	(
		sp = makeTimeSpinner()
		sp.Dock = sp.Dock.Left
		panel.controls.addrange #(sp)
		dotnet.setLifetimeControl sp #dotnet
	)
)
createdialog timelineRollout


#19

to make our spinner almost perfect we have to solve two problems:
#1 support Enter and Escape keys to set or reset the value
#2 disable this annoying BEEP when a wrong key is pressed. (letters, some controls, symbols, ect.)


#20

When doing anything artistic I use the spinners all the time. When I know exactly what I’m after I type it in. yes you need both if you intend on appealing to a wider audience.

I have been working on this for time ranges.