orca/ui Orca

Code

main.odin ¶
921 linesSource

1package src
2
3/*
4Original Source: https://github.com/orca-app/orca/blob/main/samples/ui/src/main.c
5
6Can be run using in the local folder
71. odin.exe build main.odin -file -target:orca_wasm32 -out:module.wasm 
82. orca bundle --name orca_output --resource-dir data module.wasm
93. orca_output\bin\orca_output.exe
10*/
11
12import "core:log"
13import "base:runtime"
14import "core:fmt"
15import oc "core:sys/orca"
16
17ctx: runtime.Context
18
19frameSize := oc.vec2{1200, 838}
20
21surface: oc.surface
22renderer: oc.canvas_renderer
23canvas: oc.canvas_context
24fontRegular: oc.font
25fontBold: oc.font
26ui: oc.ui_context
27textArena: oc.arena
28logArena: oc.arena
29logLines: oc.str8_list
30
31// NOTE(Skytrias): global instead of statics because of odin
32labelFont: ^oc.font
33
34cmd :: enum {
35	NONE,
36	SET_DARK_THEME,
37	SET_LIGHT_THEME,
38}
39
40command := cmd.NONE
41
42main :: proc() {
43	context.logger = oc.create_odin_logger()
44	ctx = context
45
46	oc.window_set_title("Orca UI Demo")
47	oc.window_set_size(frameSize)
48
49	renderer = oc.canvas_renderer_create()
50	surface = oc.canvas_surface_create(renderer)
51	canvas = oc.canvas_context_create()
52	oc.ui_init(&ui)
53
54	fonts := [2]^oc.font{&fontRegular, &fontBold}
55	fontNames := [2]string{"/OpenSans-Regular.ttf", "/OpenSans-Bold.ttf"}
56	for i in 0 ..< len(fontNames) {
57		scratch := oc.scratch_begin()
58
59		file := oc.file_open(fontNames[i], {.READ}, {})
60		if oc.file_last_error(file) != .OK {
61			log.errorf("Couldn't open file %s\n", fontNames[i])
62		}
63		size := oc.file_size(file)
64		buffer := cast([^]byte)oc.arena_push(scratch.arena, size)
65		oc.file_read(file, size, buffer)
66		oc.file_close(file)
67		ranges := [?]oc.unicode_range {
68			oc.UNICODE_BASIC_LATIN,
69			oc.UNICODE_C1_CONTROLS_AND_LATIN_1_SUPPLEMENT,
70			oc.UNICODE_LATIN_EXTENDED_A,
71			oc.UNICODE_LATIN_EXTENDED_B,
72			oc.UNICODE_SPECIALS,
73		}
74
75		fonts[i]^ = oc.font_create_from_memory(oc.str8_from_buffer(size, buffer), 5, &ranges[0])
76		oc.scratch_end(scratch)
77	}
78	labelFont = &fontRegular
79
80	oc.arena_init(&textArena)
81	oc.arena_init(&logArena)
82	oc.list_init(&logLines.list)
83}
84
85@(export)
86oc_on_raw_event :: proc "c" (event: ^oc.event) {
87	oc.ui_process_event(event)
88}
89
90@(export)
91oc_on_resize :: proc "c" (width, height: u32) {
92	frameSize.x = f32(width)
93	frameSize.y = f32(height)
94}
95
96log_push :: proc(line: string) {
97	oc.str8_list_push(&logArena, &logLines, line)
98}
99
100log_pushf :: proc(format: string, args: ..any) {
101	str := fmt.tprintf(format, ..args)
102	oc.str8_list_push(&logArena, &logLines, str)
103}
104
105column_begin :: proc(header: string, widthFraction: f32) {
106	style := oc.ui_style {
107		size = {{.PARENT, widthFraction, 1, 0}, {.PARENT, 1, 0, 0}},
108		layout = {axis = .Y, margin = {0, 8}, spacing = 24},
109		bgColor = ui.theme.bg1,
110		borderColor = ui.theme.border,
111		borderSize = 1,
112		roundness = ui.theme.roundnessSmall,
113	}
114
115	style_mask :=
116		oc.SIZE +
117		{
118				.LAYOUT_AXIS,
119				.LAYOUT_MARGIN_Y,
120				.LAYOUT_SPACING,
121				.BG_COLOR,
122				.BORDER_COLOR,
123				.BORDER_SIZE,
124				.ROUNDNESS,
125			}
126
127	oc.ui_style_next(style, style_mask)
128	oc.ui_box_begin_str8(header, {.DRAW_BACKGROUND, .DRAW_BORDER})
129
130	{
131		style = oc.ui_style {
132			size = {{.PARENT, 1, 0, 0}, {}},
133			layout = {align = {.CENTER, .START}},
134		}
135		oc.ui_style_next(style, {.SIZE_WIDTH, .LAYOUT_ALIGN_X})
136		oc.ui_container("header", {})
137
138		oc.ui_style_next({fontSize = 18}, {.FONT_SIZE})
139		oc.ui_label_str8(header)
140	}
141
142	style = {
143		size = {{.PARENT, 1, 0, 0}, {.PARENT, 1, 1, 0}},
144		layout = {align = {.START, .START}, margin = {16, 0}, spacing = 24},
145	}
146	oc.ui_style_next(style, oc.SIZE + {.LAYOUT_ALIGN_X, .LAYOUT_MARGIN_X, .LAYOUT_SPACING})
147	oc.ui_box_begin_str8("contents", {})
148}
149
150column_end :: proc() {
151	oc.ui_box_end() // contents
152	oc.ui_box_end() // column
153}
154
155@(deferred_none = column_end)
156column :: proc(header: string, widthFraction: f32) {
157	column_begin(header, widthFraction)
158}
159
160labeled_slider :: proc(label: string, value: ^f32) {
161	oc.ui_style_next({layout = {axis = .X, spacing = 8}}, {.LAYOUT_AXIS, .LAYOUT_SPACING})
162	oc.ui_container(label, {})
163
164	oc.ui_style_match_after(
165		oc.ui_pattern_owner(),
166		{size = {{.PIXELS, 100, 0, 0}, {}}},
167		{.SIZE_WIDTH},
168	)
169	oc.ui_label_str8(label)
170
171	oc.ui_style_next({size = {{.PIXELS, 100, 0, 0}, {}}}, {.SIZE_WIDTH})
172	oc.ui_slider("slider", value)
173}
174
175// reset_next_radio_group_to_dark_theme :: (oc.arena* arena)
176
177@(export)
178oc_on_frame_refresh :: proc "c" () {
179	context = ctx
180
181	scratch := oc.scratch_begin()
182
183	#partial switch command {
184	case .SET_DARK_THEME:
185		oc.ui_set_theme(&oc.UI_DARK_THEME)
186	case .SET_LIGHT_THEME:
187		oc.ui_set_theme(&oc.UI_LIGHT_THEME)
188	}
189	command = .NONE
190
191	defaultStyle := oc.ui_style {
192		font = fontRegular,
193	}
194	{
195		oc.ui_frame(frameSize, defaultStyle, {.FONT})
196		//--------------------------------------------------------------------------------------------
197		// Menu bar
198		//--------------------------------------------------------------------------------------------
199		{
200			oc.ui_menu_bar("menu_bar")
201
202			{
203				oc.ui_menu("File")
204				if oc.ui_menu_button("Quit").pressed {
205					oc.request_quit()
206				}
207			}
208
209			{
210				oc.ui_menu("Theme")
211				if oc.ui_menu_button("Dark theme").pressed {
212					command = .SET_DARK_THEME
213				}
214				if oc.ui_menu_button("Light theme").pressed {
215					command = .SET_LIGHT_THEME
216				}
217			}
218		}
219
220		{
221			oc.ui_panel("main panel", {})
222			oc.ui_style_next(
223				{
224					size = {{.PARENT, 1, 0, 0}, {.PARENT, 1, 1, 0}},
225					layout = {axis = .X, margin = 16, spacing = 16},
226				},
227				oc.SIZE + oc.LAYOUT_MARGINS + {.LAYOUT_AXIS, .LAYOUT_SPACING},
228			)
229
230			{
231				oc.ui_container("background", {.DRAW_BACKGROUND})
232				{
233					column("Widgets", f32(1.0) / 3)
234
235					{
236						oc.ui_style_next(
237							{size = {{.PARENT, 1, 0, 0}, {}}, layout = {axis = .X, spacing = 32}},
238							{.SIZE_WIDTH, .LAYOUT_AXIS, .LAYOUT_SPACING},
239						)
240						oc.ui_container("top", {})
241
242						{
243							oc.ui_style_next(
244								{layout = {axis = .Y, spacing = 24}},
245								{.LAYOUT_AXIS, .LAYOUT_SPACING},
246							)
247							oc.ui_container("top_left", {})
248
249							//-----------------------------------------------------------------------------
250							// Label
251							//-----------------------------------------------------------------------------
252							oc.ui_label("Label")
253
254							//-----------------------------------------------------------------------------
255							// Button
256							//-----------------------------------------------------------------------------
257							if oc.ui_button("Button").clicked {
258								log_push("Button clicked")
259							}
260
261							oc.ui_style_next(
262								{layout = {axis = .X, align = {{}, .CENTER}, spacing = 8}},
263								{.LAYOUT_AXIS, .LAYOUT_ALIGN_Y, .LAYOUT_SPACING},
264							)
265
266							{
267								oc.ui_container("checkbox", {})
268
269								//-------------------------------------------------------------------------
270								// Checkbox
271								//-------------------------------------------------------------------------
272								@(static)
273								checked := false
274								if oc.ui_checkbox("checkbox", &checked).clicked {
275									if checked {
276										log_push("Checkbox checked")
277									} else {
278										log_push("Checkbox unchecked")
279									}
280								}
281
282								oc.ui_label("Checkbox")
283							}
284						}
285
286						//---------------------------------------------------------------------------------
287						// Vertical slider
288						//---------------------------------------------------------------------------------
289						@(static)
290						vSliderValue := f32(0)
291						@(static)
292						vSliderLoggedValue := f32(0)
293						@(static)
294						vSliderLogTime := f64(0)
295						oc.ui_style_next({size = {{}, {.PIXELS, 130, 0, 0}}}, {.SIZE_HEIGHT})
296						oc.ui_slider("v_slider", &vSliderValue)
297						now := oc.clock_time(.MONOTONIC)
298						if (now - vSliderLogTime) >= 0.2 && vSliderValue != vSliderLoggedValue {
299							log_pushf("Vertical slider moved to %f", vSliderValue)
300							vSliderLoggedValue = vSliderValue
301							vSliderLogTime = now
302						}
303
304						{
305							oc.ui_style_next(
306								{layout = {axis = .Y, spacing = 24}},
307								{.LAYOUT_AXIS, .LAYOUT_SPACING},
308							)
309							oc.ui_container("top_right", {})
310
311							//-----------------------------------------------------------------------------
312							// Tooltip
313							//-----------------------------------------------------------------------------
314							if oc.ui_label("Tooltip").hovering {
315								oc.ui_tooltip("Hi")
316							}
317
318							//-----------------------------------------------------------------------------
319							// Radio group
320							//-----------------------------------------------------------------------------
321							@(static)
322							radioSelected: i32 = 0
323							options := [?]oc.str8{"Radio 1", "Radio 2"}
324							radioGroupInfo := oc.ui_radio_group_info {
325								selectedIndex = radioSelected,
326								optionCount   = 2,
327								options       = &options[0],
328							}
329							result := oc.ui_radio_group("radio_group", &radioGroupInfo)
330							radioSelected = result.selectedIndex
331							if result.changed {
332								log_pushf("Selected Radio %i", result.selectedIndex + 1)
333							}
334
335							//-----------------------------------------------------------------------------
336							// Horizontal slider
337							//-----------------------------------------------------------------------------
338							@(static)
339							hSliderValue := f32(0)
340							@(static)
341							hSliderLoggedValue := f32(0)
342							@(static)
343							hSliderLogTime := f64(0)
344							oc.ui_style_next({size = {{.PIXELS, 130, 0, 0}, {}}}, {.SIZE_WIDTH})
345							oc.ui_slider("h_slider", &hSliderValue)
346							now = oc.clock_time(.MONOTONIC)
347							if (now - hSliderLogTime) >= 0.2 &&
348							   hSliderValue != hSliderLoggedValue {
349								log_pushf("Slider moved to %f", hSliderValue)
350								hSliderLoggedValue = hSliderValue
351								hSliderLogTime = now
352							}
353						}
354					}
355
356					//-------------------------------------------------------------------------------------
357					// Text box
358					//-------------------------------------------------------------------------------------
359					oc.ui_style_next({size = {{.PIXELS, 305, 0, 0}, {.TEXT, 0, 0, 0}}}, oc.SIZE)
360					@(static)
361					text: oc.str8
362					res := oc.ui_text_box("text", scratch.arena, text)
363					if res.changed {
364						oc.arena_clear(&textArena)
365						text = oc.str8_push_copy(&textArena, res.text)
366					}
367					if res.accepted {
368						log_pushf("Entered text \"%s\"", text)
369					}
370
371					//-------------------------------------------------------------------------------------
372					// Select
373					//-------------------------------------------------------------------------------------
374					@(static)
375					selected: i32 = -1
376					options := [?]oc.str8{"Option 1", "Option 2"}
377					info := oc.ui_select_popup_info {
378						selectedIndex = selected,
379						optionCount   = 2,
380						options       = &options[0],
381						placeholder   = "Select",
382					}
383					result := oc.ui_select_popup("select", &info)
384					if result.selectedIndex != selected {
385						log_pushf("Selected %s", options[result.selectedIndex])
386					}
387					selected = result.selectedIndex
388
389					//-------------------------------------------------------------------------------------
390					// Scrollable panel
391					//-------------------------------------------------------------------------------------
392					oc.ui_style_next(
393						{
394							size = {{.PARENT, 1, 0, 0}, {.PARENT, 1, 1, 0}},
395							bgColor = ui.theme.bg2,
396							borderColor = ui.theme.border,
397							borderSize = 1,
398							roundness = ui.theme.roundnessSmall,
399						},
400						oc.SIZE + {.BG_COLOR, .BORDER_COLOR, .BORDER_SIZE, .ROUNDNESS},
401					)
402
403					{
404						oc.ui_panel("log", {.DRAW_BACKGROUND, .DRAW_BORDER})
405
406						{
407							oc.ui_style_next(
408								{layout = {margin = 16}},
409								oc.LAYOUT_MARGINS + {.LAYOUT_SPACING},
410							)
411							oc.ui_container("contents", {})
412
413							if oc.list_empty(logLines.list) {
414								oc.ui_style_next({_color = ui.theme.text2}, {.COLOR})
415								oc.ui_label("Log")
416							}
417
418							iter: ^oc.list_elt
419							i: int
420							for logLine in oc.list_for(
421								&logLines.list,
422								&iter,
423								oc.str8_elt,
424								"listElt",
425							) {
426								id := fmt.tprintf("%d", i)
427
428								oc.ui_container(id, {})
429								oc.ui_label_str8(logLine.string)
430
431								i += 1
432							}
433						}
434					}
435				}
436
437				//-----------------------------------------------------------------------------------------
438				// Styling
439				//-----------------------------------------------------------------------------------------
440				// Initial values here are hardcoded from the dark theme and everything is overridden all
441				// the time. In a real program you'd only override what you need and supply the values from
442				// ui.theme or ui.theme.palette.
443				//
444				// Rule-based styling is described at
445				// https://www.forkingpaths.dev/posts/23-03-10/rule_based_styling_imgui.html
446				{
447					column("Styling", f32(2.0) / 3)
448					@(static)
449					unselectedWidth := f32(16)
450					@(static)
451					unselectedHeight := f32(16)
452					@(static)
453					unselectedRoundness := f32(8)
454					@(static)
455					unselectedBgColor := oc.color{{0.086, 0.086, 0.102, 1}, .RGB}
456					@(static)
457					unselectedBorderColor := oc.color{{0.976, 0.976, 0.976, 0.35}, .RGB}
458					@(static)
459					unselectedBorderSize := f32(1)
460					@(static)
461					unselectedWhenStatus: oc.ui_status
462
463					@(static)
464					selectedWidth := f32(16)
465					@(static)
466					selectedHeight := f32(16)
467					@(static)
468					selectedRoundness := f32(8)
469					@(static)
470					selectedCenterColor := oc.color{{1, 1, 1, 1}, .RGB}
471					@(static)
472					selectedBgColor := oc.color{{0.33, 0.66, 1, 1}, .RGB}
473					@(static)
474					selectedWhenStatus: oc.ui_status
475
476					@(static)
477					labelFontColor := oc.color{{0.976, 0.976, 0.976, 1}, .RGB}
478					@(static)
479					labelFontSize := f32(14)
480
481					oc.ui_style_next(
482						{
483							size = {{.PARENT, 1, 0, 0}, {.PIXELS, 152, 0, 0}},
484							layout = {margin = {320, 16}},
485							bgColor = oc.UI_DARK_THEME.bg0,
486							roundness = oc.UI_DARK_THEME.roundnessSmall,
487						},
488						oc.SIZE + oc.LAYOUT_MARGINS + {.BG_COLOR, .ROUNDNESS},
489					)
490
491					{
492						oc.ui_container("styled_radios", {.DRAW_BACKGROUND, .DRAW_BORDER})
493						reset_next_radio_group_to_dark_theme(scratch.arena)
494
495						unselectedPattern := oc.ui_pattern{}
496						oc.ui_pattern_push(
497							scratch.arena,
498							&unselectedPattern,
499							{kind = .TAG, tag = oc.ui_tag_make_str8("radio")},
500						)
501
502						if unselectedWhenStatus != {} {
503							oc.ui_pattern_push(
504								scratch.arena,
505								&unselectedPattern,
506								{op = .AND, kind = .STATUS, status = unselectedWhenStatus},
507							)
508						}
509						oc.ui_style_match_after(
510							unselectedPattern,
511							{
512								size = {
513									{.PIXELS, unselectedWidth, 0, 0},
514									{.PIXELS, unselectedHeight, 0, 0},
515								},
516								bgColor = unselectedBgColor,
517								borderColor = unselectedBorderColor,
518								borderSize = unselectedBorderSize,
519								roundness = unselectedRoundness,
520							},
521							oc.SIZE + {.BG_COLOR, .BORDER_COLOR, .BORDER_SIZE, .ROUNDNESS},
522						)
523
524						selectedPattern := oc.ui_pattern{}
525						oc.ui_pattern_push(
526							scratch.arena,
527							&selectedPattern,
528							{kind = .TAG, tag = oc.ui_tag_make_str8("radio_selected")},
529						)
530
531						if selectedWhenStatus != {} {
532							oc.ui_pattern_push(
533								scratch.arena,
534								&selectedPattern,
535								{op = .AND, kind = .STATUS, status = selectedWhenStatus},
536							)
537						}
538						oc.ui_style_match_after(
539							selectedPattern,
540							{
541								size = {
542									{.PIXELS, selectedWidth, 0, 0},
543									{.PIXELS, selectedHeight, 0, 0},
544								},
545								_color = selectedCenterColor,
546								bgColor = selectedBgColor,
547								roundness = selectedRoundness,
548							},
549							oc.SIZE + {.COLOR, .BG_COLOR, .ROUNDNESS},
550						)
551
552						labelPattern := oc.ui_pattern{}
553						labelTag := oc.ui_tag_make_str8("label")
554						oc.ui_pattern_push(
555							scratch.arena,
556							&labelPattern,
557							{kind = .TAG, tag = labelTag},
558						)
559						oc.ui_style_match_after(
560							labelPattern,
561							{_color = labelFontColor, font = labelFont^, fontSize = labelFontSize},
562							{.COLOR, .FONT, .FONT_SIZE},
563						)
564
565						@(static)
566						selectedIndex: i32 = 0
567						options := [?]oc.str8{"I", "Am", "Stylish"}
568						radioGroupInfo := oc.ui_radio_group_info {
569							selectedIndex = selectedIndex,
570							optionCount   = len(options),
571							options       = &options[0],
572						}
573						result := oc.ui_radio_group("radio_group", &radioGroupInfo)
574						selectedIndex = result.selectedIndex
575					}
576
577					{
578						oc.ui_style_next(
579							{layout = {axis = .X, spacing = 32}},
580							{.LAYOUT_AXIS, .LAYOUT_SPACING},
581						)
582						oc.ui_container("controls", {})
583
584						{
585							oc.ui_style_next(
586								{layout = {axis = .Y, spacing = 16}},
587								{.LAYOUT_AXIS, .LAYOUT_SPACING},
588							)
589							oc.ui_container("unselected", {})
590
591							oc.ui_style_next({fontSize = 16}, {.FONT_SIZE})
592							oc.ui_label("Radio style")
593
594							{
595								oc.ui_style_next({layout = {spacing = 4}}, {.LAYOUT_SPACING})
596								oc.ui_container("size", {})
597
598								widthSlider := f32(unselectedWidth - 8) / 16
599								labeled_slider("Width", &widthSlider)
600								unselectedWidth = 8 + widthSlider * 16
601
602								heightSlider := f32(unselectedHeight - 8) / 16
603								labeled_slider("Height", &heightSlider)
604								unselectedHeight = 8 + heightSlider * 16
605
606								roundnessSlider := f32(unselectedRoundness - 4) / 8
607								labeled_slider("Roundness", &roundnessSlider)
608								unselectedRoundness = 4 + roundnessSlider * 8
609							}
610
611							{
612								oc.ui_style_next({layout = {spacing = 4}}, {.LAYOUT_SPACING})
613								oc.ui_container("background", {})
614
615								labeled_slider("Background R", &unselectedBgColor.r)
616								labeled_slider("Background G", &unselectedBgColor.g)
617								labeled_slider("Background B", &unselectedBgColor.b)
618								labeled_slider("Background A", &unselectedBgColor.a)
619							}
620
621							{
622								oc.ui_style_next({layout = {spacing = 4}}, {.LAYOUT_SPACING})
623								oc.ui_container("border", {})
624
625								labeled_slider("Border R", &unselectedBorderColor.r)
626								labeled_slider("Border G", &unselectedBorderColor.g)
627								labeled_slider("Border B", &unselectedBorderColor.b)
628								labeled_slider("Border A", &unselectedBorderColor.a)
629							}
630
631							borderSizeSlider := f32(unselectedBorderSize) / 5
632							labeled_slider("Border size", &borderSizeSlider)
633							unselectedBorderSize = borderSizeSlider * 5
634
635							{
636								oc.ui_style_next({layout = {spacing = 10}}, {.LAYOUT_SPACING})
637								oc.ui_container("status_override", {})
638
639								oc.ui_label("Override")
640
641								@(static)
642								statusIndex: i32 = 0
643								statusOptions := [?]oc.str8 {
644									"Always",
645									"When hovering",
646									"When active",
647								}
648								statusInfo := oc.ui_radio_group_info {
649									selectedIndex = statusIndex,
650									optionCount   = len(statusOptions),
651									options       = &statusOptions[0],
652								}
653								result := oc.ui_radio_group("status", &statusInfo)
654								statusIndex = result.selectedIndex
655								switch statusIndex {
656								case 0:
657									unselectedWhenStatus = {}
658								case 1:
659									unselectedWhenStatus = {.HOVER}
660								case 2:
661									unselectedWhenStatus = {.ACTIVE}
662								}
663							}
664						}
665
666						{
667							oc.ui_style_next(
668								{layout = {axis = .Y, spacing = 16}},
669								{.LAYOUT_AXIS, .LAYOUT_SPACING},
670							)
671							oc.ui_container("selected", {})
672
673							oc.ui_style_next({fontSize = 16}, {.FONT_SIZE})
674							oc.ui_label("Radio selected style")
675
676							{
677								oc.ui_style_next({layout = {spacing = 4}}, {.LAYOUT_SPACING})
678								oc.ui_container("size", {})
679
680								widthSlider := f32(selectedWidth - 8) / 16
681								labeled_slider("Width", &widthSlider)
682								selectedWidth = 8 + widthSlider * 16
683
684								heightSlider := f32(selectedHeight - 8) / 16
685								labeled_slider("Height", &heightSlider)
686								selectedHeight = 8 + heightSlider * 16
687
688								roundnessSlider := f32(selectedRoundness - 4) / 8
689								labeled_slider("Roundness", &roundnessSlider)
690								selectedRoundness = 4 + roundnessSlider * 8
691							}
692
693							{
694								oc.ui_style_next({layout = {spacing = 4}}, {.LAYOUT_SPACING})
695								oc.ui_container("color", {})
696
697								labeled_slider("Center R", &selectedCenterColor.r)
698								labeled_slider("Center G", &selectedCenterColor.g)
699								labeled_slider("Center B", &selectedCenterColor.b)
700								labeled_slider("Center A", &selectedCenterColor.a)
701							}
702
703							{
704								oc.ui_style_next({layout = {spacing = 4}}, {.LAYOUT_SPACING})
705								oc.ui_container("background", {})
706
707								labeled_slider("Background R", &selectedBgColor.r)
708								labeled_slider("Background G", &selectedBgColor.g)
709								labeled_slider("Background B", &selectedBgColor.b)
710								labeled_slider("Background A", &selectedBgColor.a)
711							}
712
713							{
714								oc.ui_style_next({layout = {spacing = 10}}, {.LAYOUT_SPACING})
715								oc.ui_container("status_override", {})
716
717								oc.ui_style_next(
718									{size = {{}, {.PIXELS, 30, 0, 0}}},
719									{.SIZE_HEIGHT},
720								)
721								oc.ui_box_make_str8("spacer", {})
722								oc.ui_label("Override")
723
724								@(static)
725								statusIndex: i32 = 0
726								statusOptions := [?]oc.str8 {
727									"Always",
728									"When hovering",
729									"When active",
730								}
731								statusInfo := oc.ui_radio_group_info {
732									selectedIndex = statusIndex,
733									optionCount   = len(statusOptions),
734									options       = &statusOptions[0],
735								}
736								result := oc.ui_radio_group("status", &statusInfo)
737								statusIndex = result.selectedIndex
738								switch statusIndex {
739								case 0:
740									selectedWhenStatus = {}
741								case 1:
742									selectedWhenStatus = {.HOVER}
743								case 2:
744									selectedWhenStatus = {.ACTIVE}
745								}
746							}
747						}
748
749						{
750							oc.ui_style_next(
751								{layout = {axis = .Y, spacing = 16}},
752								{.LAYOUT_AXIS, .LAYOUT_SPACING},
753							)
754							oc.ui_container("label", {})
755
756							oc.ui_style_next({fontSize = 16}, {.FONT_SIZE})
757							oc.ui_label("Label style")
758
759							{
760								oc.ui_style_next(
761									{layout = {axis = .X, spacing = 8}},
762									{.LAYOUT_AXIS, .LAYOUT_SPACING},
763								)
764								oc.ui_container("font_color", {})
765
766								oc.ui_style_match_after(
767									oc.ui_pattern_owner(),
768									{size = {{.PIXELS, 100, 0, 0}, {}}},
769									{.SIZE_WIDTH},
770								)
771								oc.ui_label("Font color")
772
773								@(static)
774								colorSelected: i32 = 0
775								colorNames := [?]oc.str8 {
776									"Default",
777									"Red",
778									"Orange",
779									"Amber",
780									"Yellow",
781									"Lime",
782									"Light Green",
783									"Green",
784								}
785								colors := [?]oc.color {
786									oc.UI_DARK_THEME.text0,
787									oc.UI_DARK_THEME.palette.red5,
788									oc.UI_DARK_THEME.palette.orange5,
789									oc.UI_DARK_THEME.palette.amber5,
790									oc.UI_DARK_THEME.palette.yellow5,
791									oc.UI_DARK_THEME.palette.lime5,
792									oc.UI_DARK_THEME.palette.lightGreen5,
793									oc.UI_DARK_THEME.palette.green5,
794								}
795								colorInfo := oc.ui_select_popup_info {
796									selectedIndex = colorSelected,
797									optionCount   = len(colorNames),
798									options       = &colorNames[0],
799								}
800								colorResult := oc.ui_select_popup("color", &colorInfo)
801								colorSelected = colorResult.selectedIndex
802								labelFontColor = colors[colorSelected]
803							}
804
805							{
806								oc.ui_style_next(
807									{layout = {axis = .X, spacing = 8}},
808									{.LAYOUT_AXIS, .LAYOUT_SPACING},
809								)
810								oc.ui_container("font", {})
811
812								oc.ui_style_match_after(
813									oc.ui_pattern_owner(),
814									{size = {{.PIXELS, 100, 0, 0}, {}}},
815									{.SIZE_WIDTH},
816								)
817								oc.ui_label("Font")
818
819								@(static)
820								fontSelected: i32 = 0
821								fontNames := [?]oc.str8{"Regular", "Bold"}
822								fonts := [?]^oc.font{&fontRegular, &fontBold}
823								fontInfo := oc.ui_select_popup_info {
824									selectedIndex = fontSelected,
825									optionCount   = len(fontNames),
826									options       = &fontNames[0],
827								}
828								fontResult := oc.ui_select_popup("font_style", &fontInfo)
829								fontSelected = fontResult.selectedIndex
830								labelFont = fonts[fontSelected]
831							}
832
833							fontSizeSlider := f32(labelFontSize - 8) / 16
834							labeled_slider("Font size", &fontSizeSlider)
835							labelFontSize = 8 + fontSizeSlider * 16
836						}
837					}
838				}
839			}
840		}
841	}
842
843	oc.canvas_context_select(canvas)
844
845	oc.set_color(ui.theme.bg0)
846	oc.clear()
847
848	oc.ui_draw()
849	oc.canvas_render(renderer, canvas, surface)
850	oc.canvas_present(renderer, surface)
851
852	oc.scratch_end(scratch)
853}
854
855// This makes sure the light theme doesn't break the styling overrides
856// You won't need it in a real program as long as your colors come from ui.theme or ui.theme.palette
857reset_next_radio_group_to_dark_theme :: proc "c" (arena: ^oc.arena) {
858	defaultTag := oc.ui_tag_make_str8("radio")
859	defaultPattern := oc.ui_pattern{}
860	oc.ui_pattern_push(arena, &defaultPattern, {kind = .TAG, tag = defaultTag})
861	defaultStyle := oc.ui_style {
862		borderColor = oc.UI_DARK_THEME.text3,
863		borderSize  = 1,
864	}
865	defaultMask: oc.ui_style_mask = {.BORDER_COLOR, .BORDER_SIZE}
866	oc.ui_style_match_after(defaultPattern, defaultStyle, defaultMask)
867
868	hoverPattern := oc.ui_pattern{}
869	oc.ui_pattern_push(arena, &hoverPattern, {kind = .TAG, tag = defaultTag})
870	oc.ui_pattern_push(arena, &hoverPattern, {op = .AND, kind = .STATUS, status = {.HOVER}})
871	hoverStyle := oc.ui_style {
872		bgColor     = oc.UI_DARK_THEME.fill0,
873		borderColor = oc.UI_DARK_THEME.primary,
874	}
875	hoverMask: oc.ui_style_mask = {.BG_COLOR, .BORDER_COLOR}
876	oc.ui_style_match_after(hoverPattern, hoverStyle, hoverMask)
877
878	activePattern := oc.ui_pattern{}
879	oc.ui_pattern_push(arena, &activePattern, {kind = .TAG, tag = defaultTag})
880	oc.ui_pattern_push(arena, &activePattern, {op = .AND, kind = .STATUS, status = {.ACTIVE}})
881	activeStyle := oc.ui_style {
882		bgColor     = oc.UI_DARK_THEME.fill1,
883		borderColor = oc.UI_DARK_THEME.primary,
884	}
885	activeMask: oc.ui_style_mask = {.BG_COLOR, .BORDER_COLOR}
886	oc.ui_style_match_after(activePattern, activeStyle, activeMask)
887
888	selectedTag := oc.ui_tag_make_str8("radio_selected")
889	selectedPattern := oc.ui_pattern{}
890	oc.ui_pattern_push(arena, &selectedPattern, {kind = .TAG, tag = selectedTag})
891	selectedStyle := oc.ui_style {
892		_color  = oc.UI_DARK_THEME.palette.white,
893		bgColor = oc.UI_DARK_THEME.primary,
894	}
895	selectedMask: oc.ui_style_mask = {.COLOR, .BG_COLOR}
896	oc.ui_style_match_after(selectedPattern, selectedStyle, selectedMask)
897
898	selectedHoverPattern := oc.ui_pattern{}
899	oc.ui_pattern_push(arena, &selectedHoverPattern, {kind = .TAG, tag = selectedTag})
900	oc.ui_pattern_push(
901		arena,
902		&selectedHoverPattern,
903		{op = .AND, kind = .STATUS, status = {.HOVER}},
904	)
905	selectedHoverStyle := oc.ui_style {
906		bgColor = oc.UI_DARK_THEME.primaryHover,
907	}
908	oc.ui_style_match_after(selectedHoverPattern, selectedHoverStyle, {.BG_COLOR})
909
910	selectedActivePattern := oc.ui_pattern{}
911	oc.ui_pattern_push(arena, &selectedActivePattern, {kind = .TAG, tag = selectedTag})
912	oc.ui_pattern_push(
913		arena,
914		&selectedActivePattern,
915		{op = .AND, kind = .STATUS, status = {.ACTIVE}},
916	)
917	selectedActiveStyle := oc.ui_style {
918		bgColor = oc.UI_DARK_THEME.primaryActive,
919	}
920	oc.ui_style_match_after(selectedActivePattern, selectedActiveStyle, {.BG_COLOR})
921}

Assets 2

Declarations Used 72