Register

Focusing on Trees

Skytrias | Michael Kutowski
3 months, 2 weeks ago
Todool's old structure relied on Task and Text Nodes alone.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
TaskNode :: struct {
    prev, next: ^TaskNode,
    parent: ^TaskNode,
    
    task_head, task_tail, task_jump: ^TaskNode,
    text_head, text_tail: ^TextNode,
    
    ...
}

TextNode :: struct {
    parent: ^TaskNode,
    prev, next: ^TextNode,
    
    length: u8,
    bytes: [BOX_CAP]byte,
}


This was obviously bad. It seriously limited the more advanced things i wanted to do. I wanted TextNode's to be more generic. Since i got really frustrated i began thinking about another issue i didnt like about Todool, the UI.

I had built a Retained Mode UI (RMGUI) around the tree data and a completly seperate Immediate Mode UI (IMGUI) for the sidebar, menu, notifications, etc. This was terrible since i had 2 different systems all working in a different way and not playing well together.

So i started experimenting with RMGUI again, since i really liked how powerful they can be. Take Luigi from nakst for example, simple yet powerful. I tried pulling in different RMGUI experiments into Todool but again, they were seperate systems which i hated.

I would have rather liked forming the UI around Todool itself.

So i experimented trying to pull everything together, while also making the SubNodes in Tasks more generic. Here's where I'm at right now.

Twitter for videos

On my twitter you can see the proof of concept and now I'm diving more deeply into the concept since i really like it.

The new data layout is the following (using Odin).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
TaskNode :: struct {
	parent, prev, next: ^TaskNode,
	head, tail: ^TaskNode,
	
	sub_head, sub_tail: ^SubNode,

	...

	custom_calls: map[ShortcutCall]proc(),
	custom_draw: proc(node: ^TaskNode),
}

SubNode :: struct {
	parent: ^TaskNode,
	prev, next: ^SubNode,
	data: SubData,
	...
}

SubData :: union {
	SubText,
	SubButton,
	SubToggle,
	SubU8,
	SubInt,
	SubF32,
	SubHex,
	
	SubColor,
	SubPadding,
	...
}

SubText :: struct {
	text: string,
}

SubButton :: struct {
	call: proc(),
}

SubToggle :: struct {
	state: ^bool,
}

SubU8 :: struct {
	ptr: ^u8,
	clamped: bool,
}

SubInt :: struct {
	ptr: ^int,
	start, end: int, // range
	clamped: bool,
}

SubF32 :: struct {
	ptr: ^f32,
	start, end: f32, // range
	clamped: bool,
}

SubColor :: struct {
	color: ^Color,
}

SubHex :: struct {
	ptr: ^Color,
	clamped: bool,
}

SubPadding :: struct {
	width: int,
	height: int,
}


You can see this allows for way more than my old code ever could've and I'm not even done adding different SubNode types yet.

In this case SubText is the general text node that can be edited. SubValue types are values that get converted to text when edited and reconverted back to their pointer locations. Also can do special types like padding or colors.

Here's some usage code for creating the Options tree seen in some of the videos on twitter.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
create_option_nodes :: proc(x, y: int) -> ^TaskNode {
	options := task_scoped();
	options.rect.l = x;
	options.rect.t = y;
	options.disabled = true;
	sub_init(SubText { "options" }).disabled = true;;
	
	{
		task_scoped().disabled = true;
		sub_init(SubText { "visual" }).disabled = true;;
		
		{
			task_scoped().disabled = true;
			sub_init(SubText { "tab" }).disabled = true;
			sub_init(SubInt { ptr = &ui.tab, start = 10, end = 100 });
		}
		
		{
			task_scoped().disabled = true;
			sub_init(SubText { "line_height" }).disabled = true;
			sub_init(SubInt { ptr = &ui.line_height, start = 20, end = 50 });
		}
		
		{
			task_scoped().disabled = true;
			sub_init(SubText { "font" }).disabled = true;
			font := &core.fonts[.Editor];
			sub_init(SubF32 { ptr = &font.temp_size, start = font.min_size, end = font.max_size });
			
		}
	}
	
	return options;
}


The disabled params are still temporary - they just disable things from being changed, which i could probably make on a parent task basis.

I know this was long, what do you guys think though? Jumping into Theme editing all with the same movement for the actual program itself feels really powerful and for everything else.
Log in to comment